+++ /dev/null
-/*\r
- * Simple Telnet server code, adapted from PuTTY's own Telnet\r
- * client code for use as a Cygwin local pty proxy.\r
- */\r
-\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <string.h>\r
-\r
-#include "sel.h"\r
-#include "telnet.h"\r
-#include "malloc.h"\r
-#include "pty.h"\r
-\r
-#ifndef FALSE\r
-#define FALSE 0\r
-#endif\r
-#ifndef TRUE\r
-#define TRUE 1\r
-#endif\r
-\r
-#define IAC 255 /* interpret as command: */\r
-#define DONT 254 /* you are not to use option */\r
-#define DO 253 /* please, you use option */\r
-#define WONT 252 /* I won't use option */\r
-#define WILL 251 /* I will use option */\r
-#define SB 250 /* interpret as subnegotiation */\r
-#define SE 240 /* end sub negotiation */\r
-\r
-#define GA 249 /* you may reverse the line */\r
-#define EL 248 /* erase the current line */\r
-#define EC 247 /* erase the current character */\r
-#define AYT 246 /* are you there */\r
-#define AO 245 /* abort output--but let prog finish */\r
-#define IP 244 /* interrupt process--permanently */\r
-#define BREAK 243 /* break */\r
-#define DM 242 /* data mark--for connect. cleaning */\r
-#define NOP 241 /* nop */\r
-#define EOR 239 /* end of record (transparent mode) */\r
-#define ABORT 238 /* Abort process */\r
-#define SUSP 237 /* Suspend process */\r
-#define xEOF 236 /* End of file: EOF is already used... */\r
-\r
-#define TELOPTS(X) \\r
- X(BINARY, 0) /* 8-bit data path */ \\r
- X(ECHO, 1) /* echo */ \\r
- X(RCP, 2) /* prepare to reconnect */ \\r
- X(SGA, 3) /* suppress go ahead */ \\r
- X(NAMS, 4) /* approximate message size */ \\r
- X(STATUS, 5) /* give status */ \\r
- X(TM, 6) /* timing mark */ \\r
- X(RCTE, 7) /* remote controlled transmission and echo */ \\r
- X(NAOL, 8) /* negotiate about output line width */ \\r
- X(NAOP, 9) /* negotiate about output page size */ \\r
- X(NAOCRD, 10) /* negotiate about CR disposition */ \\r
- X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \\r
- X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \\r
- X(NAOFFD, 13) /* negotiate about formfeed disposition */ \\r
- X(NAOVTS, 14) /* negotiate about vertical tab stops */ \\r
- X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \\r
- X(NAOLFD, 16) /* negotiate about output LF disposition */ \\r
- X(XASCII, 17) /* extended ascic character set */ \\r
- X(LOGOUT, 18) /* force logout */ \\r
- X(BM, 19) /* byte macro */ \\r
- X(DET, 20) /* data entry terminal */ \\r
- X(SUPDUP, 21) /* supdup protocol */ \\r
- X(SUPDUPOUTPUT, 22) /* supdup output */ \\r
- X(SNDLOC, 23) /* send location */ \\r
- X(TTYPE, 24) /* terminal type */ \\r
- X(EOR, 25) /* end or record */ \\r
- X(TUID, 26) /* TACACS user identification */ \\r
- X(OUTMRK, 27) /* output marking */ \\r
- X(TTYLOC, 28) /* terminal location number */ \\r
- X(3270REGIME, 29) /* 3270 regime */ \\r
- X(X3PAD, 30) /* X.3 PAD */ \\r
- X(NAWS, 31) /* window size */ \\r
- X(TSPEED, 32) /* terminal speed */ \\r
- X(LFLOW, 33) /* remote flow control */ \\r
- X(LINEMODE, 34) /* Linemode option */ \\r
- X(XDISPLOC, 35) /* X Display Location */ \\r
- X(OLD_ENVIRON, 36) /* Old - Environment variables */ \\r
- X(AUTHENTICATION, 37) /* Authenticate */ \\r
- X(ENCRYPT, 38) /* Encryption option */ \\r
- X(NEW_ENVIRON, 39) /* New - Environment variables */ \\r
- X(TN3270E, 40) /* TN3270 enhancements */ \\r
- X(XAUTH, 41) \\r
- X(CHARSET, 42) /* Character set */ \\r
- X(RSP, 43) /* Remote serial port */ \\r
- X(COM_PORT_OPTION, 44) /* Com port control */ \\r
- X(SLE, 45) /* Suppress local echo */ \\r
- X(STARTTLS, 46) /* Start TLS */ \\r
- X(KERMIT, 47) /* Automatic Kermit file transfer */ \\r
- X(SEND_URL, 48) \\r
- X(FORWARD_X, 49) \\r
- X(PRAGMA_LOGON, 138) \\r
- X(SSPI_LOGON, 139) \\r
- X(PRAGMA_HEARTBEAT, 140) \\r
- X(EXOPL, 255) /* extended-options-list */\r
-\r
-#define telnet_enum(x,y) TELOPT_##x = y,\r
-enum { TELOPTS(telnet_enum) dummy=0 };\r
-#undef telnet_enum\r
-\r
-#define TELQUAL_IS 0 /* option is... */\r
-#define TELQUAL_SEND 1 /* send option */\r
-#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */\r
-#define BSD_VAR 1\r
-#define BSD_VALUE 0\r
-#define RFC_VAR 0\r
-#define RFC_VALUE 1\r
-\r
-#define CR 13\r
-#define LF 10\r
-#define NUL 0\r
-\r
-#define iswritable(x) ( (x) != IAC && (x) != CR )\r
-\r
-static char *telopt(int opt)\r
-{\r
-#define telnet_str(x,y) case TELOPT_##x: return #x;\r
- switch (opt) {\r
- TELOPTS(telnet_str)\r
- default:\r
- return "<unknown>";\r
- }\r
-#undef telnet_str\r
-}\r
-\r
-static void telnet_size(void *handle, int width, int height);\r
-\r
-struct Opt {\r
- int send; /* what we initially send */\r
- int nsend; /* -ve send if requested to stop it */\r
- int ack, nak; /* +ve and -ve acknowledgements */\r
- int option; /* the option code */\r
- int index; /* index into telnet->opt_states[] */\r
- enum {\r
- REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE\r
- } initial_state;\r
-};\r
-\r
-enum {\r
- OPTINDEX_NAWS,\r
- OPTINDEX_TSPEED,\r
- OPTINDEX_TTYPE,\r
- OPTINDEX_OENV,\r
- OPTINDEX_NENV,\r
- OPTINDEX_ECHO,\r
- OPTINDEX_WE_SGA,\r
- OPTINDEX_THEY_SGA,\r
- OPTINDEX_WE_BIN,\r
- OPTINDEX_THEY_BIN,\r
- NUM_OPTS\r
-};\r
-\r
-static const struct Opt o_naws =\r
- { DO, DONT, WILL, WONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED };\r
-static const struct Opt o_ttype =\r
- { DO, DONT, WILL, WONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED };\r
-static const struct Opt o_oenv =\r
- { DO, DONT, WILL, WONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE };\r
-static const struct Opt o_nenv =\r
- { DO, DONT, WILL, WONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED };\r
-static const struct Opt o_echo =\r
- { WILL, WONT, DO, DONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED };\r
-static const struct Opt o_they_sga =\r
- { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED };\r
-static const struct Opt o_we_sga =\r
- { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED };\r
-\r
-static const struct Opt *const opts[] = {\r
- &o_echo, &o_we_sga, &o_they_sga, &o_naws, &o_ttype, &o_oenv, &o_nenv, NULL\r
-};\r
-\r
-struct telnet_tag {\r
- int opt_states[NUM_OPTS];\r
-\r
- int sb_opt, sb_len;\r
- unsigned char *sb_buf;\r
- int sb_size;\r
-\r
- enum {\r
- TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,\r
- SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR\r
- } state;\r
-\r
- sel_wfd *net, *pty;\r
-\r
- /*\r
- * Options we must finish processing before launching the shell\r
- */\r
- int old_environ_done, new_environ_done, ttype_done;\r
-\r
- /*\r
- * Ready to start shell?\r
- */\r
- int shell_ok;\r
- int envvarsize;\r
- struct shell_data shdata;\r
-};\r
-\r
-#define TELNET_MAX_BACKLOG 4096\r
-\r
-#define SB_DELTA 1024\r
-\r
-static void send_opt(Telnet telnet, int cmd, int option)\r
-{\r
- unsigned char b[3];\r
-\r
- b[0] = IAC;\r
- b[1] = cmd;\r
- b[2] = option;\r
- sel_write(telnet->net, (char *)b, 3);\r
-}\r
-\r
-static void deactivate_option(Telnet telnet, const struct Opt *o)\r
-{\r
- if (telnet->opt_states[o->index] == REQUESTED ||\r
- telnet->opt_states[o->index] == ACTIVE)\r
- send_opt(telnet, o->nsend, o->option);\r
- telnet->opt_states[o->index] = REALLY_INACTIVE;\r
-}\r
-\r
-/*\r
- * Generate side effects of enabling or disabling an option.\r
- */\r
-static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled)\r
-{\r
-}\r
-\r
-static void activate_option(Telnet telnet, const struct Opt *o)\r
-{\r
- if (o->option == TELOPT_NEW_ENVIRON ||\r
- o->option == TELOPT_OLD_ENVIRON ||\r
- o->option == TELOPT_TTYPE) {\r
- char buf[6];\r
- buf[0] = IAC;\r
- buf[1] = SB;\r
- buf[2] = o->option;\r
- buf[3] = TELQUAL_SEND;\r
- buf[4] = IAC;\r
- buf[5] = SE;\r
- sel_write(telnet->net, buf, 6);\r
- }\r
- option_side_effects(telnet, o, 1);\r
-}\r
-\r
-static void done_option(Telnet telnet, int option)\r
-{\r
- if (option == TELOPT_OLD_ENVIRON)\r
- telnet->old_environ_done = 1;\r
- else if (option == TELOPT_NEW_ENVIRON)\r
- telnet->new_environ_done = 1;\r
- else if (option == TELOPT_TTYPE)\r
- telnet->ttype_done = 1;\r
-\r
- if (telnet->old_environ_done && telnet->new_environ_done &&\r
- telnet->ttype_done) {\r
- telnet->shell_ok = 1;\r
- }\r
-}\r
-\r
-static void refused_option(Telnet telnet, const struct Opt *o)\r
-{\r
- done_option(telnet, o->option);\r
- if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&\r
- telnet->opt_states[o_oenv.index] == INACTIVE) {\r
- send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);\r
- telnet->opt_states[o_oenv.index] = REQUESTED;\r
- telnet->old_environ_done = 0;\r
- }\r
- option_side_effects(telnet, o, 0);\r
-}\r
-\r
-static void proc_rec_opt(Telnet telnet, int cmd, int option)\r
-{\r
- const struct Opt *const *o;\r
-\r
- for (o = opts; *o; o++) {\r
- if ((*o)->option == option && (*o)->ack == cmd) {\r
- switch (telnet->opt_states[(*o)->index]) {\r
- case REQUESTED:\r
- telnet->opt_states[(*o)->index] = ACTIVE;\r
- activate_option(telnet, *o);\r
- break;\r
- case ACTIVE:\r
- break;\r
- case INACTIVE:\r
- telnet->opt_states[(*o)->index] = ACTIVE;\r
- send_opt(telnet, (*o)->send, option);\r
- activate_option(telnet, *o);\r
- break;\r
- case REALLY_INACTIVE:\r
- send_opt(telnet, (*o)->nsend, option);\r
- break;\r
- }\r
- return;\r
- } else if ((*o)->option == option && (*o)->nak == cmd) {\r
- switch (telnet->opt_states[(*o)->index]) {\r
- case REQUESTED:\r
- telnet->opt_states[(*o)->index] = INACTIVE;\r
- refused_option(telnet, *o);\r
- break;\r
- case ACTIVE:\r
- telnet->opt_states[(*o)->index] = INACTIVE;\r
- send_opt(telnet, (*o)->nsend, option);\r
- option_side_effects(telnet, *o, 0);\r
- break;\r
- case INACTIVE:\r
- case REALLY_INACTIVE:\r
- break;\r
- }\r
- return;\r
- }\r
- }\r
- /*\r
- * If we reach here, the option was one we weren't prepared to\r
- * cope with. If the request was positive (WILL or DO), we send\r
- * a negative ack to indicate refusal. If the request was\r
- * negative (WONT / DONT), we must do nothing.\r
- */\r
- if (cmd == WILL || cmd == DO)\r
- send_opt(telnet, (cmd == WILL ? DONT : WONT), option);\r
-}\r
-\r
-static void process_subneg(Telnet telnet)\r
-{\r
- unsigned char b[2048], *p, *q;\r
- int var, value, n;\r
- char *e;\r
-\r
- switch (telnet->sb_opt) {\r
- case TELOPT_OLD_ENVIRON:\r
- case TELOPT_NEW_ENVIRON:\r
- if (telnet->sb_buf[0] == TELQUAL_IS) {\r
- if (telnet->sb_opt == TELOPT_NEW_ENVIRON) {\r
- var = RFC_VAR;\r
- value = RFC_VALUE;\r
- } else {\r
- if (telnet->sb_len > 1 && !(telnet->sb_buf[0] &~ 1)) {\r
- var = telnet->sb_buf[0];\r
- value = BSD_VAR ^ BSD_VALUE ^ var;\r
- } else {\r
- var = BSD_VAR;\r
- value = BSD_VALUE;\r
- }\r
- }\r
- }\r
- n = 1;\r
- while (n < telnet->sb_len && telnet->sb_buf[n] == var) {\r
- int varpos, varlen, valpos, vallen;\r
- char *result;\r
-\r
- varpos = ++n;\r
- while (n < telnet->sb_len && telnet->sb_buf[n] != value)\r
- n++;\r
- if (n == telnet->sb_len)\r
- break;\r
- varlen = n - varpos;\r
- valpos = ++n;\r
- while (n < telnet->sb_len && telnet->sb_buf[n] != var)\r
- n++;\r
- vallen = n - valpos;\r
-\r
- result = snewn(varlen + vallen + 2, char);\r
- sprintf(result, "%.*s=%.*s",\r
- varlen, telnet->sb_buf+varpos,\r
- vallen, telnet->sb_buf+valpos);\r
- if (telnet->shdata.nenvvars >= telnet->envvarsize) {\r
- telnet->envvarsize = telnet->shdata.nenvvars * 3 / 2 + 16;\r
- telnet->shdata.envvars = sresize(telnet->shdata.envvars,\r
- telnet->envvarsize, char *);\r
- }\r
- telnet->shdata.envvars[telnet->shdata.nenvvars++] = result;\r
- }\r
- done_option(telnet, telnet->sb_opt);\r
- break;\r
- case TELOPT_TTYPE:\r
- if (telnet->sb_len >= 1 && telnet->sb_buf[0] == TELQUAL_IS) {\r
- telnet->shdata.termtype = snewn(5 + telnet->sb_len, char);\r
- strcpy(telnet->shdata.termtype, "TERM=");\r
- for (n = 0; n < telnet->sb_len-1; n++) {\r
- char c = telnet->sb_buf[n+1];\r
- if (c >= 'A' && c <= 'Z')\r
- c = c + 'a' - 'A';\r
- telnet->shdata.termtype[n+5] = c;\r
- }\r
- telnet->shdata.termtype[telnet->sb_len+5-1] = '\0';\r
- }\r
- done_option(telnet, telnet->sb_opt);\r
- break;\r
- case TELOPT_NAWS:\r
- if (telnet->sb_len == 4) {\r
- int w, h;\r
- w = (unsigned char)telnet->sb_buf[0];\r
- w = (w << 8) | (unsigned char)telnet->sb_buf[1];\r
- h = (unsigned char)telnet->sb_buf[2];\r
- h = (h << 8) | (unsigned char)telnet->sb_buf[3];\r
- pty_resize(w, h);\r
- }\r
- break;\r
- }\r
-}\r
-\r
-void telnet_from_net(Telnet telnet, char *buf, int len)\r
-{\r
- while (len--) {\r
- int c = (unsigned char) *buf++;\r
-\r
- switch (telnet->state) {\r
- case TOP_LEVEL:\r
- case SEENCR:\r
- /*\r
- * PuTTY sends Telnet's new line sequence (CR LF on\r
- * the wire) in response to the return key. We must\r
- * therefore treat that as equivalent to CR NUL, and\r
- * send CR to the pty.\r
- */\r
- if ((c == NUL || c == '\n') && telnet->state == SEENCR)\r
- telnet->state = TOP_LEVEL;\r
- else if (c == IAC)\r
- telnet->state = SEENIAC;\r
- else {\r
- char cc = c;\r
- sel_write(telnet->pty, &cc, 1);\r
-\r
- telnet->state = SEENCR;\r
- }\r
- break;\r
- case SEENIAC:\r
- if (c == DO)\r
- telnet->state = SEENDO;\r
- else if (c == DONT)\r
- telnet->state = SEENDONT;\r
- else if (c == WILL)\r
- telnet->state = SEENWILL;\r
- else if (c == WONT)\r
- telnet->state = SEENWONT;\r
- else if (c == SB)\r
- telnet->state = SEENSB;\r
- else if (c == DM)\r
- telnet->state = TOP_LEVEL;\r
- else {\r
- /* ignore everything else; print it if it's IAC */\r
- if (c == IAC) {\r
- char cc = c;\r
- sel_write(telnet->pty, &cc, 1);\r
- }\r
- telnet->state = TOP_LEVEL;\r
- }\r
- break;\r
- case SEENWILL:\r
- proc_rec_opt(telnet, WILL, c);\r
- telnet->state = TOP_LEVEL;\r
- break;\r
- case SEENWONT:\r
- proc_rec_opt(telnet, WONT, c);\r
- telnet->state = TOP_LEVEL;\r
- break;\r
- case SEENDO:\r
- proc_rec_opt(telnet, DO, c);\r
- telnet->state = TOP_LEVEL;\r
- break;\r
- case SEENDONT:\r
- proc_rec_opt(telnet, DONT, c);\r
- telnet->state = TOP_LEVEL;\r
- break;\r
- case SEENSB:\r
- telnet->sb_opt = c;\r
- telnet->sb_len = 0;\r
- telnet->state = SUBNEGOT;\r
- break;\r
- case SUBNEGOT:\r
- if (c == IAC)\r
- telnet->state = SUBNEG_IAC;\r
- else {\r
- subneg_addchar:\r
- if (telnet->sb_len >= telnet->sb_size) {\r
- telnet->sb_size += SB_DELTA;\r
- telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size,\r
- unsigned char);\r
- }\r
- telnet->sb_buf[telnet->sb_len++] = c;\r
- telnet->state = SUBNEGOT; /* in case we came here by goto */\r
- }\r
- break;\r
- case SUBNEG_IAC:\r
- if (c != SE)\r
- goto subneg_addchar; /* yes, it's a hack, I know, but... */\r
- else {\r
- process_subneg(telnet);\r
- telnet->state = TOP_LEVEL;\r
- }\r
- break;\r
- }\r
- }\r
-}\r
-\r
-Telnet telnet_new(sel_wfd *net, sel_wfd *pty)\r
-{\r
- Telnet telnet;\r
-\r
- telnet = snew(struct telnet_tag);\r
- telnet->sb_buf = NULL;\r
- telnet->sb_size = 0;\r
- telnet->state = TOP_LEVEL;\r
- telnet->net = net;\r
- telnet->pty = pty;\r
- telnet->shdata.envvars = NULL;\r
- telnet->shdata.nenvvars = telnet->envvarsize = 0;\r
- telnet->shdata.termtype = NULL;\r
-\r
- /*\r
- * Initialise option states.\r
- */\r
- {\r
- const struct Opt *const *o;\r
-\r
- for (o = opts; *o; o++) {\r
- telnet->opt_states[(*o)->index] = (*o)->initial_state;\r
- if (telnet->opt_states[(*o)->index] == REQUESTED)\r
- send_opt(telnet, (*o)->send, (*o)->option);\r
- }\r
- }\r
-\r
- telnet->old_environ_done = 1; /* initially don't want to bother */\r
- telnet->new_environ_done = 0;\r
- telnet->ttype_done = 0;\r
- telnet->shell_ok = 0;\r
-\r
- return telnet;\r
-}\r
-\r
-void telnet_free(Telnet telnet)\r
-{\r
- sfree(telnet->sb_buf);\r
- sfree(telnet);\r
-}\r
-\r
-void telnet_from_pty(Telnet telnet, char *buf, int len)\r
-{\r
- unsigned char *p, *end;\r
- static const unsigned char iac[2] = { IAC, IAC };\r
- static const unsigned char cr[2] = { CR, NUL };\r
-#if 0\r
- static const unsigned char nl[2] = { CR, LF };\r
-#endif\r
-\r
- p = (unsigned char *)buf;\r
- end = (unsigned char *)(buf + len);\r
- while (p < end) {\r
- unsigned char *q = p;\r
-\r
- while (p < end && iswritable(*p))\r
- p++;\r
- sel_write(telnet->net, (char *)q, p - q);\r
-\r
- while (p < end && !iswritable(*p)) {\r
- sel_write(telnet->net, (char *)(*p == IAC ? iac : cr), 2);\r
- p++;\r
- }\r
- }\r
-}\r
-\r
-int telnet_shell_ok(Telnet telnet, struct shell_data *shdata)\r
-{\r
- if (telnet->shell_ok)\r
- *shdata = telnet->shdata; /* structure copy */\r
- return telnet->shell_ok;\r
-}\r