diff options
Diffstat (limited to 'telnet.c')
-rw-r--r-- | telnet.c | 1070 |
1 files changed, 0 insertions, 1070 deletions
diff --git a/telnet.c b/telnet.c deleted file mode 100644 index 3a60e646..00000000 --- a/telnet.c +++ /dev/null @@ -1,1070 +0,0 @@ -/* - * 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 closed_on_socket_error; - - Seat *seat; - LogContext *logctx; - Ldisc *ldisc; - int term_width, term_height; - - int opt_states[NUM_OPTS]; - - bool echoing, editing; - bool activated; - size_t bufsize; - bool in_synch; - int sb_opt; - strbuf *sb_buf; - bool session_started; - - enum { - TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, - SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR - } state; - - Conf *conf; - - Pinger *pinger; - - Plug plug; - Backend backend; -}; - -#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:\t%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:\tSB TSPEED SEND"); - logeventf(telnet->logctx, "client:\tSB TSPEED IS %s", termspeed); - sfree(b); - } else - logevent(telnet->logctx, "server:\tSB 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:\tSB TTYPE SEND"); - logeventf(telnet->logctx, "client:\tSB TTYPE IS %s", b + 4); - sfree(b); - } else - logevent(telnet->logctx, "server:\tSB 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:\tSB %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:\tSB %s IS <nothing>", - telopt(telnet->sb_opt)); - } else { - logeventf(telnet->logctx, "client:\tSB %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, "\t%s=%s", ekey, eval); - } - if (user) - logeventf(telnet->logctx, "\tUSER=%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->session_started); -} - -static void telnet_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - 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); - } - if (error_msg) { - logevent(telnet->logctx, error_msg); - 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; - telnet->session_started = 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; -} - -static const PlugVtable Telnet_plugvt = { - .log = telnet_log, - .closing = telnet_closing, - .receive = telnet_receive, - .sent = telnet_sent, -}; - -/* - * 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; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - telnet = snew(Telnet); - telnet->plug.vt = &Telnet_plugvt; - telnet->backend.vt = vt; - telnet->conf = conf_copy(conf); - telnet->s = NULL; - 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->session_started = true; - *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); - if ((err = sk_socket_error(telnet->s)) != NULL) - return dupstr(err); - - 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); - - 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); -} -/* - * 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 size_t 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 0; - - 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++; - } - } - - return telnet->bufsize; -} - -/* - * 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:\tSB 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 true; -} - -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 = "Telnet", - .protocol = PROT_TELNET, - .default_port = 23, -}; |