Get rid of magic numbers
[TortoiseGit.git] / src / TortoisePlink / TELNET.C
blob1589d6c6cfca4f0bd165fbb804b46bf608a7d8fc
1 /*\r
2  * Telnet backend.\r
3  */\r
4 \r
5 #include <stdio.h>\r
6 #include <stdlib.h>\r
7 #include <limits.h>\r
8 \r
9 #include "putty.h"\r
11 #ifndef FALSE\r
12 #define FALSE 0\r
13 #endif\r
14 #ifndef TRUE\r
15 #define TRUE 1\r
16 #endif\r
18 #define IAC     255                    /* interpret as command: */\r
19 #define DONT    254                    /* you are not to use option */\r
20 #define DO      253                    /* please, you use option */\r
21 #define WONT    252                    /* I won't use option */\r
22 #define WILL    251                    /* I will use option */\r
23 #define SB      250                    /* interpret as subnegotiation */\r
24 #define SE      240                    /* end sub negotiation */\r
26 #define GA      249                    /* you may reverse the line */\r
27 #define EL      248                    /* erase the current line */\r
28 #define EC      247                    /* erase the current character */\r
29 #define AYT     246                    /* are you there */\r
30 #define AO      245                    /* abort output--but let prog finish */\r
31 #define IP      244                    /* interrupt process--permanently */\r
32 #define BREAK   243                    /* break */\r
33 #define DM      242                    /* data mark--for connect. cleaning */\r
34 #define NOP     241                    /* nop */\r
35 #define EOR     239                    /* end of record (transparent mode) */\r
36 #define ABORT   238                    /* Abort process */\r
37 #define SUSP    237                    /* Suspend process */\r
38 #define xEOF    236                    /* End of file: EOF is already used... */\r
40 #define TELOPTS(X) \\r
41     X(BINARY, 0)                       /* 8-bit data path */ \\r
42     X(ECHO, 1)                         /* echo */ \\r
43     X(RCP, 2)                          /* prepare to reconnect */ \\r
44     X(SGA, 3)                          /* suppress go ahead */ \\r
45     X(NAMS, 4)                         /* approximate message size */ \\r
46     X(STATUS, 5)                       /* give status */ \\r
47     X(TM, 6)                           /* timing mark */ \\r
48     X(RCTE, 7)                         /* remote controlled transmission and echo */ \\r
49     X(NAOL, 8)                         /* negotiate about output line width */ \\r
50     X(NAOP, 9)                         /* negotiate about output page size */ \\r
51     X(NAOCRD, 10)                      /* negotiate about CR disposition */ \\r
52     X(NAOHTS, 11)                      /* negotiate about horizontal tabstops */ \\r
53     X(NAOHTD, 12)                      /* negotiate about horizontal tab disposition */ \\r
54     X(NAOFFD, 13)                      /* negotiate about formfeed disposition */ \\r
55     X(NAOVTS, 14)                      /* negotiate about vertical tab stops */ \\r
56     X(NAOVTD, 15)                      /* negotiate about vertical tab disposition */ \\r
57     X(NAOLFD, 16)                      /* negotiate about output LF disposition */ \\r
58     X(XASCII, 17)                      /* extended ascic character set */ \\r
59     X(LOGOUT, 18)                      /* force logout */ \\r
60     X(BM, 19)                          /* byte macro */ \\r
61     X(DET, 20)                         /* data entry terminal */ \\r
62     X(SUPDUP, 21)                      /* supdup protocol */ \\r
63     X(SUPDUPOUTPUT, 22)                /* supdup output */ \\r
64     X(SNDLOC, 23)                      /* send location */ \\r
65     X(TTYPE, 24)                       /* terminal type */ \\r
66     X(EOR, 25)                         /* end or record */ \\r
67     X(TUID, 26)                        /* TACACS user identification */ \\r
68     X(OUTMRK, 27)                      /* output marking */ \\r
69     X(TTYLOC, 28)                      /* terminal location number */ \\r
70     X(3270REGIME, 29)                  /* 3270 regime */ \\r
71     X(X3PAD, 30)                       /* X.3 PAD */ \\r
72     X(NAWS, 31)                        /* window size */ \\r
73     X(TSPEED, 32)                      /* terminal speed */ \\r
74     X(LFLOW, 33)                       /* remote flow control */ \\r
75     X(LINEMODE, 34)                    /* Linemode option */ \\r
76     X(XDISPLOC, 35)                    /* X Display Location */ \\r
77     X(OLD_ENVIRON, 36)                 /* Old - Environment variables */ \\r
78     X(AUTHENTICATION, 37)              /* Authenticate */ \\r
79     X(ENCRYPT, 38)                     /* Encryption option */ \\r
80     X(NEW_ENVIRON, 39)                 /* New - Environment variables */ \\r
81     X(TN3270E, 40)                     /* TN3270 enhancements */ \\r
82     X(XAUTH, 41)                       \\r
83     X(CHARSET, 42)                     /* Character set */ \\r
84     X(RSP, 43)                         /* Remote serial port */ \\r
85     X(COM_PORT_OPTION, 44)             /* Com port control */ \\r
86     X(SLE, 45)                         /* Suppress local echo */ \\r
87     X(STARTTLS, 46)                    /* Start TLS */ \\r
88     X(KERMIT, 47)                      /* Automatic Kermit file transfer */ \\r
89     X(SEND_URL, 48)                    \\r
90     X(FORWARD_X, 49)                   \\r
91     X(PRAGMA_LOGON, 138)               \\r
92     X(SSPI_LOGON, 139)                 \\r
93     X(PRAGMA_HEARTBEAT, 140)           \\r
94     X(EXOPL, 255)                      /* extended-options-list */\r
96 #define telnet_enum(x,y) TELOPT_##x = y,\r
97 enum { TELOPTS(telnet_enum) dummy=0 };\r
98 #undef telnet_enum\r
100 #define TELQUAL_IS      0              /* option is... */\r
101 #define TELQUAL_SEND    1              /* send option */\r
102 #define TELQUAL_INFO    2              /* ENVIRON: informational version of IS */\r
103 #define BSD_VAR 1\r
104 #define BSD_VALUE 0\r
105 #define RFC_VAR 0\r
106 #define RFC_VALUE 1\r
108 #define CR 13\r
109 #define LF 10\r
110 #define NUL 0\r
112 #define iswritable(x) \\r
113         ( (x) != IAC && \\r
114               (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR))\r
116 static const char *telopt(int opt)\r
118 #define telnet_str(x,y) case TELOPT_##x: return #x;\r
119     switch (opt) {\r
120         TELOPTS(telnet_str)\r
121       default:\r
122         return "<unknown>";\r
123     }\r
124 #undef telnet_str\r
127 static void telnet_size(void *handle, int width, int height);\r
129 struct Opt {\r
130     int send;                          /* what we initially send */\r
131     int nsend;                         /* -ve send if requested to stop it */\r
132     int ack, nak;                      /* +ve and -ve acknowledgements */\r
133     int option;                        /* the option code */\r
134     int index;                         /* index into telnet->opt_states[] */\r
135     enum {\r
136         REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE\r
137     } initial_state;\r
138 };\r
140 enum {\r
141     OPTINDEX_NAWS,\r
142     OPTINDEX_TSPEED,\r
143     OPTINDEX_TTYPE,\r
144     OPTINDEX_OENV,\r
145     OPTINDEX_NENV,\r
146     OPTINDEX_ECHO,\r
147     OPTINDEX_WE_SGA,\r
148     OPTINDEX_THEY_SGA,\r
149     OPTINDEX_WE_BIN,\r
150     OPTINDEX_THEY_BIN,\r
151     NUM_OPTS\r
152 };\r
154 static const struct Opt o_naws =\r
155     { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED };\r
156 static const struct Opt o_tspeed =\r
157     { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED };\r
158 static const struct Opt o_ttype =\r
159     { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED };\r
160 static const struct Opt o_oenv =\r
161     { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE };\r
162 static const struct Opt o_nenv =\r
163     { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED };\r
164 static const struct Opt o_echo =\r
165     { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED };\r
166 static const struct Opt o_we_sga =\r
167     { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED };\r
168 static const struct Opt o_they_sga =\r
169     { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED };\r
170 static const struct Opt o_we_bin =\r
171     { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE };\r
172 static const struct Opt o_they_bin =\r
173     { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE };\r
175 static const struct Opt *const opts[] = {\r
176     &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo,\r
177     &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL\r
178 };\r
180 typedef struct telnet_tag {\r
181     const struct plug_function_table *fn;\r
182     /* the above field _must_ be first in the structure */\r
184     Socket s;\r
185     int closed_on_socket_error;\r
187     void *frontend;\r
188     void *ldisc;\r
189     int term_width, term_height;\r
191     int opt_states[NUM_OPTS];\r
193     int echoing, editing;\r
194     int activated;\r
195     int bufsize;\r
196     int in_synch;\r
197     int sb_opt, sb_len;\r
198     unsigned char *sb_buf;\r
199     int sb_size;\r
200     int session_started;\r
202     enum {\r
203         TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,\r
204             SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR\r
205     } state;\r
207     Conf *conf;\r
209     Pinger pinger;\r
210 } *Telnet;\r
212 #define TELNET_MAX_BACKLOG 4096\r
214 #define SB_DELTA 1024\r
216 static void c_write(Telnet telnet, const char *buf, int len)\r
218     int backlog;\r
219     backlog = from_backend(telnet->frontend, 0, buf, len);\r
220     sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);\r
223 static void log_option(Telnet telnet, const char *sender, int cmd, int option)\r
225     char *buf;\r
226     /*\r
227      * The strange-looking "<?""?>" below is there to avoid a\r
228      * trigraph - a double question mark followed by > maps to a\r
229      * closing brace character!\r
230      */\r
231     buf = dupprintf("%s:\t%s %s", sender,\r
232                     (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" :\r
233                      cmd == DO ? "DO" : cmd == DONT ? "DONT" : "<?""?>"),\r
234                     telopt(option));\r
235     logevent(telnet->frontend, buf);\r
236     sfree(buf);\r
239 static void send_opt(Telnet telnet, int cmd, int option)\r
241     unsigned char b[3];\r
243     b[0] = IAC;\r
244     b[1] = cmd;\r
245     b[2] = option;\r
246     telnet->bufsize = sk_write(telnet->s, (char *)b, 3);\r
247     log_option(telnet, "client", cmd, option);\r
250 static void deactivate_option(Telnet telnet, const struct Opt *o)\r
252     if (telnet->opt_states[o->index] == REQUESTED ||\r
253         telnet->opt_states[o->index] == ACTIVE)\r
254         send_opt(telnet, o->nsend, o->option);\r
255     telnet->opt_states[o->index] = REALLY_INACTIVE;\r
258 /*\r
259  * Generate side effects of enabling or disabling an option.\r
260  */\r
261 static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled)\r
263     if (o->option == TELOPT_ECHO && o->send == DO)\r
264         telnet->echoing = !enabled;\r
265     else if (o->option == TELOPT_SGA && o->send == DO)\r
266         telnet->editing = !enabled;\r
267     if (telnet->ldisc)                 /* cause ldisc to notice the change */\r
268         ldisc_echoedit_update(telnet->ldisc);\r
270     /* Ensure we get the minimum options */\r
271     if (!telnet->activated) {\r
272         if (telnet->opt_states[o_echo.index] == INACTIVE) {\r
273             telnet->opt_states[o_echo.index] = REQUESTED;\r
274             send_opt(telnet, o_echo.send, o_echo.option);\r
275         }\r
276         if (telnet->opt_states[o_we_sga.index] == INACTIVE) {\r
277             telnet->opt_states[o_we_sga.index] = REQUESTED;\r
278             send_opt(telnet, o_we_sga.send, o_we_sga.option);\r
279         }\r
280         if (telnet->opt_states[o_they_sga.index] == INACTIVE) {\r
281             telnet->opt_states[o_they_sga.index] = REQUESTED;\r
282             send_opt(telnet, o_they_sga.send, o_they_sga.option);\r
283         }\r
284         telnet->activated = TRUE;\r
285     }\r
288 static void activate_option(Telnet telnet, const struct Opt *o)\r
290     if (o->send == WILL && o->option == TELOPT_NAWS)\r
291         telnet_size(telnet, telnet->term_width, telnet->term_height);\r
292     if (o->send == WILL &&\r
293         (o->option == TELOPT_NEW_ENVIRON ||\r
294          o->option == TELOPT_OLD_ENVIRON)) {\r
295         /*\r
296          * We may only have one kind of ENVIRON going at a time.\r
297          * This is a hack, but who cares.\r
298          */\r
299         deactivate_option(telnet, o->option ==\r
300                           TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv);\r
301     }\r
302     option_side_effects(telnet, o, 1);\r
305 static void refused_option(Telnet telnet, const struct Opt *o)\r
307     if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&\r
308         telnet->opt_states[o_oenv.index] == INACTIVE) {\r
309         send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);\r
310         telnet->opt_states[o_oenv.index] = REQUESTED;\r
311     }\r
312     option_side_effects(telnet, o, 0);\r
315 static void proc_rec_opt(Telnet telnet, int cmd, int option)\r
317     const struct Opt *const *o;\r
319     log_option(telnet, "server", cmd, option);\r
320     for (o = opts; *o; o++) {\r
321         if ((*o)->option == option && (*o)->ack == cmd) {\r
322             switch (telnet->opt_states[(*o)->index]) {\r
323               case REQUESTED:\r
324                 telnet->opt_states[(*o)->index] = ACTIVE;\r
325                 activate_option(telnet, *o);\r
326                 break;\r
327               case ACTIVE:\r
328                 break;\r
329               case INACTIVE:\r
330                 telnet->opt_states[(*o)->index] = ACTIVE;\r
331                 send_opt(telnet, (*o)->send, option);\r
332                 activate_option(telnet, *o);\r
333                 break;\r
334               case REALLY_INACTIVE:\r
335                 send_opt(telnet, (*o)->nsend, option);\r
336                 break;\r
337             }\r
338             return;\r
339         } else if ((*o)->option == option && (*o)->nak == cmd) {\r
340             switch (telnet->opt_states[(*o)->index]) {\r
341               case REQUESTED:\r
342                 telnet->opt_states[(*o)->index] = INACTIVE;\r
343                 refused_option(telnet, *o);\r
344                 break;\r
345               case ACTIVE:\r
346                 telnet->opt_states[(*o)->index] = INACTIVE;\r
347                 send_opt(telnet, (*o)->nsend, option);\r
348                 option_side_effects(telnet, *o, 0);\r
349                 break;\r
350               case INACTIVE:\r
351               case REALLY_INACTIVE:\r
352                 break;\r
353             }\r
354             return;\r
355         }\r
356     }\r
357     /*\r
358      * If we reach here, the option was one we weren't prepared to\r
359      * cope with. If the request was positive (WILL or DO), we send\r
360      * a negative ack to indicate refusal. If the request was\r
361      * negative (WONT / DONT), we must do nothing.\r
362      */\r
363     if (cmd == WILL || cmd == DO)\r
364         send_opt(telnet, (cmd == WILL ? DONT : WONT), option);\r
367 static void process_subneg(Telnet telnet)\r
369     unsigned char *b, *p, *q;\r
370     int var, value, n, bsize;\r
371     char *e, *eval, *ekey, *user;\r
373     switch (telnet->sb_opt) {\r
374       case TELOPT_TSPEED:\r
375         if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {\r
376             char *logbuf;\r
377             char *termspeed = conf_get_str(telnet->conf, CONF_termspeed);\r
378             b = snewn(20 + strlen(termspeed), unsigned char);\r
379             b[0] = IAC;\r
380             b[1] = SB;\r
381             b[2] = TELOPT_TSPEED;\r
382             b[3] = TELQUAL_IS;\r
383             strcpy((char *)(b + 4), termspeed);\r
384             n = 4 + strlen(termspeed);\r
385             b[n] = IAC;\r
386             b[n + 1] = SE;\r
387             telnet->bufsize = sk_write(telnet->s, (char *)b, n + 2);\r
388             logevent(telnet->frontend, "server:\tSB TSPEED SEND");\r
389             logbuf = dupprintf("client:\tSB TSPEED IS %s", termspeed);\r
390             logevent(telnet->frontend, logbuf);\r
391             sfree(logbuf);\r
392             sfree(b);\r
393         } else\r
394             logevent(telnet->frontend, "server:\tSB TSPEED <something weird>");\r
395         break;\r
396       case TELOPT_TTYPE:\r
397         if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {\r
398             char *logbuf;\r
399             char *termtype = conf_get_str(telnet->conf, CONF_termtype);\r
400             b = snewn(20 + strlen(termtype), unsigned char);\r
401             b[0] = IAC;\r
402             b[1] = SB;\r
403             b[2] = TELOPT_TTYPE;\r
404             b[3] = TELQUAL_IS;\r
405             for (n = 0; termtype[n]; n++)\r
406                 b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ?\r
407                             termtype[n] + 'A' - 'a' :\r
408                             termtype[n]);\r
409             b[n + 4] = IAC;\r
410             b[n + 5] = SE;\r
411             telnet->bufsize = sk_write(telnet->s, (char *)b, n + 6);\r
412             b[n + 4] = 0;\r
413             logevent(telnet->frontend, "server:\tSB TTYPE SEND");\r
414             logbuf = dupprintf("client:\tSB TTYPE IS %s", b + 4);\r
415             logevent(telnet->frontend, logbuf);\r
416             sfree(logbuf);\r
417             sfree(b);\r
418         } else\r
419             logevent(telnet->frontend, "server:\tSB TTYPE <something weird>\r\n");\r
420         break;\r
421       case TELOPT_OLD_ENVIRON:\r
422       case TELOPT_NEW_ENVIRON:\r
423         p = telnet->sb_buf;\r
424         q = p + telnet->sb_len;\r
425         if (p < q && *p == TELQUAL_SEND) {\r
426             char *logbuf;\r
427             p++;\r
428             logbuf = dupprintf("server:\tSB %s SEND", telopt(telnet->sb_opt));\r
429             logevent(telnet->frontend, logbuf);\r
430             sfree(logbuf);\r
431             if (telnet->sb_opt == TELOPT_OLD_ENVIRON) {\r
432                 if (conf_get_int(telnet->conf, CONF_rfc_environ)) {\r
433                     value = RFC_VALUE;\r
434                     var = RFC_VAR;\r
435                 } else {\r
436                     value = BSD_VALUE;\r
437                     var = BSD_VAR;\r
438                 }\r
439                 /*\r
440                  * Try to guess the sense of VAR and VALUE.\r
441                  */\r
442                 while (p < q) {\r
443                     if (*p == RFC_VAR) {\r
444                         value = RFC_VALUE;\r
445                         var = RFC_VAR;\r
446                     } else if (*p == BSD_VAR) {\r
447                         value = BSD_VALUE;\r
448                         var = BSD_VAR;\r
449                     }\r
450                     p++;\r
451                 }\r
452             } else {\r
453                 /*\r
454                  * With NEW_ENVIRON, the sense of VAR and VALUE\r
455                  * isn't in doubt.\r
456                  */\r
457                 value = RFC_VALUE;\r
458                 var = RFC_VAR;\r
459             }\r
460             bsize = 20;\r
461             for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
462                                          NULL, &ekey);\r
463                  eval != NULL;\r
464                  eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
465                                          ekey, &ekey))\r
466                  bsize += strlen(ekey) + strlen(eval) + 2;\r
467             user = get_remote_username(telnet->conf);\r
468             if (user)\r
469                 bsize += 6 + strlen(user);\r
471             b = snewn(bsize, unsigned char);\r
472             b[0] = IAC;\r
473             b[1] = SB;\r
474             b[2] = telnet->sb_opt;\r
475             b[3] = TELQUAL_IS;\r
476             n = 4;\r
477             for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
478                                          NULL, &ekey);\r
479                  eval != NULL;\r
480                  eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
481                                          ekey, &ekey)) {\r
482                 b[n++] = var;\r
483                 for (e = ekey; *e; e++)\r
484                     b[n++] = *e;\r
485                 b[n++] = value;\r
486                 for (e = eval; *e; e++)\r
487                     b[n++] = *e;\r
488             }\r
489             if (user) {\r
490                 b[n++] = var;\r
491                 b[n++] = 'U';\r
492                 b[n++] = 'S';\r
493                 b[n++] = 'E';\r
494                 b[n++] = 'R';\r
495                 b[n++] = value;\r
496                 for (e = user; *e; e++)\r
497                     b[n++] = *e;\r
498             }\r
499             b[n++] = IAC;\r
500             b[n++] = SE;\r
501             telnet->bufsize = sk_write(telnet->s, (char *)b, n);\r
502             if (n == 6) {\r
503                 logbuf = dupprintf("client:\tSB %s IS <nothing>",\r
504                                    telopt(telnet->sb_opt));\r
505                 logevent(telnet->frontend, logbuf);\r
506                 sfree(logbuf);\r
507             } else {\r
508                 logbuf = dupprintf("client:\tSB %s IS:",\r
509                                    telopt(telnet->sb_opt));\r
510                 logevent(telnet->frontend, logbuf);\r
511                 sfree(logbuf);\r
512                 for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
513                                              NULL, &ekey);\r
514                      eval != NULL;\r
515                      eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
516                                              ekey, &ekey)) {\r
517                     logbuf = dupprintf("\t%s=%s", ekey, eval);\r
518                     logevent(telnet->frontend, logbuf);\r
519                     sfree(logbuf);\r
520                 }\r
521                 if (user) {\r
522                     logbuf = dupprintf("\tUSER=%s", user);\r
523                     logevent(telnet->frontend, logbuf);\r
524                     sfree(logbuf);\r
525                 }\r
526             }\r
527             sfree(b);\r
528             sfree(user);\r
529         }\r
530         break;\r
531     }\r
534 static void do_telnet_read(Telnet telnet, char *buf, int len)\r
536     char *outbuf = NULL;\r
537     int outbuflen = 0, outbufsize = 0;\r
539 #define ADDTOBUF(c) do { \\r
540     if (outbuflen >= outbufsize) { \\r
541         outbufsize = outbuflen + 256; \\r
542         outbuf = sresize(outbuf, outbufsize, char); \\r
543     } \\r
544     outbuf[outbuflen++] = (c); \\r
545 } while (0)\r
547     while (len--) {\r
548         int c = (unsigned char) *buf++;\r
550         switch (telnet->state) {\r
551           case TOP_LEVEL:\r
552           case SEENCR:\r
553             if (c == NUL && telnet->state == SEENCR)\r
554                 telnet->state = TOP_LEVEL;\r
555             else if (c == IAC)\r
556                 telnet->state = SEENIAC;\r
557             else {\r
558                 if (!telnet->in_synch)\r
559                     ADDTOBUF(c);\r
561 #if 1\r
562                 /* I can't get the F***ing winsock to insert the urgent IAC\r
563                  * into the right position! Even with SO_OOBINLINE it gives\r
564                  * it to recv too soon. And of course the DM byte (that\r
565                  * arrives in the same packet!) appears several K later!!\r
566                  *\r
567                  * Oh well, we do get the DM in the right place so I'll\r
568                  * just stop hiding on the next 0xf2 and hope for the best.\r
569                  */\r
570                 else if (c == DM)\r
571                     telnet->in_synch = 0;\r
572 #endif\r
573                 if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE)\r
574                     telnet->state = SEENCR;\r
575                 else\r
576                     telnet->state = TOP_LEVEL;\r
577             }\r
578             break;\r
579           case SEENIAC:\r
580             if (c == DO)\r
581                 telnet->state = SEENDO;\r
582             else if (c == DONT)\r
583                 telnet->state = SEENDONT;\r
584             else if (c == WILL)\r
585                 telnet->state = SEENWILL;\r
586             else if (c == WONT)\r
587                 telnet->state = SEENWONT;\r
588             else if (c == SB)\r
589                 telnet->state = SEENSB;\r
590             else if (c == DM) {\r
591                 telnet->in_synch = 0;\r
592                 telnet->state = TOP_LEVEL;\r
593             } else {\r
594                 /* ignore everything else; print it if it's IAC */\r
595                 if (c == IAC) {\r
596                     ADDTOBUF(c);\r
597                 }\r
598                 telnet->state = TOP_LEVEL;\r
599             }\r
600             break;\r
601           case SEENWILL:\r
602             proc_rec_opt(telnet, WILL, c);\r
603             telnet->state = TOP_LEVEL;\r
604             break;\r
605           case SEENWONT:\r
606             proc_rec_opt(telnet, WONT, c);\r
607             telnet->state = TOP_LEVEL;\r
608             break;\r
609           case SEENDO:\r
610             proc_rec_opt(telnet, DO, c);\r
611             telnet->state = TOP_LEVEL;\r
612             break;\r
613           case SEENDONT:\r
614             proc_rec_opt(telnet, DONT, c);\r
615             telnet->state = TOP_LEVEL;\r
616             break;\r
617           case SEENSB:\r
618             telnet->sb_opt = c;\r
619             telnet->sb_len = 0;\r
620             telnet->state = SUBNEGOT;\r
621             break;\r
622           case SUBNEGOT:\r
623             if (c == IAC)\r
624                 telnet->state = SUBNEG_IAC;\r
625             else {\r
626               subneg_addchar:\r
627                 if (telnet->sb_len >= telnet->sb_size) {\r
628                     telnet->sb_size += SB_DELTA;\r
629                     telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size,\r
630                                              unsigned char);\r
631                 }\r
632                 telnet->sb_buf[telnet->sb_len++] = c;\r
633                 telnet->state = SUBNEGOT;       /* in case we came here by goto */\r
634             }\r
635             break;\r
636           case SUBNEG_IAC:\r
637             if (c != SE)\r
638                 goto subneg_addchar;   /* yes, it's a hack, I know, but... */\r
639             else {\r
640                 process_subneg(telnet);\r
641                 telnet->state = TOP_LEVEL;\r
642             }\r
643             break;\r
644         }\r
645     }\r
647     if (outbuflen)\r
648         c_write(telnet, outbuf, outbuflen);\r
649     sfree(outbuf);\r
652 static void telnet_log(Plug plug, int type, SockAddr addr, int port,\r
653                        const char *error_msg, int error_code)\r
655     Telnet telnet = (Telnet) plug;\r
656     backend_socket_log(telnet->frontend, type, addr, port,\r
657                        error_msg, error_code, telnet->conf,\r
658                        telnet->session_started);\r
661 static void telnet_closing(Plug plug, const char *error_msg, int error_code,\r
662                            int calling_back)\r
664     Telnet telnet = (Telnet) plug;\r
666     /*\r
667      * We don't implement independent EOF in each direction for Telnet\r
668      * connections; as soon as we get word that the remote side has\r
669      * sent us EOF, we wind up the whole connection.\r
670      */\r
672     if (telnet->s) {\r
673         sk_close(telnet->s);\r
674         telnet->s = NULL;\r
675         if (error_msg)\r
676             telnet->closed_on_socket_error = TRUE;\r
677         notify_remote_exit(telnet->frontend);\r
678     }\r
679     if (error_msg) {\r
680         logevent(telnet->frontend, error_msg);\r
681         connection_fatal(telnet->frontend, "%s", error_msg);\r
682     }\r
683     /* Otherwise, the remote side closed the connection normally. */\r
686 static void telnet_receive(Plug plug, int urgent, char *data, int len)\r
688     Telnet telnet = (Telnet) plug;\r
689     if (urgent)\r
690         telnet->in_synch = TRUE;\r
691     telnet->session_started = TRUE;\r
692     do_telnet_read(telnet, data, len);\r
695 static void telnet_sent(Plug plug, int bufsize)\r
697     Telnet telnet = (Telnet) plug;\r
698     telnet->bufsize = bufsize;\r
701 /*\r
702  * Called to set up the Telnet connection.\r
703  *\r
704  * Returns an error message, or NULL on success.\r
705  *\r
706  * Also places the canonical host name into `realhost'. It must be\r
707  * freed by the caller.\r
708  */\r
709 static const char *telnet_init(void *frontend_handle, void **backend_handle,\r
710                                Conf *conf, const char *host, int port,\r
711                                char **realhost, int nodelay, int keepalive)\r
713     static const struct plug_function_table fn_table = {\r
714         telnet_log,\r
715         telnet_closing,\r
716         telnet_receive,\r
717         telnet_sent\r
718     };\r
719     SockAddr addr;\r
720     const char *err;\r
721     Telnet telnet;\r
722     char *loghost;\r
723     int addressfamily;\r
725     telnet = snew(struct telnet_tag);\r
726     telnet->fn = &fn_table;\r
727     telnet->conf = conf_copy(conf);\r
728     telnet->s = NULL;\r
729     telnet->closed_on_socket_error = FALSE;\r
730     telnet->echoing = TRUE;\r
731     telnet->editing = TRUE;\r
732     telnet->activated = FALSE;\r
733     telnet->sb_buf = NULL;\r
734     telnet->sb_size = 0;\r
735     telnet->frontend = frontend_handle;\r
736     telnet->term_width = conf_get_int(telnet->conf, CONF_width);\r
737     telnet->term_height = conf_get_int(telnet->conf, CONF_height);\r
738     telnet->state = TOP_LEVEL;\r
739     telnet->ldisc = NULL;\r
740     telnet->pinger = NULL;\r
741     telnet->session_started = TRUE;\r
742     *backend_handle = telnet;\r
744     /*\r
745      * Try to find host.\r
746      */\r
747     addressfamily = conf_get_int(telnet->conf, CONF_addressfamily);\r
748     addr = name_lookup(host, port, realhost, telnet->conf, addressfamily,\r
749                        telnet->frontend, "Telnet connection");\r
750     if ((err = sk_addr_error(addr)) != NULL) {\r
751         sk_addr_free(addr);\r
752         return err;\r
753     }\r
755     if (port < 0)\r
756         port = 23;                     /* default telnet port */\r
758     /*\r
759      * Open socket.\r
760      */\r
761     telnet->s = new_connection(addr, *realhost, port, 0, 1,\r
762                                nodelay, keepalive, (Plug) telnet, telnet->conf);\r
763     if ((err = sk_socket_error(telnet->s)) != NULL)\r
764         return err;\r
766     telnet->pinger = pinger_new(telnet->conf, &telnet_backend, telnet);\r
768     /*\r
769      * Initialise option states.\r
770      */\r
771     if (conf_get_int(telnet->conf, CONF_passive_telnet)) {\r
772         const struct Opt *const *o;\r
774         for (o = opts; *o; o++)\r
775             telnet->opt_states[(*o)->index] = INACTIVE;\r
776     } else {\r
777         const struct Opt *const *o;\r
779         for (o = opts; *o; o++) {\r
780             telnet->opt_states[(*o)->index] = (*o)->initial_state;\r
781             if (telnet->opt_states[(*o)->index] == REQUESTED)\r
782                 send_opt(telnet, (*o)->send, (*o)->option);\r
783         }\r
784         telnet->activated = TRUE;\r
785     }\r
787     /*\r
788      * Set up SYNCH state.\r
789      */\r
790     telnet->in_synch = FALSE;\r
792     /*\r
793      * We can send special commands from the start.\r
794      */\r
795     update_specials_menu(telnet->frontend);\r
797     /*\r
798      * loghost overrides realhost, if specified.\r
799      */\r
800     loghost = conf_get_str(telnet->conf, CONF_loghost);\r
801     if (*loghost) {\r
802         char *colon;\r
804         sfree(*realhost);\r
805         *realhost = dupstr(loghost);\r
807         colon = host_strrchr(*realhost, ':');\r
808         if (colon)\r
809             *colon++ = '\0';\r
810     }\r
812     return NULL;\r
815 static void telnet_free(void *handle)\r
817     Telnet telnet = (Telnet) handle;\r
819     sfree(telnet->sb_buf);\r
820     if (telnet->s)\r
821         sk_close(telnet->s);\r
822     if (telnet->pinger)\r
823         pinger_free(telnet->pinger);\r
824     conf_free(telnet->conf);\r
825     sfree(telnet);\r
827 /*\r
828  * Reconfigure the Telnet backend. There's no immediate action\r
829  * necessary, in this backend: we just save the fresh config for\r
830  * any subsequent negotiations.\r
831  */\r
832 static void telnet_reconfig(void *handle, Conf *conf)\r
834     Telnet telnet = (Telnet) handle;\r
835     pinger_reconfig(telnet->pinger, telnet->conf, conf);\r
836     conf_free(telnet->conf);\r
837     telnet->conf = conf_copy(conf);\r
840 /*\r
841  * Called to send data down the Telnet connection.\r
842  */\r
843 static int telnet_send(void *handle, const char *buf, int len)\r
845     Telnet telnet = (Telnet) handle;\r
846     unsigned char *p, *end;\r
847     static const unsigned char iac[2] = { IAC, IAC };\r
848     static const unsigned char cr[2] = { CR, NUL };\r
849 #if 0\r
850     static const unsigned char nl[2] = { CR, LF };\r
851 #endif\r
853     if (telnet->s == NULL)\r
854         return 0;\r
856     p = (unsigned char *)buf;\r
857     end = (unsigned char *)(buf + len);\r
858     while (p < end) {\r
859         unsigned char *q = p;\r
861         while (p < end && iswritable(*p))\r
862             p++;\r
863         telnet->bufsize = sk_write(telnet->s, (char *)q, p - q);\r
865         while (p < end && !iswritable(*p)) {\r
866             telnet->bufsize = \r
867                 sk_write(telnet->s, (char *)(*p == IAC ? iac : cr), 2);\r
868             p++;\r
869         }\r
870     }\r
872     return telnet->bufsize;\r
875 /*\r
876  * Called to query the current socket sendability status.\r
877  */\r
878 static int telnet_sendbuffer(void *handle)\r
880     Telnet telnet = (Telnet) handle;\r
881     return telnet->bufsize;\r
884 /*\r
885  * Called to set the size of the window from Telnet's POV.\r
886  */\r
887 static void telnet_size(void *handle, int width, int height)\r
889     Telnet telnet = (Telnet) handle;\r
890     unsigned char b[24];\r
891     int n;\r
892     char *logbuf;\r
894     telnet->term_width = width;\r
895     telnet->term_height = height;\r
897     if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE)\r
898         return;\r
899     n = 0;\r
900     b[n++] = IAC;\r
901     b[n++] = SB;\r
902     b[n++] = TELOPT_NAWS;\r
903     b[n++] = telnet->term_width >> 8;\r
904     if (b[n-1] == IAC) b[n++] = IAC;   /* duplicate any IAC byte occurs */\r
905     b[n++] = telnet->term_width & 0xFF;\r
906     if (b[n-1] == IAC) b[n++] = IAC;   /* duplicate any IAC byte occurs */\r
907     b[n++] = telnet->term_height >> 8;\r
908     if (b[n-1] == IAC) b[n++] = IAC;   /* duplicate any IAC byte occurs */\r
909     b[n++] = telnet->term_height & 0xFF;\r
910     if (b[n-1] == IAC) b[n++] = IAC;   /* duplicate any IAC byte occurs */\r
911     b[n++] = IAC;\r
912     b[n++] = SE;\r
913     telnet->bufsize = sk_write(telnet->s, (char *)b, n);\r
914     logbuf = dupprintf("client:\tSB NAWS %d,%d",\r
915                        telnet->term_width, telnet->term_height);\r
916     logevent(telnet->frontend, logbuf);\r
917     sfree(logbuf);\r
920 /*\r
921  * Send Telnet special codes.\r
922  */\r
923 static void telnet_special(void *handle, Telnet_Special code)\r
925     Telnet telnet = (Telnet) handle;\r
926     unsigned char b[2];\r
928     if (telnet->s == NULL)\r
929         return;\r
931     b[0] = IAC;\r
932     switch (code) {\r
933       case TS_AYT:\r
934         b[1] = AYT;\r
935         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
936         break;\r
937       case TS_BRK:\r
938         b[1] = BREAK;\r
939         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
940         break;\r
941       case TS_EC:\r
942         b[1] = EC;\r
943         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
944         break;\r
945       case TS_EL:\r
946         b[1] = EL;\r
947         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
948         break;\r
949       case TS_GA:\r
950         b[1] = GA;\r
951         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
952         break;\r
953       case TS_NOP:\r
954         b[1] = NOP;\r
955         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
956         break;\r
957       case TS_ABORT:\r
958         b[1] = ABORT;\r
959         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
960         break;\r
961       case TS_AO:\r
962         b[1] = AO;\r
963         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
964         break;\r
965       case TS_IP:\r
966         b[1] = IP;\r
967         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
968         break;\r
969       case TS_SUSP:\r
970         b[1] = SUSP;\r
971         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
972         break;\r
973       case TS_EOR:\r
974         b[1] = EOR;\r
975         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
976         break;\r
977       case TS_EOF:\r
978         b[1] = xEOF;\r
979         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
980         break;\r
981       case TS_EOL:\r
982         /* In BINARY mode, CR-LF becomes just CR -\r
983          * and without the NUL suffix too. */\r
984         if (telnet->opt_states[o_we_bin.index] == ACTIVE)\r
985             telnet->bufsize = sk_write(telnet->s, "\r", 1);\r
986         else\r
987             telnet->bufsize = sk_write(telnet->s, "\r\n", 2);\r
988         break;\r
989       case TS_SYNCH:\r
990         b[1] = DM;\r
991         telnet->bufsize = sk_write(telnet->s, (char *)b, 1);\r
992         telnet->bufsize = sk_write_oob(telnet->s, (char *)(b + 1), 1);\r
993         break;\r
994       case TS_RECHO:\r
995         if (telnet->opt_states[o_echo.index] == INACTIVE ||\r
996             telnet->opt_states[o_echo.index] == REALLY_INACTIVE) {\r
997             telnet->opt_states[o_echo.index] = REQUESTED;\r
998             send_opt(telnet, o_echo.send, o_echo.option);\r
999         }\r
1000         break;\r
1001       case TS_LECHO:\r
1002         if (telnet->opt_states[o_echo.index] == ACTIVE) {\r
1003             telnet->opt_states[o_echo.index] = REQUESTED;\r
1004             send_opt(telnet, o_echo.nsend, o_echo.option);\r
1005         }\r
1006         break;\r
1007       case TS_PING:\r
1008         if (telnet->opt_states[o_they_sga.index] == ACTIVE) {\r
1009             b[1] = NOP;\r
1010             telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
1011         }\r
1012         break;\r
1013       default:\r
1014         break;  /* never heard of it */\r
1015     }\r
1018 static const struct telnet_special *telnet_get_specials(void *handle)\r
1020     static const struct telnet_special specials[] = {\r
1021         {"Are You There", TS_AYT},\r
1022         {"Break", TS_BRK},\r
1023         {"Synch", TS_SYNCH},\r
1024         {"Erase Character", TS_EC},\r
1025         {"Erase Line", TS_EL},\r
1026         {"Go Ahead", TS_GA},\r
1027         {"No Operation", TS_NOP},\r
1028         {NULL, TS_SEP},\r
1029         {"Abort Process", TS_ABORT},\r
1030         {"Abort Output", TS_AO},\r
1031         {"Interrupt Process", TS_IP},\r
1032         {"Suspend Process", TS_SUSP},\r
1033         {NULL, TS_SEP},\r
1034         {"End Of Record", TS_EOR},\r
1035         {"End Of File", TS_EOF},\r
1036         {NULL, TS_EXITMENU}\r
1037     };\r
1038     return specials;\r
1041 static int telnet_connected(void *handle)\r
1043     Telnet telnet = (Telnet) handle;\r
1044     return telnet->s != NULL;\r
1047 static int telnet_sendok(void *handle)\r
1049     /* Telnet telnet = (Telnet) handle; */\r
1050     return 1;\r
1053 static void telnet_unthrottle(void *handle, int backlog)\r
1055     Telnet telnet = (Telnet) handle;\r
1056     sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);\r
1059 static int telnet_ldisc(void *handle, int option)\r
1061     Telnet telnet = (Telnet) handle;\r
1062     if (option == LD_ECHO)\r
1063         return telnet->echoing;\r
1064     if (option == LD_EDIT)\r
1065         return telnet->editing;\r
1066     return FALSE;\r
1069 static void telnet_provide_ldisc(void *handle, void *ldisc)\r
1071     Telnet telnet = (Telnet) handle;\r
1072     telnet->ldisc = ldisc;\r
1075 static void telnet_provide_logctx(void *handle, void *logctx)\r
1077     /* This is a stub. */\r
1080 static int telnet_exitcode(void *handle)\r
1082     Telnet telnet = (Telnet) handle;\r
1083     if (telnet->s != NULL)\r
1084         return -1;                     /* still connected */\r
1085     else if (telnet->closed_on_socket_error)\r
1086         return INT_MAX;     /* a socket error counts as an unclean exit */\r
1087     else\r
1088         /* Telnet doesn't transmit exit codes back to the client */\r
1089         return 0;\r
1092 /*\r
1093  * cfg_info for Telnet does nothing at all.\r
1094  */\r
1095 static int telnet_cfg_info(void *handle)\r
1097     return 0;\r
1100 Backend telnet_backend = {\r
1101     telnet_init,\r
1102     telnet_free,\r
1103     telnet_reconfig,\r
1104     telnet_send,\r
1105     telnet_sendbuffer,\r
1106     telnet_size,\r
1107     telnet_special,\r
1108     telnet_get_specials,\r
1109     telnet_connected,\r
1110     telnet_exitcode,\r
1111     telnet_sendok,\r
1112     telnet_ldisc,\r
1113     telnet_provide_ldisc,\r
1114     telnet_provide_logctx,\r
1115     telnet_unthrottle,\r
1116     telnet_cfg_info,\r
1117     NULL /* test_for_upstream */,\r
1118     "telnet",\r
1119     PROT_TELNET,\r
1120     23\r
1121 };\r