diff --git a/Recipe b/Recipe index 1fd302bb..f02f1680 100644 --- a/Recipe +++ b/Recipe @@ -253,7 +253,7 @@ GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym GTKMAIN = gtkmain cmdline # Non-SSH back ends (putty, puttytel, plink). -NONSSH = telnet raw rlogin ldisc pinger +NONSSH = telnet raw rlogin supdup ldisc pinger # SSH back end (putty, plink, pscp, psftp). ARITH = mpint ecc diff --git a/be_all.c b/be_all.c index 4f272f82..f035dfdc 100644 --- a/be_all.c +++ b/be_all.c @@ -26,6 +26,7 @@ const struct BackendVtable *const backends[] = { &ssh_backend, &telnet_backend, &rlogin_backend, + &supdup_backend, &raw_backend, &sshconn_backend, NULL diff --git a/be_all_s.c b/be_all_s.c index d8e24d54..2da3bb4c 100644 --- a/be_all_s.c +++ b/be_all_s.c @@ -26,6 +26,7 @@ const struct BackendVtable *const backends[] = { &ssh_backend, &telnet_backend, &rlogin_backend, + &supdup_backend, &raw_backend, &serial_backend, &sshconn_backend, diff --git a/be_nos_s.c b/be_nos_s.c index f298f1b5..566a2ff8 100644 --- a/be_nos_s.c +++ b/be_nos_s.c @@ -13,6 +13,7 @@ const char *const appname = "PuTTYtel"; const struct BackendVtable *const backends[] = { &telnet_backend, &rlogin_backend, + &supdup_backend, &raw_backend, &serial_backend, NULL diff --git a/be_nossh.c b/be_nossh.c index 9adc8837..a537874f 100644 --- a/be_nossh.c +++ b/be_nossh.c @@ -13,6 +13,7 @@ const char *const appname = "PuTTYtel"; const struct BackendVtable *const backends[] = { &telnet_backend, &rlogin_backend, + &supdup_backend, &raw_backend, NULL }; diff --git a/config.c b/config.c index 6fe74529..62ceee3a 100644 --- a/config.c +++ b/config.c @@ -2367,6 +2367,36 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(rlogin_localuser), conf_editbox_handler, I(CONF_localusername), I(1)); + /* + * The Protocol/SUPDUP panel. + */ + ctrl_settitle(b, "Connection/SUPDUP", + "Enabling and disabling SUPDUP user options"); + + s = ctrl_getset(b, "Connection/SUPDUP", "main", NULL); + + ctrl_editbox(s, "Location string", 'l', 70, + HELPCTX(supdup_location), + conf_editbox_handler, I(CONF_supdup_location), + I(1)); + + ctrl_radiobuttons(s, "Extended ASCII Character set:", 'z', 4, + HELPCTX(supdup_ascii), + conf_radiobutton_handler, + I(CONF_supdup_ascii_set), + "None", I(SUPDUP_CHARSET_ASCII), + "ITS", I(SUPDUP_CHARSET_ITS), + "WAITS", I(SUPDUP_CHARSET_WAITS), NULL); + + ctrl_checkbox(s, "**MORE** processing", 'm', + HELPCTX(supdup_more), + conf_checkbox_handler, + I(CONF_supdup_more)); + + ctrl_checkbox(s, "Terminal scrolling", 's', + HELPCTX(supdup_scroll), + conf_checkbox_handler, + I(CONF_supdup_scroll)); } /* diff --git a/putty.h b/putty.h index 298ad775..0a7390a9 100644 --- a/putty.h +++ b/putty.h @@ -364,12 +364,19 @@ enum { TITLE_NONE, TITLE_EMPTY, TITLE_REAL }; +enum { + /* SUPDUP character set options */ + SUPDUP_CHARSET_ASCII, SUPDUP_CHARSET_ITS, SUPDUP_CHARSET_WAITS +}; + enum { /* Protocol back ends. (CONF_protocol) */ PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH, PROT_SSHCONN, /* PROT_SERIAL is supported on a subset of platforms, but it doesn't * hurt to define it globally. */ PROT_SERIAL, + /* PROT_SUPDUP is the historical RFC 734 protocol. */ + PROT_SUPDUP, PROTOCOL_LIMIT, /* upper bound on number of protocols */ }; @@ -1299,6 +1306,11 @@ NORETURN void cleanup_exit(int); X(INT, NONE, serstopbits) \ X(INT, NONE, serparity) /* SER_PAR_NONE, SER_PAR_ODD, ... */ \ X(INT, NONE, serflow) /* SER_FLOW_NONE, SER_FLOW_XONXOFF, ... */ \ + /* Supdup options */ \ + X(STR, NONE, supdup_location) \ + X(INT, NONE, supdup_ascii_set) \ + X(BOOL, NONE, supdup_more) \ + X(BOOL, NONE, supdup_scroll) \ /* Keyboard options */ \ X(BOOL, NONE, bksp_is_delete) \ X(BOOL, NONE, rxvt_homeend) \ @@ -1766,6 +1778,11 @@ extern const struct BackendVtable telnet_backend; extern const struct BackendVtable ssh_backend; extern const struct BackendVtable sshconn_backend; +/* + * Exports from supdup.c. + */ +extern const struct BackendVtable supdup_backend; + /* * Exports from ldisc.c. */ diff --git a/settings.c b/settings.c index 411c2faa..5f51f3b0 100644 --- a/settings.c +++ b/settings.c @@ -786,6 +786,14 @@ void save_open_settings(settings_w *sesskey, Conf *conf) write_setting_b(sesskey, "ConnectionSharingUpstream", conf_get_bool(conf, CONF_ssh_connection_sharing_upstream)); write_setting_b(sesskey, "ConnectionSharingDownstream", conf_get_bool(conf, CONF_ssh_connection_sharing_downstream)); wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, false); + + /* + * SUPDUP settings + */ + write_setting_s(sesskey, "SUPDUPLocation", conf_get_str(conf, CONF_supdup_location)); + write_setting_i(sesskey, "SUPDUPCharset", conf_get_int(conf, CONF_supdup_ascii_set)); + write_setting_b(sesskey, "SUPDUPMoreProcessing", conf_get_bool(conf, CONF_supdup_more)); + write_setting_b(sesskey, "SUPDUPScrolling", conf_get_bool(conf, CONF_supdup_scroll)); } bool load_settings(const char *section, Conf *conf) @@ -1255,6 +1263,14 @@ void load_open_settings(settings_r *sesskey, Conf *conf) gppb(sesskey, "ConnectionSharingDownstream", true, conf, CONF_ssh_connection_sharing_downstream); gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys); + + /* + * SUPDUP settings + */ + gpps(sesskey, "SUPDUPLocation", "The Internet", conf, CONF_supdup_location); + gppi(sesskey, "SUPDUPCharset", false, conf, CONF_supdup_ascii_set); + gppb(sesskey, "SUPDUPMoreProcessing", false, conf, CONF_supdup_more); + gppb(sesskey, "SUPDUPScrolling", false, conf, CONF_supdup_scroll); } bool do_defaults(const char *session, Conf *conf) diff --git a/supdup.c b/supdup.c new file mode 100644 index 00000000..dcf7e1c2 --- /dev/null +++ b/supdup.c @@ -0,0 +1,924 @@ +/* +* Supdup backend +*/ + +#include +#include +#include + +#include "putty.h" + +/* + * TTYOPT FUNCTION BITS (36-bit bitmasks) +*/ +#define TOALT 0200000000000LL // Characters 0175 and 0176 are converted to altmode (0033) on input +#define TOCLC 0100000000000LL // (user option bit) Convert lower-case input to upper-case +#define TOERS 0040000000000LL // Selective erase is supported +#define TOMVB 0010000000000LL // Backspacing is supported +#define TOSAI 0004000000000LL // Stanford/ITS extended ASCII graphics character set is supported +#define TOSA1 0002000000000LL // (user option bit) Characters 0001-0037 displayed using Stanford/ITS chars +#define TOOVR 0001000000000LL // Overprinting is supported +#define TOMVU 0000400000000LL // Moving cursor upwards is supported +#define TOMOR 0000200000000LL // (user option bit) System should provide **MORE** processing +#define TOROL 0000100000000LL // (user option bit) Terminal should scroll instead of wrapping +#define TOLWR 0000020000000LL // Lowercase characters are supported +#define TOFCI 0000010000000LL // Terminal can generate CONTROL and META characters +#define TOLID 0000002000000LL // Line insert/delete operations supported +#define TOCID 0000001000000LL // Character insert/delete operations supported +#define TPCBS 0000000000040LL // Terminal is using the "intelligent terminal protocol" (must be on) +#define TPORS 0000000000010LL // Server should process output resets + +// Initialization words (36-bit constants) +#define WORDS 0777773000000 // Negative number of config words to send (6) in high 18 bits +#define TCTYP 0000000000007 // Defines the terminal type (MUST be 7) +#define TTYROL 0000000000001 // Scroll amount for terminal (1 line at a time) + + +// %TD opcodes +// +#define TDMOV 0200 // Cursor positioning +#define TDMV1 0201 // Internal cursor positioning +#define TDEOF 0202 // Erase to end of screen +#define TDEOL 0203 // Erase to end of line +#define TDDLF 0204 // Clear the character the cursor is on +#define TDCRL 0207 // Carriage return +#define TDNOP 0210 // No-op; should be ignored. +#define TDBS 0211 // Backspace (not in official SUPDUP spec) +#define TDLF 0212 // Linefeed (not in official SUPDUP spec) +#define TDCR 0213 // Carriage Return (ditto) +#define TDORS 0214 // Output reset +#define TDQOT 0215 // Quotes the following character +#define TDFS 0216 // Non-destructive forward space +#define TDMV0 0217 // General cursor positioning code +#define TDCLR 0220 // Erase the screen, home cursor +#define TDBEL 0221 // Generate an audio tone, bell, whatever +#define TDILP 0223 // Insert blank lines at the cursor +#define TDDLP 0224 // Delete lines at the cursor +#define TDICP 0225 // Insert blanks at cursor +#define TDDCP 0226 // Delete characters at cursor +#define TDBOW 0227 // Display black chars on white screen +#define TDRST 0230 // Reset %TDBOW + +/* Maximum number of octets following a %TD code. */ +#define TD_ARGS_MAX 4 + +typedef struct supdup_tag Supdup; +struct supdup_tag +{ + Socket *s; + bool closed_on_socket_error; + + Seat *seat; + LogContext *logctx; + int term_width, term_height; + + long long ttyopt; + long tcmxv; + long tcmxh; + + bool sent_location; + + Conf *conf; + + int bufsize; + + enum { + CONNECTING, // waiting for %TDNOP from server after sending connection params + CONNECTED // %TDNOP received, connected. + } state; + + enum { + TD_TOPLEVEL, + TD_ARGS, + TD_ARGSDONE + } tdstate; + + int td_code; + int td_argcount; + char td_args[TD_ARGS_MAX]; + int td_argindex; + + void (*print) (strbuf *outbuf, int c); + + Pinger *pinger; + + Plug plug; + Backend backend; +}; + +#define SUPDUP_MAX_BACKLOG 4096 + +static void c_write(Supdup *supdup, unsigned char *buf, int len) +{ + size_t backlog = seat_stdout(supdup->seat, buf, len); + sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG); +} + +static void supdup_send_location(Supdup *supdup) +{ + char locHeader[] = { 0300, 0302 }; + char* locString = conf_get_str(supdup->conf, CONF_supdup_location); + + sk_write(supdup->s, locHeader, sizeof(locHeader)); + sk_write(supdup->s, locString, strlen(locString) + 1); // include NULL terminator +} + +static void print_ascii(strbuf *outbuf, int c) +{ + /* In ASCII mode, ignore control characters. The server shouldn't + send them. */ + if (c >= 040 && c < 0177) + put_byte (outbuf, c); +} + +static void print_its(strbuf *outbuf, int c) +{ + /* The ITS character set is documented in RFC 734. */ + static const char *map[] = { + "\xc2\xb7", "\342\206\223", "\316\261", "\316\262", + "\342\210\247", "\302\254", "\316\265", "\317\200", + "\316\273", "\xce\xb3", "\xce\xb4", "\xe2\x86\x91", + "\xc2\xb1", "\xe2\x8a\x95", "\342\210\236", "\342\210\202", + "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252", + "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224", + "\xe2\x86\x90", "\342\206\222", "\xe2\x89\xa0", "\xe2\x97\x8a", + "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250", + " ", "!", "\"", "#", "$", "%", "&", "'", + "(", ")", "*", "+", ",", "-", ".", "/", + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", ":", ";", "<", "=", ">", "?", + "@", "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "[", "\\", "]", "^", "_", + "`", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "}", "~", "\xe2\x88\xab" + }; + + put_data (outbuf, map[c], strlen(map[c])); +} + +static void print_waits(strbuf *outbuf, int c) +{ + /* The WAITS character set used at the Stanford AI Lab is documented + here: https://www.saildart.org/allow/sail-charset-utf8.html */ + static const char *map[] = { + "", "\342\206\223", "\316\261", "\316\262", + "\342\210\247", "\302\254", "\316\265", "\317\200", + "\316\273", "", "", "", + "", "", "\342\210\236", "\342\210\202", + "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252", + "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224", + "_", "\342\206\222", "~", "\xe2\x89\xa0", + "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250", + " ", "!", "\"", "#", "$", "%", "&", "'", + "(", ")", "*", "+", ",", "-", ".", "/", + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", ":", ";", "<", "=", ">", "?", + "@", "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "[", "\\", "]", "\xe2\x86\x91", "\xe2\x86\x90", + "`", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "\xe2\x97\x8a", "}", "" + }; + + put_data (outbuf, map[c], strlen(map[c])); +} + +static void do_toplevel(Supdup *supdup, strbuf *outbuf, int c) +{ + // Toplevel: Waiting for a %TD code or a printable character + if (c >= 0200) { + // Handle SUPDUP %TD codes (codes greater than or equal to 200) + supdup->td_argindex = 0; + supdup->td_code = c; + switch (c) { + case TDMOV: + // %TD codes using 4 arguments + supdup->td_argcount = 4; + supdup->tdstate = TD_ARGS; + break; + + case TDMV0: + case TDMV1: + // %TD codes using 2 arguments + supdup->td_argcount = 2; + supdup->tdstate = TD_ARGS; + break; + + case TDQOT: + case TDILP: + case TDDLP: + case TDICP: + case TDDCP: + // %TD codes using 1 argument + supdup->td_argcount = 1; + supdup->tdstate = TD_ARGS; + break; + + case TDEOF: + case TDEOL: + case TDDLF: + case TDCRL: + case TDNOP: + case TDORS: + case TDFS: + case TDCLR: + case TDBEL: + case TDBOW: + case TDRST: + case TDBS: + case TDCR: + case TDLF: + // %TD codes using 0 arguments + supdup->td_argcount = 0; + supdup->tdstate = TD_ARGSDONE; + break; + + default: + // Unhandled, ignore + break; + } + } else { + supdup->print(outbuf, c); + } +} + +static void do_args(Supdup *supdup, strbuf *outbuf, int c) +{ + // Collect up args for %TD code + if (supdup->td_argindex < TD_ARGS_MAX) { + supdup->td_args[supdup->td_argindex] = c; + supdup->td_argindex++; + + if (supdup->td_argcount == supdup->td_argindex) { + // No more args, %TD code is ready to go. + supdup->tdstate = TD_ARGSDONE; + } + } else { + // Should never hit this state, if we do we will just + // return to TOPLEVEL. + supdup->tdstate = TD_TOPLEVEL; + } +} + +static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) +{ + char buf[4]; + int x, y; + + // Arguments for %TD code have been collected; dispatch based + // on the %TD code we're handling. + switch (supdup->td_code) { + case TDMOV: + /* + General cursor position code. Followed by four bytes; + the first two are the "old" vertical and horizontal + positions and may be ignored. The next two are the new + vertical and horizontal positions. The cursor should be + moved to this position. + */ + + // We only care about the new position. + strbuf_catf(outbuf, "\033[%d;%dH", supdup->td_args[2]+1, supdup->td_args[3]+1); + break; + + case TDMV0: + case TDMV1: + /* + General cursor position code. Followed by two bytes; + the new vertical and horizontal positions. + */ + strbuf_catf(outbuf, "\033[%d;%dH", supdup->td_args[0]+1, supdup->td_args[1]+1); + break; + + case TDEOF: + /* + Erase to end of screen. This is an optional function + since many terminals do not support this. If the + terminal does not support this function, it should be + treated the same as %TDEOL. + + %TDEOF does an erase to end of line, then erases all + lines lower on the screen than the cursor. The cursor + does not move. + */ + strbuf_catf(outbuf, "\033[J"); + break; + + case TDEOL: + /* + Erase to end of line. This erases the character + position the cursor is at and all positions to the right + on the same line. The cursor does not move. + */ + strbuf_catf(outbuf, "\033[K"); + break; + + case TDDLF: + /* + Clear the character position the cursor is on. The + cursor does not move. + */ + strbuf_catf(outbuf, "\033[X"); + break; + + case TDCRL: + /* + If the cursor is not on the bottom line of the screen, + move cursor to the beginning of the next line and clear + that line. If the cursor is at the bottom line, scroll + up. + */ + strbuf_catf(outbuf, "\015\012"); + break; + + case TDNOP: + /* + No-op; should be ignored. + */ + break; + + case TDORS: + /* + Output reset. This code serves as a data mark for + aborting output much as IAC DM does in the ordinary + TELNET protocol. + */ + outbuf->len = 0; + if (!seat_get_cursor_position(supdup->seat, &x, &y)) + x = y = 0; + buf[0] = 034; + buf[1] = 020; + buf[2] = y; + buf[3] = x; + sk_write(supdup->s, buf, 4); + break; + + case TDQOT: + /* + Quotes the following character. This is used when + sending 8-bit codes which are not %TD codes, for + instance when loading programs into an intelligent + terminal. The following character should be passed + through intact to the terminal. + */ + + put_byte(outbuf, supdup->td_args[0]); + break; + + case TDFS: + /* + Non-destructive forward space. The cursor moves right + one position; this code will not be sent at the end of a + line. + */ + + strbuf_catf(outbuf, "\033[C"); + break; + + case TDCLR: + /* + Erase the screen. Home the cursor to the top left hand + corner of the screen. + */ + strbuf_catf(outbuf, "\033[2J\033[H"); + break; + + case TDBEL: + /* + Generate an audio tone, bell, whatever. + */ + + strbuf_catf(outbuf, "\007"); + break; + + case TDILP: + /* + Insert blank lines at the cursor; followed by a byte + containing a count of the number of blank lines to + insert. The cursor is unmoved. The line the cursor is + on and all lines below it move down; lines moved off the + bottom of the screen are lost. + */ + strbuf_catf(outbuf, "\033[%dL", supdup->td_args[0]); + break; + + case TDDLP: + /* + Delete lines at the cursor; followed by a count. The + cursor is unmoved. The first line deleted is the one + the cursor is on. Lines below those deleted move up. + Newly- created lines at the bottom of the screen are + blank. + */ + strbuf_catf(outbuf, "\033[%dM", supdup->td_args[0]); + break; + + case TDICP: + /* + Insert blank character positions at the cursor; followed + by a count. The cursor is unmoved. The character the + cursor is on and all characters to the right on the + current line move to the right; characters moved off the + end of the line are lost. + */ + strbuf_catf(outbuf, "\033[%d@", supdup->td_args[0]); + break; + + case TDDCP: + /* + Delete characters at the cursor; followed by a count. + The cursor is unmoved. The first character deleted is + the one the cursor is on. Newly-created characters at + the end of the line are blank. + */ + strbuf_catf(outbuf, "\033[%dP", supdup->td_args[0]); + break; + + case TDBOW: + case TDRST: + /* + Display black characters on white screen. + HIGHLY OPTIONAL. + */ + + // Since this is HIGHLY OPTIONAL, I'm not going + // to implement it yet. + break; + + /* + * Non-standard (whatever "standard" means here) SUPDUP + * commands. These are used (at the very least) by + * Genera's SUPDUP implementation. Cannot find any + * official documentation, behavior is based on UNIX + * SUPDUP implementation from MIT. + */ + case TDBS: + /* + * Backspace -- move cursor back one character (does not + * appear to wrap...) + */ + put_byte(outbuf, '\010'); + break; + + case TDLF: + /* + * Linefeed -- move cursor down one line (again, no wrapping) + */ + put_byte(outbuf, '\012'); + break; + + case TDCR: + /* + * Carriage return -- move cursor to start of current line. + */ + put_byte(outbuf, '\015'); + break; + } + + // Return to top level to pick up the next %TD code or + // printable character. + supdup->tdstate = TD_TOPLEVEL; +} + +static void term_out_supdup(Supdup *supdup, strbuf *outbuf, int c) +{ + if (supdup->tdstate == TD_TOPLEVEL) { + do_toplevel (supdup, outbuf, c); + } else if (supdup->tdstate == TD_ARGS) { + do_args (supdup, outbuf, c); + } + + // If all arguments for a %TD code are ready, we will execute the code now. + if (supdup->tdstate == TD_ARGSDONE) { + do_argsdone (supdup, outbuf, c); + } +} + +static void do_supdup_read(Supdup *supdup, const char *buf, size_t len) +{ + strbuf *outbuf = strbuf_new(); + + while (len--) { + int c = (unsigned char)*buf++; + switch (supdup->state) { + case CONNECTING: + // "Following the transmission of the terminal options by + // the user, the server should respond with an ASCII + // greeting message, terminated with a %TDNOP code..." + if (TDNOP == c) { + // Greeting done, switch to the CONNECTED state. + supdup->state = CONNECTED; + supdup->tdstate = TD_TOPLEVEL; + } else { + // Forward the greeting message (which is straight + // ASCII, no controls) on so it gets displayed TODO: + // filter out only printable chars? + put_byte(outbuf, c); + } + break; + + case CONNECTED: + // "All transmissions from the server after the %TDNOP + // [see above] are either printing characters or virtual + // terminal display codes." Forward these on to the + // frontend which will decide what to do with them. + term_out_supdup(supdup, outbuf, c); + /* + * Hack to make Symbolics Genera SUPDUP happy: Wait until + * after we're connected (finished the initial handshake + * and have gotten additional data) before sending the + * location string. For some reason doing so earlier + * causes the Symbolics SUPDUP to end up in an odd state. + */ + if (!supdup->sent_location) { + supdup_send_location(supdup); + supdup->sent_location = true; + } + break; + } + + if (outbuf->len >= 4096) { + c_write(supdup, outbuf->u, outbuf->len); + outbuf->len = 0; + } + } + + if (outbuf->len) + c_write(supdup, outbuf->u, outbuf->len); + strbuf_free(outbuf); +} + +static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + backend_socket_log(supdup->seat, supdup->logctx, type, addr, port, + error_msg, error_code, + supdup->conf, supdup->state != CONNECTING); +} + +static void supdup_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + + if (supdup->s) { + sk_close(supdup->s); + supdup->s = NULL; + if (error_msg) + supdup->closed_on_socket_error = true; + seat_notify_remote_exit(supdup->seat); + } + if (error_msg) { + logevent(supdup->logctx, error_msg); + seat_connection_fatal(supdup->seat, "%s", error_msg); + } + /* Otherwise, the remote side closed the connection normally. */ +} + +static void supdup_receive(Plug *plug, int urgent, const char *data, size_t len) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + do_supdup_read(supdup, data, len); +} + +static void supdup_sent(Plug *plug, size_t bufsize) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + supdup->bufsize = bufsize; +} + +static void supdup_send_36bits(Supdup *supdup, unsigned long long thirtysix) +{ + // + // From RFC734: + // "Each word is sent through the 8-bit connection as six + // 6-bit bytes, most-significant first." + // + // Split the 36-bit word into 6 6-bit "bytes", packed into + // 8-bit bytes and send, most-significant byte first. + // + for (int i = 5; i >= 0; i--) { + char sixBits = (thirtysix >> (i * 6)) & 077; + sk_write(supdup->s, &sixBits, 1); + } +} + +static void supdup_send_config(Supdup *supdup) +{ + supdup_send_36bits(supdup, WORDS); // negative length + supdup_send_36bits(supdup, TCTYP); // terminal type + supdup_send_36bits(supdup, supdup->ttyopt); // options + supdup_send_36bits(supdup, supdup->tcmxv); // height + supdup_send_36bits(supdup, supdup->tcmxh); // width + supdup_send_36bits(supdup, TTYROL); // scroll amount +} + +/* +* Called to set up the Supdup connection. +* +* Returns an error message, or NULL on success. +* +* Also places the canonical host name into `realhost'. It must be +* freed by the caller. +*/ +static const char *supdup_init(const BackendVtable *x, Seat *seat, + Backend **backend_handle, + LogContext *logctx, Conf *conf, + const char *host, int port, char **realhost, + bool nodelay, bool keepalive) +{ + static const PlugVtable fn_table = { + supdup_log, + supdup_closing, + supdup_receive, + supdup_sent + }; + SockAddr *addr; + const char *err; + Supdup *supdup; + char *loghost; + int addressfamily; + const char *utf8 = "\033%G"; + + supdup = snew(struct supdup_tag); + supdup->plug.vt = &fn_table; + supdup->backend.vt = &supdup_backend; + supdup->logctx = logctx; + supdup->conf = conf_copy(conf); + supdup->s = NULL; + supdup->closed_on_socket_error = false; + supdup->seat = seat; + supdup->term_width = conf_get_int(supdup->conf, CONF_width); + supdup->term_height = conf_get_int(supdup->conf, CONF_height); + supdup->pinger = NULL; + supdup->sent_location = false; + *backend_handle = &supdup->backend; + + switch (conf_get_int(supdup->conf, CONF_supdup_ascii_set)) { + case SUPDUP_CHARSET_ASCII: + supdup->print = print_ascii; + break; + case SUPDUP_CHARSET_ITS: + supdup->print = print_its; + break; + case SUPDUP_CHARSET_WAITS: + supdup->print = print_waits; + break; + } + + /* + * Try to find host. + */ + { + char *buf; + addressfamily = conf_get_int(supdup->conf, CONF_addressfamily); + buf = dupprintf("Looking up host \"%s\"%s", host, + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""))); + logevent(supdup->logctx, buf); + sfree(buf); + } + addr = name_lookup(host, port, realhost, supdup->conf, addressfamily, NULL, ""); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + if (port < 0) + port = 0137; /* default supdup port */ + + /* + * Open socket. + */ + supdup->s = new_connection(addr, *realhost, port, false, true, + nodelay, keepalive, &supdup->plug, supdup->conf); + if ((err = sk_socket_error(supdup->s)) != NULL) + return err; + + supdup->pinger = pinger_new(supdup->conf, &supdup->backend); + + /* + * We can send special commands from the start. + */ + seat_update_specials_menu(supdup->seat); + + /* + * loghost overrides realhost, if specified. + */ + loghost = conf_get_str(supdup->conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + /* + * Set up TTYOPTS based on config + */ + int ascii_set = conf_get_int(supdup->conf, CONF_supdup_ascii_set); + int more_processing = conf_get_bool(supdup->conf, CONF_supdup_more); + int scrolling = conf_get_bool(supdup->conf, CONF_supdup_scroll); + supdup->ttyopt = + TOERS | + TOMVB | + (ascii_set == SUPDUP_CHARSET_ASCII ? 0 : TOSAI | TOSA1) | + TOMVU | + TOLWR | + TOLID | + TOCID | + TPCBS | + (scrolling ? TOROL : 0) | + (more_processing ? TOMOR : 0) | + TPORS; + + supdup->tcmxh = supdup->term_width - 1; // -1 "..one column is used to indicate line continuation." + supdup->tcmxv = supdup->term_height; + + /* + * Send our configuration words to the server + */ + supdup_send_config(supdup); + + /* + * We next expect a connection message followed by %TDNOP from the server + */ + supdup->state = CONNECTING; + seat_set_trust_status(supdup->seat, false); + + /* Make sure the terminal is in UTF-8 mode. */ + c_write(supdup, (unsigned char *)utf8, strlen(utf8)); + + return NULL; +} + + +static void supdup_free(Backend *be) +{ + Supdup *supdup = container_of(be, Supdup, backend); + + if (supdup->s) + sk_close(supdup->s); + if (supdup->pinger) + pinger_free(supdup->pinger); + conf_free(supdup->conf); + sfree(supdup); +} + +/* +* Reconfigure the Supdup backend. +*/ +static void supdup_reconfig(Backend *be, Conf *conf) +{ + /* Nothing to do; SUPDUP cannot be reconfigured while running. */ +} + +/* +* Called to send data down the Supdup connection. +*/ +static size_t supdup_send(Backend *be, const char *buf, size_t len) +{ + Supdup *supdup = container_of(be, Supdup, backend); + char c; + int i; + + if (supdup->s == NULL) + return 0; + + for (i = 0; i < len; i++) { + if (buf[i] == 034) + supdup->bufsize = sk_write(supdup->s, "\034\034", 2); + else { + c = buf[i] & 0177; + supdup->bufsize = sk_write(supdup->s, &c, 1); + } + } + return supdup->bufsize; +} + +/* +* Called to query the current socket sendability status. +*/ +static size_t supdup_sendbuffer(Backend *be) +{ + Supdup *supdup = container_of(be, Supdup, backend); + return supdup->bufsize; +} + +/* +* Called to set the size of the window from Supdup's POV. +*/ +static void supdup_size(Backend *be, int width, int height) +{ + Supdup *supdup = container_of(be, Supdup, backend); + + supdup->term_width = width; + supdup->term_height = height; + + // + // SUPDUP does not support resizing the terminal after connection + // establishment. + // +} + +/* +* Send Telnet special codes. +*/ +static void supdup_special(Backend *be, SessionSpecialCode code, int arg) +{ +} + +static const SessionSpecial *supdup_get_specials(Backend *be) +{ + return NULL; +} + +static bool supdup_connected(Backend *be) +{ + Supdup *supdup = container_of(be, Supdup, backend); + return supdup->s != NULL; +} + +static bool supdup_sendok(Backend *be) +{ + return 1; +} + +static void supdup_unthrottle(Backend *be, size_t backlog) +{ + Supdup *supdup = container_of(be, Supdup, backend); + sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG); +} + +static bool supdup_ldisc(Backend *be, int option) +{ + /* No support for echoing or local editing. */ + return false; +} + +static void supdup_provide_ldisc(Backend *be, Ldisc *ldisc) +{ +} + +static int supdup_exitcode(Backend *be) +{ + Supdup *supdup = container_of(be, Supdup, backend); + if (supdup->s != NULL) + return -1; /* still connected */ + else if (supdup->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* Supdup doesn't transmit exit codes back to the client */ + return 0; +} + +/* +* cfg_info for Dupdup does nothing at all. +*/ +static int supdup_cfg_info(Backend *be) +{ + return 0; +} + +const struct BackendVtable supdup_backend = { + supdup_init, + supdup_free, + supdup_reconfig, + supdup_send, + supdup_sendbuffer, + supdup_size, + supdup_special, + supdup_get_specials, + supdup_connected, + supdup_exitcode, + supdup_sendok, + supdup_ldisc, + supdup_provide_ldisc, + supdup_unthrottle, + supdup_cfg_info, + NULL /* test_for_upstream */, + "supdup", + "SUPDUP", + PROT_SUPDUP, + 0137, + BACKEND_RESIZE_FORBIDDEN | BACKEND_NEEDS_TERMINAL +}; diff --git a/windows/putty.rc b/windows/putty.rc index fd585178..6d15fdbf 100644 --- a/windows/putty.rc +++ b/windows/putty.rc @@ -1,7 +1,7 @@ #include "rcstuff.h" #define APPNAME "PuTTY" -#define APPDESC "SSH, Telnet and Rlogin client" +#define APPDESC "SSH, Telnet, Rlogin and SUPDUDP client" #include "winhelp.rc2" #include "win_res.rc2" diff --git a/windows/winhelp.h b/windows/winhelp.h index 823a2429..e5bf6c14 100644 --- a/windows/winhelp.h +++ b/windows/winhelp.h @@ -52,6 +52,10 @@ #define WINHELP_CTX_terminal_localecho "config-localecho" #define WINHELP_CTX_terminal_localedit "config-localedit" #define WINHELP_CTX_terminal_printing "config-printing" +#define WINHELP_CTX_supdup_location "supdup-location" +#define WINHELP_CTX_supdup_ascii "supdup-ascii" +#define WINHELP_CTX_supdup_more "supdup-more" +#define WINHELP_CTX_supdup_scroll "supdup-scroll" #define WINHELP_CTX_bell_style "config-bellstyle" #define WINHELP_CTX_bell_taskbar "config-belltaskbar" #define WINHELP_CTX_bell_overload "config-bellovl"