diff options
author | Dimitrij <kvarkas@gmail.com> | 2022-10-31 00:45:23 +0300 |
---|---|---|
committer | Dimitrij <kvarkas@gmail.com> | 2022-10-31 00:45:23 +0300 |
commit | 302fb2e8ddea1c993552c9a30c02f41d01ca54a9 (patch) | |
tree | d6cf1b32664296ef2cecda33caeafbe39e6695c1 /otherbackends | |
parent | 59105d9b26363e47f00676bd365b2ac8d4cb536a (diff) | |
parent | 4ff82ab29a22936b78510c68f544a99e677efed3 (diff) |
Diffstat (limited to 'otherbackends')
-rw-r--r-- | otherbackends/CMakeLists.txt | 6 | ||||
-rw-r--r-- | otherbackends/raw.c | 377 | ||||
-rw-r--r-- | otherbackends/rlogin.c | 496 | ||||
-rw-r--r-- | otherbackends/supdup.c | 978 | ||||
-rw-r--r-- | otherbackends/telnet.c | 1123 | ||||
-rw-r--r-- | otherbackends/testback.c | 200 |
6 files changed, 3180 insertions, 0 deletions
diff --git a/otherbackends/CMakeLists.txt b/otherbackends/CMakeLists.txt new file mode 100644 index 00000000..099d1253 --- /dev/null +++ b/otherbackends/CMakeLists.txt @@ -0,0 +1,6 @@ +add_sources_from_current_dir(otherbackends + raw.c + rlogin.c + supdup.c + telnet.c + testback.c) diff --git a/otherbackends/raw.c b/otherbackends/raw.c new file mode 100644 index 00000000..c9a32e05 --- /dev/null +++ b/otherbackends/raw.c @@ -0,0 +1,377 @@ +/* + * "Raw" backend. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#include "putty.h" + +#define RAW_MAX_BACKLOG 4096 + +typedef struct Raw Raw; +struct Raw { + Socket *s; + bool closed_on_socket_error; + size_t bufsize; + Seat *seat; + LogContext *logctx; + Ldisc *ldisc; + bool sent_console_eof, sent_socket_eof, socket_connected; + char *description; + + Conf *conf; + + Plug plug; + Backend backend; + Interactor interactor; +}; + +static void raw_size(Backend *be, int width, int height); + +static void c_write(Raw *raw, const void *buf, size_t len) +{ + size_t backlog = seat_stdout(raw->seat, buf, len); + sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); +} + +static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Raw *raw = container_of(plug, Raw, plug); + backend_socket_log(raw->seat, raw->logctx, type, addr, port, error_msg, + error_code, raw->conf, raw->socket_connected); + if (type == PLUGLOG_CONNECT_SUCCESS) { + raw->socket_connected = true; + if (raw->ldisc) + ldisc_check_sendok(raw->ldisc); + } +} + +static void raw_check_close(Raw *raw) +{ + /* + * Called after we send EOF on either the socket or the console. + * Its job is to wind up the session once we have sent EOF on both. + */ + if (raw->sent_console_eof && raw->sent_socket_eof) { + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + seat_notify_remote_exit(raw->seat); + seat_notify_remote_disconnect(raw->seat); + } + } +} + +static void raw_closing(Plug *plug, PlugCloseType type, const char *error_msg) +{ + Raw *raw = container_of(plug, Raw, plug); + + if (type != PLUGCLOSE_NORMAL) { + /* A socket error has occurred. */ + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + raw->closed_on_socket_error = true; + seat_notify_remote_exit(raw->seat); + seat_notify_remote_disconnect(raw->seat); + } + logevent(raw->logctx, error_msg); + if (type != PLUGCLOSE_USER_ABORT) + seat_connection_fatal(raw->seat, "%s", error_msg); + } else { + /* Otherwise, the remote side closed the connection normally. */ + if (!raw->sent_console_eof && seat_eof(raw->seat)) { + /* + * The front end wants us to close the outgoing side of the + * connection as soon as we see EOF from the far end. + */ + if (!raw->sent_socket_eof) { + if (raw->s) + sk_write_eof(raw->s); + raw->sent_socket_eof= true; + } + } + raw->sent_console_eof = true; + raw_check_close(raw); + } +} + +static void raw_receive(Plug *plug, int urgent, const char *data, size_t len) +{ + Raw *raw = container_of(plug, Raw, plug); + c_write(raw, data, len); +} + +static void raw_sent(Plug *plug, size_t bufsize) +{ + Raw *raw = container_of(plug, Raw, plug); + raw->bufsize = bufsize; + seat_sent(raw->seat, raw->bufsize); +} + +static const PlugVtable Raw_plugvt = { + .log = raw_log, + .closing = raw_closing, + .receive = raw_receive, + .sent = raw_sent, +}; + +static char *raw_description(Interactor *itr) +{ + Raw *raw = container_of(itr, Raw, interactor); + return dupstr(raw->description); +} + +static LogPolicy *raw_logpolicy(Interactor *itr) +{ + Raw *raw = container_of(itr, Raw, interactor); + return log_get_policy(raw->logctx); +} + +static Seat *raw_get_seat(Interactor *itr) +{ + Raw *raw = container_of(itr, Raw, interactor); + return raw->seat; +} + +static void raw_set_seat(Interactor *itr, Seat *seat) +{ + Raw *raw = container_of(itr, Raw, interactor); + raw->seat = seat; +} + +static const InteractorVtable Raw_interactorvt = { + .description = raw_description, + .logpolicy = raw_logpolicy, + .get_seat = raw_get_seat, + .set_seat = raw_set_seat, +}; + +/* + * Called to set up the raw 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 char *raw_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + Raw *raw; + int addressfamily; + char *loghost; + + raw = snew(Raw); + memset(raw, 0, sizeof(Raw)); + raw->plug.vt = &Raw_plugvt; + raw->backend.vt = vt; + raw->interactor.vt = &Raw_interactorvt; + raw->backend.interactor = &raw->interactor; + raw->s = NULL; + raw->closed_on_socket_error = false; + *backend_handle = &raw->backend; + raw->sent_console_eof = raw->sent_socket_eof = false; + raw->bufsize = 0; + raw->socket_connected = false; + raw->conf = conf_copy(conf); + raw->description = default_description(vt, host, port); + + raw->seat = seat; + raw->logctx = logctx; + + addressfamily = conf_get_int(conf, CONF_addressfamily); + /* + * Try to find host. + */ + addr = name_lookup(host, port, realhost, conf, addressfamily, + raw->logctx, "main connection"); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return dupstr(err); + } + + if (port < 0) + port = 23; /* default telnet port */ + + /* + * Open socket. + */ + raw->s = new_connection(addr, *realhost, port, false, true, nodelay, + keepalive, &raw->plug, conf, &raw->interactor); + if ((err = sk_socket_error(raw->s)) != NULL) + return dupstr(err); + + /* No local authentication phase in this protocol */ + seat_set_trust_status(raw->seat, false); + + loghost = conf_get_str(conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + return NULL; +} + +static void raw_free(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + + if (is_tempseat(raw->seat)) + tempseat_free(raw->seat); + if (raw->s) + sk_close(raw->s); + conf_free(raw->conf); + sfree(raw->description); + sfree(raw); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void raw_reconfig(Backend *be, Conf *conf) +{ +} + +/* + * Called to send data down the raw connection. + */ +static void raw_send(Backend *be, const char *buf, size_t len) +{ + Raw *raw = container_of(be, Raw, backend); + + if (raw->s == NULL) + return; + + raw->bufsize = sk_write(raw->s, buf, len); +} + +/* + * Called to query the current socket sendability status. + */ +static size_t raw_sendbuffer(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + return raw->bufsize; +} + +/* + * Called to set the size of the window + */ +static void raw_size(Backend *be, int width, int height) +{ + /* Do nothing! */ + return; +} + +/* + * Send raw special codes. We only handle outgoing EOF here. + */ +static void raw_special(Backend *be, SessionSpecialCode code, int arg) +{ + Raw *raw = container_of(be, Raw, backend); + if (code == SS_EOF && raw->s) { + sk_write_eof(raw->s); + raw->sent_socket_eof= true; + raw_check_close(raw); + } + + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const SessionSpecial *raw_get_specials(Backend *be) +{ + return NULL; +} + +static bool raw_connected(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + return raw->s != NULL; +} + +static bool raw_sendok(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + return raw->socket_connected; +} + +static void raw_unthrottle(Backend *be, size_t backlog) +{ + Raw *raw = container_of(be, Raw, backend); + sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); +} + +static bool raw_ldisc(Backend *be, int option) +{ + if (option == LD_EDIT || option == LD_ECHO) + return true; + return false; +} + +static void raw_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + Raw *raw = container_of(be, Raw, backend); + raw->ldisc = ldisc; +} + +static int raw_exitcode(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + if (raw->s != NULL) + return -1; /* still connected */ + else if (raw->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* Exit codes are a meaningless concept in the Raw protocol */ + return 0; +} + +/* + * cfg_info for Raw does nothing at all. + */ +static int raw_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable raw_backend = { + .init = raw_init, + .free = raw_free, + .reconfig = raw_reconfig, + .send = raw_send, + .sendbuffer = raw_sendbuffer, + .size = raw_size, + .special = raw_special, + .get_specials = raw_get_specials, + .connected = raw_connected, + .exitcode = raw_exitcode, + .sendok = raw_sendok, + .ldisc_option_state = raw_ldisc, + .provide_ldisc = raw_provide_ldisc, + .unthrottle = raw_unthrottle, + .cfg_info = raw_cfg_info, + .id = "raw", + .displayname_tc = "Raw", + .displayname_lc = "raw", + .protocol = PROT_RAW, + .default_port = 0, +}; diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c new file mode 100644 index 00000000..37087257 --- /dev/null +++ b/otherbackends/rlogin.c @@ -0,0 +1,496 @@ +/* + * Rlogin backend. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <ctype.h> + +#include "putty.h" + +#define RLOGIN_MAX_BACKLOG 4096 + +typedef struct Rlogin Rlogin; +struct Rlogin { + Socket *s; + bool closed_on_socket_error; + int bufsize; + bool socket_connected; + bool firstbyte; + bool cansize; + int term_width, term_height; + Seat *seat; + LogContext *logctx; + Ldisc *ldisc; + char *description; + + Conf *conf; + + /* In case we need to read a username from the terminal before starting */ + prompts_t *prompt; + + Plug plug; + Backend backend; + Interactor interactor; +}; + +static void rlogin_startup(Rlogin *rlogin, SeatPromptResult spr, + const char *ruser); +static void rlogin_try_username_prompt(void *ctx); + +static void c_write(Rlogin *rlogin, const void *buf, size_t len) +{ + size_t backlog = seat_stdout(rlogin->seat, buf, len); + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, + error_msg, error_code, + rlogin->conf, rlogin->socket_connected); + if (type == PLUGLOG_CONNECT_SUCCESS) { + rlogin->socket_connected = true; + + char *ruser = get_remote_username(rlogin->conf); + if (ruser) { + /* + * If we already know the remote username, call + * rlogin_startup, which will send the initial protocol + * greeting including local username, remote username, + * terminal type and terminal speed. + */ + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, SPR_OK, ruser); + sfree(ruser); + } else { + /* + * Otherwise, set up a prompts_t asking for the local + * username. If it completes synchronously, call + * rlogin_startup as above; otherwise, wait until it does. + */ + rlogin->prompt = new_prompts(); + rlogin->prompt->to_server = true; + rlogin->prompt->from_server = false; + rlogin->prompt->name = dupstr("Rlogin login name"); + rlogin->prompt->callback = rlogin_try_username_prompt; + rlogin->prompt->callback_ctx = rlogin; + add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); + rlogin_try_username_prompt(rlogin); + } + } +} + +static void rlogin_closing(Plug *plug, PlugCloseType type, + const char *error_msg) +{ + Rlogin *rlogin = container_of(plug, Rlogin, 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 (rlogin->s) { + sk_close(rlogin->s); + rlogin->s = NULL; + if (error_msg) + rlogin->closed_on_socket_error = true; + seat_notify_remote_exit(rlogin->seat); + seat_notify_remote_disconnect(rlogin->seat); + } + if (type != PLUGCLOSE_NORMAL) { + /* A socket error has occurred. */ + logevent(rlogin->logctx, error_msg); + if (type != PLUGCLOSE_USER_ABORT) + seat_connection_fatal(rlogin->seat, "%s", error_msg); + } + /* Otherwise, the remote side closed the connection normally. */ +} + +static void rlogin_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + if (len == 0) + return; + if (urgent == 2) { + char c; + + c = *data++; + len--; + if (c == '\x80') { + rlogin->cansize = true; + backend_size(&rlogin->backend, + rlogin->term_width, rlogin->term_height); + } + /* + * We should flush everything (aka Telnet SYNCH) if we see + * 0x02, and we should turn off and on _local_ flow control + * on 0x10 and 0x20 respectively. I'm not convinced it's + * worth it... + */ + } else { + /* + * Main rlogin protocol. This is really simple: the first + * byte is expected to be NULL and is ignored, and the rest + * is printed. + */ + if (rlogin->firstbyte) { + if (data[0] == '\0') { + data++; + len--; + } + rlogin->firstbyte = false; + } + if (len > 0) + c_write(rlogin, data, len); + } +} + +static void rlogin_sent(Plug *plug, size_t bufsize) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + rlogin->bufsize = bufsize; + seat_sent(rlogin->seat, rlogin->bufsize); +} + +static void rlogin_startup(Rlogin *rlogin, SeatPromptResult spr, + const char *ruser) +{ + char z = 0; + char *p; + + if (spr.kind == SPRK_USER_ABORT) { + /* User aborted at the username prompt. */ + sk_close(rlogin->s); + rlogin->s = NULL; + seat_notify_remote_exit(rlogin->seat); + } else if (spr.kind == SPRK_SW_ABORT) { + /* Something else went wrong at the username prompt, so we + * have to show some kind of error. */ + sk_close(rlogin->s); + rlogin->s = NULL; + char *err = spr_get_error_message(spr); + seat_connection_fatal(rlogin->seat, "%s", err); + sfree(err); + } else { + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_localusername); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, ruser, strlen(ruser)); + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_termtype); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, "/", 1); + p = conf_get_str(rlogin->conf, CONF_termspeed); + sk_write(rlogin->s, p, strspn(p, "0123456789")); + rlogin->bufsize = sk_write(rlogin->s, &z, 1); + } + + rlogin->prompt = NULL; + if (rlogin->ldisc) + ldisc_check_sendok(rlogin->ldisc); +} + +static const PlugVtable Rlogin_plugvt = { + .log = rlogin_log, + .closing = rlogin_closing, + .receive = rlogin_receive, + .sent = rlogin_sent, +}; + +static char *rlogin_description(Interactor *itr) +{ + Rlogin *rlogin = container_of(itr, Rlogin, interactor); + return dupstr(rlogin->description); +} + +static LogPolicy *rlogin_logpolicy(Interactor *itr) +{ + Rlogin *rlogin = container_of(itr, Rlogin, interactor); + return log_get_policy(rlogin->logctx); +} + +static Seat *rlogin_get_seat(Interactor *itr) +{ + Rlogin *rlogin = container_of(itr, Rlogin, interactor); + return rlogin->seat; +} + +static void rlogin_set_seat(Interactor *itr, Seat *seat) +{ + Rlogin *rlogin = container_of(itr, Rlogin, interactor); + rlogin->seat = seat; +} + +static const InteractorVtable Rlogin_interactorvt = { + .description = rlogin_description, + .logpolicy = rlogin_logpolicy, + .get_seat = rlogin_get_seat, + .set_seat = rlogin_set_seat, +}; + +/* + * Called to set up the rlogin 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 char *rlogin_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + Rlogin *rlogin; + int addressfamily; + char *loghost; + + rlogin = snew(Rlogin); + memset(rlogin, 0, sizeof(Rlogin)); + rlogin->plug.vt = &Rlogin_plugvt; + rlogin->backend.vt = vt; + rlogin->interactor.vt = &Rlogin_interactorvt; + rlogin->backend.interactor = &rlogin->interactor; + rlogin->s = NULL; + rlogin->closed_on_socket_error = false; + rlogin->seat = seat; + rlogin->logctx = logctx; + rlogin->term_width = conf_get_int(conf, CONF_width); + rlogin->term_height = conf_get_int(conf, CONF_height); + rlogin->socket_connected = false; + rlogin->firstbyte = true; + rlogin->cansize = false; + rlogin->prompt = NULL; + rlogin->conf = conf_copy(conf); + rlogin->description = default_description(vt, host, port); + *backend_handle = &rlogin->backend; + + addressfamily = conf_get_int(conf, CONF_addressfamily); + /* + * Try to find host. + */ + addr = name_lookup(host, port, realhost, conf, addressfamily, + rlogin->logctx, "rlogin connection"); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return dupstr(err); + } + + if (port < 0) + port = 513; /* default rlogin port */ + + /* + * Open socket. + */ + rlogin->s = new_connection(addr, *realhost, port, true, false, + nodelay, keepalive, &rlogin->plug, conf, + &rlogin->interactor); + if ((err = sk_socket_error(rlogin->s)) != NULL) + return dupstr(err); + + loghost = conf_get_str(conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + return NULL; +} + +static void rlogin_free(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + + if (is_tempseat(rlogin->seat)) + tempseat_free(rlogin->seat); + if (rlogin->prompt) + free_prompts(rlogin->prompt); + if (rlogin->s) + sk_close(rlogin->s); + conf_free(rlogin->conf); + sfree(rlogin->description); + sfree(rlogin); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void rlogin_reconfig(Backend *be, Conf *conf) +{ +} + +static void rlogin_try_username_prompt(void *ctx) +{ + Rlogin *rlogin = (Rlogin *)ctx; + + SeatPromptResult spr = seat_get_userpass_input( + interactor_announce(&rlogin->interactor), rlogin->prompt); + if (spr.kind == SPRK_INCOMPLETE) + return; + + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + + /* Send the rlogin setup protocol data, and then we're ready to + * start receiving normal input to send down the wire, which + * rlogin_startup will signal to rlogin_sendok by nulling out + * rlogin->prompt. */ + rlogin_startup( + rlogin, spr, prompt_get_result_ref(rlogin->prompt->prompts[0])); +} + +/* + * Called to send data down the rlogin connection. + */ +static void rlogin_send(Backend *be, const char *buf, size_t len) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + + if (rlogin->s == NULL) + return; + + rlogin->bufsize = sk_write(rlogin->s, buf, len); +} + +/* + * Called to query the current socket sendability status. + */ +static size_t rlogin_sendbuffer(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + return rlogin->bufsize; +} + +/* + * Called to set the size of the window + */ +static void rlogin_size(Backend *be, int width, int height) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + char b[12] = { '\xFF', '\xFF', 0x73, 0x73, 0, 0, 0, 0, 0, 0, 0, 0 }; + + rlogin->term_width = width; + rlogin->term_height = height; + + if (rlogin->s == NULL || !rlogin->cansize) + return; + + b[6] = rlogin->term_width >> 8; + b[7] = rlogin->term_width & 0xFF; + b[4] = rlogin->term_height >> 8; + b[5] = rlogin->term_height & 0xFF; + rlogin->bufsize = sk_write(rlogin->s, b, 12); + return; +} + +/* + * Send rlogin special codes. + */ +static void rlogin_special(Backend *be, SessionSpecialCode code, int arg) +{ + /* Do nothing! */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const SessionSpecial *rlogin_get_specials(Backend *be) +{ + return NULL; +} + +static bool rlogin_connected(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + return rlogin->s != NULL; +} + +static bool rlogin_sendok(Backend *be) +{ + /* + * We only want to receive input data if the socket is connected + * and we're not still at the username prompt stage. + */ + Rlogin *rlogin = container_of(be, Rlogin, backend); + return rlogin->socket_connected && !rlogin->prompt; +} + +static void rlogin_unthrottle(Backend *be, size_t backlog) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static bool rlogin_ldisc(Backend *be, int option) +{ + /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ + return false; +} + +static void rlogin_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + rlogin->ldisc = ldisc; +} + +static int rlogin_exitcode(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + if (rlogin->s != NULL) + return -1; /* still connected */ + else if (rlogin->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* If we ever implement RSH, we'll probably need to do this properly */ + return 0; +} + +/* + * cfg_info for rlogin does nothing at all. + */ +static int rlogin_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable rlogin_backend = { + .init = rlogin_init, + .free = rlogin_free, + .reconfig = rlogin_reconfig, + .send = rlogin_send, + .sendbuffer = rlogin_sendbuffer, + .size = rlogin_size, + .special = rlogin_special, + .get_specials = rlogin_get_specials, + .connected = rlogin_connected, + .exitcode = rlogin_exitcode, + .sendok = rlogin_sendok, + .ldisc_option_state = rlogin_ldisc, + .provide_ldisc = rlogin_provide_ldisc, + .unthrottle = rlogin_unthrottle, + .cfg_info = rlogin_cfg_info, + .id = "rlogin", + .displayname_tc = "Rlogin", + .displayname_lc = "Rlogin", /* proper name, so capitalise it anyway */ + .protocol = PROT_RLOGIN, + .default_port = 513, +}; diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c new file mode 100644 index 00000000..9aaf1d4f --- /dev/null +++ b/otherbackends/supdup.c @@ -0,0 +1,978 @@ +/* + * Supdup backend + */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#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 socket_connected; + bool closed_on_socket_error; + + Seat *seat; + LogContext *logctx; + Ldisc *ldisc; + int term_width, term_height; + char *description; + + 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; + Interactor interactor; +}; + +#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. + put_fmt(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. + */ + put_fmt(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. + */ + put_fmt(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. + */ + put_fmt(outbuf, "\033[K"); + break; + + case TDDLF: + /* + Clear the character position the cursor is on. The + cursor does not move. + */ + put_fmt(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. + */ + put_fmt(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. + */ + + put_fmt(outbuf, "\033[C"); + break; + + case TDCLR: + /* + Erase the screen. Home the cursor to the top left hand + corner of the screen. + */ + put_fmt(outbuf, "\033[2J\033[H"); + break; + + case TDBEL: + /* + Generate an audio tone, bell, whatever. + */ + + put_fmt(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. + */ + put_fmt(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. + */ + put_fmt(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. + */ + put_fmt(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. + */ + put_fmt(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->socket_connected); + if (type == PLUGLOG_CONNECT_SUCCESS) { + supdup->socket_connected = true; + if (supdup->ldisc) + ldisc_check_sendok(supdup->ldisc); + } +} + +static void supdup_closing(Plug *plug, PlugCloseType type, + const char *error_msg) +{ + 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); + seat_notify_remote_disconnect(supdup->seat); + } + if (type != PLUGCLOSE_NORMAL) { + logevent(supdup->logctx, error_msg); + if (type != PLUGCLOSE_USER_ABORT) + 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; + seat_sent(supdup->seat, supdup->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 +} + +static char *supdup_description(Interactor *itr) +{ + Supdup *supdup = container_of(itr, Supdup, interactor); + return dupstr(supdup->description); +} + +static LogPolicy *supdup_logpolicy(Interactor *itr) +{ + Supdup *supdup = container_of(itr, Supdup, interactor); + return log_get_policy(supdup->logctx); +} + +static Seat *supdup_get_seat(Interactor *itr) +{ + Supdup *supdup = container_of(itr, Supdup, interactor); + return supdup->seat; +} + +static void supdup_set_seat(Interactor *itr, Seat *seat) +{ + Supdup *supdup = container_of(itr, Supdup, interactor); + supdup->seat = seat; +} + +static const InteractorVtable Supdup_interactorvt = { + .description = supdup_description, + .logpolicy = supdup_logpolicy, + .get_seat = supdup_get_seat, + .set_seat = supdup_set_seat, +}; + +/* + * 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 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 = { + .log = supdup_log, + .closing = supdup_closing, + .receive = supdup_receive, + .sent = supdup_sent, + }; + SockAddr *addr; + const char *err; + Supdup *supdup; + char *loghost; + int addressfamily; + const char *utf8 = "\033%G"; + + supdup = snew(struct supdup_tag); + memset(supdup, 0, sizeof(Supdup)); + supdup->plug.vt = &fn_table; + supdup->backend.vt = &supdup_backend; + supdup->interactor.vt = &Supdup_interactorvt; + supdup->backend.interactor = &supdup->interactor; + supdup->logctx = logctx; + supdup->conf = conf_copy(conf); + supdup->s = NULL; + supdup->socket_connected = false; + 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; + supdup->description = default_description(supdup->backend.vt, host, port); + *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 dupstr(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, + &supdup->interactor); + if ((err = sk_socket_error(supdup->s)) != NULL) + return dupstr(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 (is_tempseat(supdup->seat)) + tempseat_free(supdup->seat); + if (supdup->s) + sk_close(supdup->s); + if (supdup->pinger) + pinger_free(supdup->pinger); + conf_free(supdup->conf); + sfree(supdup->description); + 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 void 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; + + 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); + } + } +} + +/* + * 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) +{ + Supdup *supdup = container_of(be, Supdup, backend); + return supdup->socket_connected; +} + +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) +{ + Supdup *supdup = container_of(be, Supdup, backend); + supdup->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 Supdup does nothing at all. + */ +static int supdup_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable supdup_backend = { + .init = supdup_init, + .free = supdup_free, + .reconfig = supdup_reconfig, + .send = supdup_send, + .sendbuffer = supdup_sendbuffer, + .size = supdup_size, + .special = supdup_special, + .get_specials = supdup_get_specials, + .connected = supdup_connected, + .exitcode = supdup_exitcode, + .sendok = supdup_sendok, + .ldisc_option_state = supdup_ldisc, + .provide_ldisc = supdup_provide_ldisc, + .unthrottle = supdup_unthrottle, + .cfg_info = supdup_cfg_info, + .id = "supdup", + .displayname_tc = "SUPDUP", + .displayname_lc = "SUPDUP", /* proper name, so capitalise it anyway */ + .protocol = PROT_SUPDUP, + .default_port = 0137, + .flags = BACKEND_RESIZE_FORBIDDEN | BACKEND_NEEDS_TERMINAL, +}; diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c new file mode 100644 index 00000000..23b7fc9e --- /dev/null +++ b/otherbackends/telnet.c @@ -0,0 +1,1123 @@ +/* + * Telnet backend. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#include "putty.h" + +#define IAC 255 /* interpret as command: */ +#define DONT 254 /* you are not to use option */ +#define DO 253 /* please, you use option */ +#define WONT 252 /* I won't use option */ +#define WILL 251 /* I will use option */ +#define SB 250 /* interpret as subnegotiation */ +#define SE 240 /* end sub negotiation */ + +#define GA 249 /* you may reverse the line */ +#define EL 248 /* erase the current line */ +#define EC 247 /* erase the current character */ +#define AYT 246 /* are you there */ +#define AO 245 /* abort output--but let prog finish */ +#define IP 244 /* interrupt process--permanently */ +#define BREAK 243 /* break */ +#define DM 242 /* data mark--for connect. cleaning */ +#define NOP 241 /* nop */ +#define EOR 239 /* end of record (transparent mode) */ +#define ABORT 238 /* Abort process */ +#define SUSP 237 /* Suspend process */ +#define xEOF 236 /* End of file: EOF is already used... */ + +#define TELOPTS(X) \ + X(BINARY, 0) /* 8-bit data path */ \ + X(ECHO, 1) /* echo */ \ + X(RCP, 2) /* prepare to reconnect */ \ + X(SGA, 3) /* suppress go ahead */ \ + X(NAMS, 4) /* approximate message size */ \ + X(STATUS, 5) /* give status */ \ + X(TM, 6) /* timing mark */ \ + X(RCTE, 7) /* remote controlled transmission and echo */ \ + X(NAOL, 8) /* negotiate about output line width */ \ + X(NAOP, 9) /* negotiate about output page size */ \ + X(NAOCRD, 10) /* negotiate about CR disposition */ \ + X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \ + X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \ + X(NAOFFD, 13) /* negotiate about formfeed disposition */ \ + X(NAOVTS, 14) /* negotiate about vertical tab stops */ \ + X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \ + X(NAOLFD, 16) /* negotiate about output LF disposition */ \ + X(XASCII, 17) /* extended ascic character set */ \ + X(LOGOUT, 18) /* force logout */ \ + X(BM, 19) /* byte macro */ \ + X(DET, 20) /* data entry terminal */ \ + X(SUPDUP, 21) /* supdup protocol */ \ + X(SUPDUPOUTPUT, 22) /* supdup output */ \ + X(SNDLOC, 23) /* send location */ \ + X(TTYPE, 24) /* terminal type */ \ + X(EOR, 25) /* end or record */ \ + X(TUID, 26) /* TACACS user identification */ \ + X(OUTMRK, 27) /* output marking */ \ + X(TTYLOC, 28) /* terminal location number */ \ + X(3270REGIME, 29) /* 3270 regime */ \ + X(X3PAD, 30) /* X.3 PAD */ \ + X(NAWS, 31) /* window size */ \ + X(TSPEED, 32) /* terminal speed */ \ + X(LFLOW, 33) /* remote flow control */ \ + X(LINEMODE, 34) /* Linemode option */ \ + X(XDISPLOC, 35) /* X Display Location */ \ + X(OLD_ENVIRON, 36) /* Old - Environment variables */ \ + X(AUTHENTICATION, 37) /* Authenticate */ \ + X(ENCRYPT, 38) /* Encryption option */ \ + X(NEW_ENVIRON, 39) /* New - Environment variables */ \ + X(TN3270E, 40) /* TN3270 enhancements */ \ + X(XAUTH, 41) \ + X(CHARSET, 42) /* Character set */ \ + X(RSP, 43) /* Remote serial port */ \ + X(COM_PORT_OPTION, 44) /* Com port control */ \ + X(SLE, 45) /* Suppress local echo */ \ + X(STARTTLS, 46) /* Start TLS */ \ + X(KERMIT, 47) /* Automatic Kermit file transfer */ \ + X(SEND_URL, 48) \ + X(FORWARD_X, 49) \ + X(PRAGMA_LOGON, 138) \ + X(SSPI_LOGON, 139) \ + X(PRAGMA_HEARTBEAT, 140) \ + X(EXOPL, 255) /* extended-options-list */ + +#define telnet_enum(x,y) TELOPT_##x = y, +enum { TELOPTS(telnet_enum) dummy=0 }; +#undef telnet_enum + +#define TELQUAL_IS 0 /* option is... */ +#define TELQUAL_SEND 1 /* send option */ +#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */ +#define BSD_VAR 1 +#define BSD_VALUE 0 +#define RFC_VAR 0 +#define RFC_VALUE 1 + +#define CR 13 +#define LF 10 +#define NUL 0 + +#define iswritable(x) \ + ( (x) != IAC && \ + (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR)) + +static const char *telopt(int opt) +{ +#define telnet_str(x,y) case TELOPT_##x: return #x; + switch (opt) { + TELOPTS(telnet_str) + default: + return "<unknown>"; + } +#undef telnet_str +} + +struct Opt { + int send; /* what we initially send */ + int nsend; /* -ve send if requested to stop it */ + int ack, nak; /* +ve and -ve acknowledgements */ + int option; /* the option code */ + int index; /* index into telnet->opt_states[] */ + enum { + REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE + } initial_state; +}; + +enum { + OPTINDEX_NAWS, + OPTINDEX_TSPEED, + OPTINDEX_TTYPE, + OPTINDEX_OENV, + OPTINDEX_NENV, + OPTINDEX_ECHO, + OPTINDEX_WE_SGA, + OPTINDEX_THEY_SGA, + OPTINDEX_WE_BIN, + OPTINDEX_THEY_BIN, + NUM_OPTS +}; + +static const struct Opt o_naws = + { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED }; +static const struct Opt o_tspeed = + { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED }; +static const struct Opt o_ttype = + { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED }; +static const struct Opt o_oenv = + { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE }; +static const struct Opt o_nenv = + { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED }; +static const struct Opt o_echo = + { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED }; +static const struct Opt o_we_sga = + { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED }; +static const struct Opt o_they_sga = + { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED }; +static const struct Opt o_we_bin = + { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE }; +static const struct Opt o_they_bin = + { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE }; + +static const struct Opt *const opts[] = { + &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo, + &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL +}; + +typedef struct Telnet Telnet; +struct Telnet { + Socket *s; + bool socket_connected; + bool closed_on_socket_error; + + Seat *seat; + LogContext *logctx; + Ldisc *ldisc; + int term_width, term_height; + char *description; + + int opt_states[NUM_OPTS]; + + bool echoing, editing; + bool activated; + size_t bufsize; + bool in_synch; + int sb_opt; + strbuf *sb_buf; + + enum { + TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, + SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR + } state; + + Conf *conf; + + Pinger *pinger; + + Plug plug; + Backend backend; + Interactor interactor; +}; + +#define TELNET_MAX_BACKLOG 4096 + +#define SB_DELTA 1024 + +static void c_write(Telnet *telnet, const void *buf, size_t len) +{ + size_t backlog = seat_stdout(telnet->seat, buf, len); + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static void log_option(Telnet *telnet, const char *sender, int cmd, int option) +{ + /* + * The strange-looking "<?""?>" below is there to avoid a + * trigraph - a double question mark followed by > maps to a + * closing brace character! + */ + logeventf(telnet->logctx, "%s negotiation: %s %s", sender, + (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : + cmd == DO ? "DO" : cmd == DONT ? "DONT" : "<?""?>"), + telopt(option)); +} + +static void send_opt(Telnet *telnet, int cmd, int option) +{ + unsigned char b[3]; + + b[0] = IAC; + b[1] = cmd; + b[2] = option; + telnet->bufsize = sk_write(telnet->s, b, 3); + log_option(telnet, "client", cmd, option); +} + +static void deactivate_option(Telnet *telnet, const struct Opt *o) +{ + if (telnet->opt_states[o->index] == REQUESTED || + telnet->opt_states[o->index] == ACTIVE) + send_opt(telnet, o->nsend, o->option); + telnet->opt_states[o->index] = REALLY_INACTIVE; +} + +/* + * Generate side effects of enabling or disabling an option. + */ +static void option_side_effects( + Telnet *telnet, const struct Opt *o, bool enabled) +{ + if (o->option == TELOPT_ECHO && o->send == DO) + telnet->echoing = !enabled; + else if (o->option == TELOPT_SGA && o->send == DO) + telnet->editing = !enabled; + if (telnet->ldisc) /* cause ldisc to notice the change */ + ldisc_echoedit_update(telnet->ldisc); + + /* Ensure we get the minimum options */ + if (!telnet->activated) { + if (telnet->opt_states[o_echo.index] == INACTIVE) { + telnet->opt_states[o_echo.index] = REQUESTED; + send_opt(telnet, o_echo.send, o_echo.option); + } + if (telnet->opt_states[o_we_sga.index] == INACTIVE) { + telnet->opt_states[o_we_sga.index] = REQUESTED; + send_opt(telnet, o_we_sga.send, o_we_sga.option); + } + if (telnet->opt_states[o_they_sga.index] == INACTIVE) { + telnet->opt_states[o_they_sga.index] = REQUESTED; + send_opt(telnet, o_they_sga.send, o_they_sga.option); + } + telnet->activated = true; + } +} + +static void activate_option(Telnet *telnet, const struct Opt *o) +{ + if (o->send == WILL && o->option == TELOPT_NAWS) + backend_size(&telnet->backend, + telnet->term_width, telnet->term_height); + if (o->send == WILL && + (o->option == TELOPT_NEW_ENVIRON || + o->option == TELOPT_OLD_ENVIRON)) { + /* + * We may only have one kind of ENVIRON going at a time. + * This is a hack, but who cares. + */ + deactivate_option(telnet, o->option == + TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv); + } + option_side_effects(telnet, o, true); +} + +static void refused_option(Telnet *telnet, const struct Opt *o) +{ + if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON && + telnet->opt_states[o_oenv.index] == INACTIVE) { + send_opt(telnet, WILL, TELOPT_OLD_ENVIRON); + telnet->opt_states[o_oenv.index] = REQUESTED; + } + option_side_effects(telnet, o, false); +} + +static void proc_rec_opt(Telnet *telnet, int cmd, int option) +{ + const struct Opt *const *o; + + log_option(telnet, "server", cmd, option); + for (o = opts; *o; o++) { + if ((*o)->option == option && (*o)->ack == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = ACTIVE; + activate_option(telnet, *o); + break; + case ACTIVE: + break; + case INACTIVE: + telnet->opt_states[(*o)->index] = ACTIVE; + send_opt(telnet, (*o)->send, option); + activate_option(telnet, *o); + break; + case REALLY_INACTIVE: + send_opt(telnet, (*o)->nsend, option); + break; + } + return; + } else if ((*o)->option == option && (*o)->nak == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = INACTIVE; + refused_option(telnet, *o); + break; + case ACTIVE: + telnet->opt_states[(*o)->index] = INACTIVE; + send_opt(telnet, (*o)->nsend, option); + option_side_effects(telnet, *o, false); + break; + case INACTIVE: + case REALLY_INACTIVE: + break; + } + return; + } + } + /* + * If we reach here, the option was one we weren't prepared to + * cope with. If the request was positive (WILL or DO), we send + * a negative ack to indicate refusal. If the request was + * negative (WONT / DONT), we must do nothing. + */ + if (cmd == WILL || cmd == DO) + send_opt(telnet, (cmd == WILL ? DONT : WONT), option); +} + +static void process_subneg(Telnet *telnet) +{ + unsigned char *b, *p, *q; + int var, value, n, bsize; + char *e, *eval, *ekey, *user; + + switch (telnet->sb_opt) { + case TELOPT_TSPEED: + if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { + char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); + b = snewn(20 + strlen(termspeed), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TSPEED; + b[3] = TELQUAL_IS; + strcpy((char *)(b + 4), termspeed); + n = 4 + strlen(termspeed); + b[n] = IAC; + b[n + 1] = SE; + telnet->bufsize = sk_write(telnet->s, b, n + 2); + logevent(telnet->logctx, "server subnegotiation: SB TSPEED SEND"); + logeventf(telnet->logctx, + "client subnegotiation: SB TSPEED IS %s", termspeed); + sfree(b); + } else + logevent(telnet->logctx, + "server subnegotiation: SB TSPEED <something weird>"); + break; + case TELOPT_TTYPE: + if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { + char *termtype = conf_get_str(telnet->conf, CONF_termtype); + b = snewn(20 + strlen(termtype), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TTYPE; + b[3] = TELQUAL_IS; + for (n = 0; termtype[n]; n++) + b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ? + termtype[n] + 'A' - 'a' : + termtype[n]); + b[n + 4] = IAC; + b[n + 5] = SE; + telnet->bufsize = sk_write(telnet->s, b, n + 6); + b[n + 4] = 0; + logevent(telnet->logctx, + "server subnegotiation: SB TTYPE SEND"); + logeventf(telnet->logctx, + "client subnegotiation: SB TTYPE IS %s", b + 4); + sfree(b); + } else + logevent(telnet->logctx, + "server subnegotiation: SB TTYPE <something weird>\r\n"); + break; + case TELOPT_OLD_ENVIRON: + case TELOPT_NEW_ENVIRON: + p = telnet->sb_buf->u; + q = p + telnet->sb_buf->len; + if (p < q && *p == TELQUAL_SEND) { + p++; + logeventf(telnet->logctx, "server subnegotiation: SB %s SEND", + telopt(telnet->sb_opt)); + if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { + if (conf_get_bool(telnet->conf, CONF_rfc_environ)) { + value = RFC_VALUE; + var = RFC_VAR; + } else { + value = BSD_VALUE; + var = BSD_VAR; + } + /* + * Try to guess the sense of VAR and VALUE. + */ + while (p < q) { + if (*p == RFC_VAR) { + value = RFC_VALUE; + var = RFC_VAR; + } else if (*p == BSD_VAR) { + value = BSD_VALUE; + var = BSD_VAR; + } + p++; + } + } else { + /* + * With NEW_ENVIRON, the sense of VAR and VALUE + * isn't in doubt. + */ + value = RFC_VALUE; + var = RFC_VAR; + } + bsize = 20; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) + bsize += strlen(ekey) + strlen(eval) + 2; + user = get_remote_username(telnet->conf); + if (user) + bsize += 6 + strlen(user); + + b = snewn(bsize, unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = telnet->sb_opt; + b[3] = TELQUAL_IS; + n = 4; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + b[n++] = var; + for (e = ekey; *e; e++) + b[n++] = *e; + b[n++] = value; + for (e = eval; *e; e++) + b[n++] = *e; + } + if (user) { + b[n++] = var; + b[n++] = 'U'; + b[n++] = 'S'; + b[n++] = 'E'; + b[n++] = 'R'; + b[n++] = value; + for (e = user; *e; e++) + b[n++] = *e; + } + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, b, n); + if (n == 6) { + logeventf(telnet->logctx, + "client subnegotiation: SB %s IS <nothing>", + telopt(telnet->sb_opt)); + } else { + logeventf(telnet->logctx, "client subnegotiation: SB %s IS:", + telopt(telnet->sb_opt)); + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + logeventf(telnet->logctx, " %s=%s", ekey, eval); + } + if (user) + logeventf(telnet->logctx, " USER=%s", user); + } + sfree(b); + sfree(user); + } + break; + } +} + +static void do_telnet_read(Telnet *telnet, const char *buf, size_t len) +{ + strbuf *outbuf = strbuf_new_nm(); + + while (len--) { + int c = (unsigned char) *buf++; + + switch (telnet->state) { + case TOP_LEVEL: + case SEENCR: + if (c == NUL && telnet->state == SEENCR) + telnet->state = TOP_LEVEL; + else if (c == IAC) + telnet->state = SEENIAC; + else { + if (!telnet->in_synch) + put_byte(outbuf, c); + +#if 1 + /* I can't get the F***ing winsock to insert the urgent IAC + * into the right position! Even with SO_OOBINLINE it gives + * it to recv too soon. And of course the DM byte (that + * arrives in the same packet!) appears several K later!! + * + * Oh well, we do get the DM in the right place so I'll + * just stop hiding on the next 0xf2 and hope for the best. + */ + else if (c == DM) + telnet->in_synch = false; +#endif + if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE) + telnet->state = SEENCR; + else + telnet->state = TOP_LEVEL; + } + break; + case SEENIAC: + if (c == DO) + telnet->state = SEENDO; + else if (c == DONT) + telnet->state = SEENDONT; + else if (c == WILL) + telnet->state = SEENWILL; + else if (c == WONT) + telnet->state = SEENWONT; + else if (c == SB) + telnet->state = SEENSB; + else if (c == DM) { + telnet->in_synch = false; + telnet->state = TOP_LEVEL; + } else { + /* ignore everything else; print it if it's IAC */ + if (c == IAC) { + put_byte(outbuf, c); + } + telnet->state = TOP_LEVEL; + } + break; + case SEENWILL: + proc_rec_opt(telnet, WILL, c); + telnet->state = TOP_LEVEL; + break; + case SEENWONT: + proc_rec_opt(telnet, WONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENDO: + proc_rec_opt(telnet, DO, c); + telnet->state = TOP_LEVEL; + break; + case SEENDONT: + proc_rec_opt(telnet, DONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENSB: + telnet->sb_opt = c; + strbuf_clear(telnet->sb_buf); + telnet->state = SUBNEGOT; + break; + case SUBNEGOT: + if (c == IAC) + telnet->state = SUBNEG_IAC; + else { + subneg_addchar: + put_byte(telnet->sb_buf, c); + telnet->state = SUBNEGOT; /* in case we came here by goto */ + } + break; + case SUBNEG_IAC: + if (c != SE) + goto subneg_addchar; /* yes, it's a hack, I know, but... */ + else { + process_subneg(telnet); + telnet->state = TOP_LEVEL; + } + break; + } + + if (outbuf->len >= 4096) { + c_write(telnet, outbuf->u, outbuf->len); + strbuf_clear(outbuf); + } + } + + if (outbuf->len) + c_write(telnet, outbuf->u, outbuf->len); + strbuf_free(outbuf); +} + +static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, + error_msg, error_code, telnet->conf, + telnet->socket_connected); + if (type == PLUGLOG_CONNECT_SUCCESS) { + telnet->socket_connected = true; + if (telnet->ldisc) + ldisc_check_sendok(telnet->ldisc); + } +} + +static void telnet_closing(Plug *plug, PlugCloseType type, + const char *error_msg) +{ + Telnet *telnet = container_of(plug, Telnet, 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 (telnet->s) { + sk_close(telnet->s); + telnet->s = NULL; + if (error_msg) + telnet->closed_on_socket_error = true; + seat_notify_remote_exit(telnet->seat); + seat_notify_remote_disconnect(telnet->seat); + } + if (type != PLUGCLOSE_NORMAL) { + logevent(telnet->logctx, error_msg); + if (type != PLUGCLOSE_USER_ABORT) + seat_connection_fatal(telnet->seat, "%s", error_msg); + } + /* Otherwise, the remote side closed the connection normally. */ +} + +static void telnet_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + if (urgent) + telnet->in_synch = true; + do_telnet_read(telnet, data, len); +} + +static void telnet_sent(Plug *plug, size_t bufsize) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + telnet->bufsize = bufsize; + seat_sent(telnet->seat, telnet->bufsize); +} + +static const PlugVtable Telnet_plugvt = { + .log = telnet_log, + .closing = telnet_closing, + .receive = telnet_receive, + .sent = telnet_sent, +}; + +static char *telnet_description(Interactor *itr) +{ + Telnet *telnet = container_of(itr, Telnet, interactor); + return dupstr(telnet->description); +} + +static LogPolicy *telnet_logpolicy(Interactor *itr) +{ + Telnet *telnet = container_of(itr, Telnet, interactor); + return log_get_policy(telnet->logctx); +} + +static Seat *telnet_get_seat(Interactor *itr) +{ + Telnet *telnet = container_of(itr, Telnet, interactor); + return telnet->seat; +} + +static void telnet_set_seat(Interactor *itr, Seat *seat) +{ + Telnet *telnet = container_of(itr, Telnet, interactor); + telnet->seat = seat; +} + +static const InteractorVtable Telnet_interactorvt = { + .description = telnet_description, + .logpolicy = telnet_logpolicy, + .get_seat = telnet_get_seat, + .set_seat = telnet_set_seat, +}; + +/* + * Called to set up the Telnet 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 char *telnet_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + Telnet *telnet; + char *loghost; + int addressfamily; + + telnet = snew(Telnet); + memset(telnet, 0, sizeof(Telnet)); + telnet->plug.vt = &Telnet_plugvt; + telnet->backend.vt = vt; + telnet->interactor.vt = &Telnet_interactorvt; + telnet->backend.interactor = &telnet->interactor; + telnet->conf = conf_copy(conf); + telnet->s = NULL; + telnet->socket_connected = false; + telnet->closed_on_socket_error = false; + telnet->echoing = true; + telnet->editing = true; + telnet->activated = false; + telnet->sb_buf = strbuf_new(); + telnet->seat = seat; + telnet->logctx = logctx; + telnet->term_width = conf_get_int(telnet->conf, CONF_width); + telnet->term_height = conf_get_int(telnet->conf, CONF_height); + telnet->state = TOP_LEVEL; + telnet->ldisc = NULL; + telnet->pinger = NULL; + telnet->description = default_description(vt, host, port); + *backend_handle = &telnet->backend; + + /* + * Try to find host. + */ + addressfamily = conf_get_int(telnet->conf, CONF_addressfamily); + addr = name_lookup(host, port, realhost, telnet->conf, addressfamily, + telnet->logctx, "Telnet connection"); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return dupstr(err); + } + + if (port < 0) + port = 23; /* default telnet port */ + + /* + * Open socket. + */ + telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, + keepalive, &telnet->plug, telnet->conf, + &telnet->interactor); + if ((err = sk_socket_error(telnet->s)) != NULL) + return dupstr(err); + + /* No local authentication phase in this protocol */ + seat_set_trust_status(telnet->seat, false); + + telnet->pinger = pinger_new(telnet->conf, &telnet->backend); + + /* + * Initialise option states. + */ + if (conf_get_bool(telnet->conf, CONF_passive_telnet)) { + const struct Opt *const *o; + + for (o = opts; *o; o++) + telnet->opt_states[(*o)->index] = INACTIVE; + } else { + const struct Opt *const *o; + + for (o = opts; *o; o++) { + telnet->opt_states[(*o)->index] = (*o)->initial_state; + if (telnet->opt_states[(*o)->index] == REQUESTED) + send_opt(telnet, (*o)->send, (*o)->option); + } + telnet->activated = true; + } + + /* + * Set up SYNCH state. + */ + telnet->in_synch = false; + + /* + * We can send special commands from the start. + */ + seat_update_specials_menu(telnet->seat); + + /* + * loghost overrides realhost, if specified. + */ + loghost = conf_get_str(telnet->conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + return NULL; +} + +static void telnet_free(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + + if (is_tempseat(telnet->seat)) + tempseat_free(telnet->seat); + strbuf_free(telnet->sb_buf); + if (telnet->s) + sk_close(telnet->s); + if (telnet->pinger) + pinger_free(telnet->pinger); + conf_free(telnet->conf); + sfree(telnet->description); + sfree(telnet); +} +/* + * Reconfigure the Telnet backend. There's no immediate action + * necessary, in this backend: we just save the fresh config for + * any subsequent negotiations. + */ +static void telnet_reconfig(Backend *be, Conf *conf) +{ + Telnet *telnet = container_of(be, Telnet, backend); + pinger_reconfig(telnet->pinger, telnet->conf, conf); + conf_free(telnet->conf); + telnet->conf = conf_copy(conf); +} + +/* + * Called to send data down the Telnet connection. + */ +static void telnet_send(Backend *be, const char *buf, size_t len) +{ + Telnet *telnet = container_of(be, Telnet, backend); + unsigned char *p, *end; + static const unsigned char iac[2] = { IAC, IAC }; + static const unsigned char cr[2] = { CR, NUL }; +#if 0 + static const unsigned char nl[2] = { CR, LF }; +#endif + + if (telnet->s == NULL) + return; + + p = (unsigned char *)buf; + end = (unsigned char *)(buf + len); + while (p < end) { + unsigned char *q = p; + + while (p < end && iswritable(*p)) + p++; + telnet->bufsize = sk_write(telnet->s, q, p - q); + + while (p < end && !iswritable(*p)) { + telnet->bufsize = + sk_write(telnet->s, *p == IAC ? iac : cr, 2); + p++; + } + } +} + +/* + * Called to query the current socket sendability status. + */ +static size_t telnet_sendbuffer(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + return telnet->bufsize; +} + +/* + * Called to set the size of the window from Telnet's POV. + */ +static void telnet_size(Backend *be, int width, int height) +{ + Telnet *telnet = container_of(be, Telnet, backend); + unsigned char b[24]; + int n; + + telnet->term_width = width; + telnet->term_height = height; + + if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE) + return; + n = 0; + b[n++] = IAC; + b[n++] = SB; + b[n++] = TELOPT_NAWS; + b[n++] = telnet->term_width >> 8; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_width & 0xFF; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_height >> 8; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_height & 0xFF; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, b, n); + logeventf(telnet->logctx, "client subnegotiation: SB NAWS %d,%d", + telnet->term_width, telnet->term_height); +} + +/* + * Send Telnet special codes. + */ +static void telnet_special(Backend *be, SessionSpecialCode code, int arg) +{ + Telnet *telnet = container_of(be, Telnet, backend); + unsigned char b[2]; + + if (telnet->s == NULL) + return; + + b[0] = IAC; + switch (code) { + case SS_AYT: + b[1] = AYT; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_BRK: + b[1] = BREAK; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EC: + b[1] = EC; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EL: + b[1] = EL; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_GA: + b[1] = GA; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_NOP: + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_ABORT: + b[1] = ABORT; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_AO: + b[1] = AO; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_IP: + b[1] = IP; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_SUSP: + b[1] = SUSP; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EOR: + b[1] = EOR; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EOF: + b[1] = xEOF; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EOL: + /* In BINARY mode, CR-LF becomes just CR - + * and without the NUL suffix too. */ + if (telnet->opt_states[o_we_bin.index] == ACTIVE) + telnet->bufsize = sk_write(telnet->s, "\r", 1); + else + telnet->bufsize = sk_write(telnet->s, "\r\n", 2); + break; + case SS_SYNCH: + b[1] = DM; + telnet->bufsize = sk_write(telnet->s, b, 1); + telnet->bufsize = sk_write_oob(telnet->s, b + 1, 1); + break; + case SS_PING: + if (telnet->opt_states[o_they_sga.index] == ACTIVE) { + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, b, 2); + } + break; + default: + break; /* never heard of it */ + } +} + +static const SessionSpecial *telnet_get_specials(Backend *be) +{ + static const SessionSpecial specials[] = { + {"Are You There", SS_AYT}, + {"Break", SS_BRK}, + {"Synch", SS_SYNCH}, + {"Erase Character", SS_EC}, + {"Erase Line", SS_EL}, + {"Go Ahead", SS_GA}, + {"No Operation", SS_NOP}, + {NULL, SS_SEP}, + {"Abort Process", SS_ABORT}, + {"Abort Output", SS_AO}, + {"Interrupt Process", SS_IP}, + {"Suspend Process", SS_SUSP}, + {NULL, SS_SEP}, + {"End Of Record", SS_EOR}, + {"End Of File", SS_EOF}, + {NULL, SS_EXITMENU} + }; + return specials; +} + +static bool telnet_connected(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + return telnet->s != NULL; +} + +static bool telnet_sendok(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + return telnet->socket_connected; +} + +static void telnet_unthrottle(Backend *be, size_t backlog) +{ + Telnet *telnet = container_of(be, Telnet, backend); + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static bool telnet_ldisc(Backend *be, int option) +{ + Telnet *telnet = container_of(be, Telnet, backend); + if (option == LD_ECHO) + return telnet->echoing; + if (option == LD_EDIT) + return telnet->editing; + return false; +} + +static void telnet_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + Telnet *telnet = container_of(be, Telnet, backend); + telnet->ldisc = ldisc; +} + +static int telnet_exitcode(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + if (telnet->s != NULL) + return -1; /* still connected */ + else if (telnet->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* Telnet doesn't transmit exit codes back to the client */ + return 0; +} + +/* + * cfg_info for Telnet does nothing at all. + */ +static int telnet_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable telnet_backend = { + .init = telnet_init, + .free = telnet_free, + .reconfig = telnet_reconfig, + .send = telnet_send, + .sendbuffer = telnet_sendbuffer, + .size = telnet_size, + .special = telnet_special, + .get_specials = telnet_get_specials, + .connected = telnet_connected, + .exitcode = telnet_exitcode, + .sendok = telnet_sendok, + .ldisc_option_state = telnet_ldisc, + .provide_ldisc = telnet_provide_ldisc, + .unthrottle = telnet_unthrottle, + .cfg_info = telnet_cfg_info, + .id = "telnet", + .displayname_tc = "Telnet", + .displayname_lc = "Telnet", /* proper name, so capitalise it anyway */ + .protocol = PROT_TELNET, + .default_port = 23, +}; diff --git a/otherbackends/testback.c b/otherbackends/testback.c new file mode 100644 index 00000000..f46d1d98 --- /dev/null +++ b/otherbackends/testback.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 1999 Simon Tatham + * Copyright (c) 1999 Ben Harris + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* PuTTY test backends */ + +#include <stdio.h> +#include <stdlib.h> + +#include "putty.h" + +static char *loop_init(const BackendVtable *, Seat *, Backend **, LogContext *, + Conf *, const char *, int, char **, bool, bool); +static void loop_free(Backend *); +static void null_reconfig(Backend *, Conf *); +static void null_send(Backend *, const char *, size_t); +static void loop_send(Backend *, const char *, size_t); +static size_t null_sendbuffer(Backend *); +static size_t loop_sendbuffer(Backend *); +static void null_size(Backend *, int, int); +static void null_special(Backend *, SessionSpecialCode, int); +static const SessionSpecial *null_get_specials(Backend *); +static bool null_connected(Backend *); +static int null_exitcode(Backend *); +static bool null_sendok(Backend *); +static bool null_ldisc(Backend *, int); +static void null_provide_ldisc(Backend *, Ldisc *); +static void null_unthrottle(Backend *, size_t); +static int null_cfg_info(Backend *); + +const BackendVtable null_backend = { + .init = loop_init, + .free = loop_free, + .reconfig = null_reconfig, + .send = null_send, + .sendbuffer = null_sendbuffer, + .size = null_size, + .special = null_special, + .get_specials = null_get_specials, + .connected = null_connected, + .exitcode = null_exitcode, + .sendok = null_sendok, + .ldisc_option_state = null_ldisc, + .provide_ldisc = null_provide_ldisc, + .unthrottle = null_unthrottle, + .cfg_info = null_cfg_info, + .id = "null", + .displayname_tc = "Null", + .displayname_lc = "null", + .protocol = -1, + .default_port = 0, +}; + +const BackendVtable loop_backend = { + .init = loop_init, + .free = loop_free, + .reconfig = null_reconfig, + .send = loop_send, + .sendbuffer = loop_sendbuffer, + .size = null_size, + .special = null_special, + .get_specials = null_get_specials, + .connected = null_connected, + .exitcode = null_exitcode, + .sendok = null_sendok, + .ldisc_option_state = null_ldisc, + .provide_ldisc = null_provide_ldisc, + .unthrottle = null_unthrottle, + .cfg_info = null_cfg_info, + .id = "loop", + .displayname_tc = "Loop", + .displayname_lc = "loop", + .protocol = -1, + .default_port = 0, +}; + +struct loop_state { + Seat *seat; + Backend backend; + size_t sendbuffer; +}; + +static char *loop_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) { + struct loop_state *st = snew(struct loop_state); + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + st->seat = seat; + st->backend.vt = vt; + *backend_handle = &st->backend; + + *realhost = dupstr(host); + + return NULL; +} + +static void loop_free(Backend *be) +{ + struct loop_state *st = container_of(be, struct loop_state, backend); + + sfree(st); +} + +static void null_reconfig(Backend *be, Conf *conf) { + +} + +static void null_send(Backend *be, const char *buf, size_t len) { + +} + +static void loop_send(Backend *be, const char *buf, size_t len) { + struct loop_state *st = container_of(be, struct loop_state, backend); + + st->sendbuffer = seat_output(st->seat, 0, buf, len); +} + +static size_t null_sendbuffer(Backend *be) { + + return 0; +} + +static size_t loop_sendbuffer(Backend *be) { + struct loop_state *st = container_of(be, struct loop_state, backend); + + return st->sendbuffer; +} + +static void null_size(Backend *be, int width, int height) { + +} + +static void null_special(Backend *be, SessionSpecialCode code, int arg) { + +} + +static const SessionSpecial *null_get_specials (Backend *be) { + + return NULL; +} + +static bool null_connected(Backend *be) { + + return false; +} + +static int null_exitcode(Backend *be) { + + return 0; +} + +static bool null_sendok(Backend *be) { + + return true; +} + +static void null_unthrottle(Backend *be, size_t backlog) { + +} + +static bool null_ldisc(Backend *be, int option) { + + return false; +} + +static void null_provide_ldisc (Backend *be, Ldisc *ldisc) { + +} + +static int null_cfg_info(Backend *be) +{ + return 0; +} |