Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitrij <kvarkas@gmail.com>2022-10-31 00:45:23 +0300
committerDimitrij <kvarkas@gmail.com>2022-10-31 00:45:23 +0300
commit302fb2e8ddea1c993552c9a30c02f41d01ca54a9 (patch)
treed6cf1b32664296ef2cecda33caeafbe39e6695c1 /otherbackends
parent59105d9b26363e47f00676bd365b2ac8d4cb536a (diff)
parent4ff82ab29a22936b78510c68f544a99e677efed3 (diff)
Merge tag 'tags/0.78'HEADmaster
Diffstat (limited to 'otherbackends')
-rw-r--r--otherbackends/CMakeLists.txt6
-rw-r--r--otherbackends/raw.c377
-rw-r--r--otherbackends/rlogin.c496
-rw-r--r--otherbackends/supdup.c978
-rw-r--r--otherbackends/telnet.c1123
-rw-r--r--otherbackends/testback.c200
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;
+}