Initialize members
[TortoiseGit.git] / src / TortoisePlink / TELNET.C
blob0d630c721e3a6df85bf4124c8e45e4429326730f
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 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
201     enum {\r
202         TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,\r
203             SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR\r
204     } state;\r
206     Conf *conf;\r
208     Pinger pinger;\r
209 } *Telnet;\r
211 #define TELNET_MAX_BACKLOG 4096\r
213 #define SB_DELTA 1024\r
215 static void c_write(Telnet telnet, char *buf, int len)\r
217     int backlog;\r
218     backlog = from_backend(telnet->frontend, 0, buf, len);\r
219     sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);\r
222 static void log_option(Telnet telnet, char *sender, int cmd, int option)\r
224     char *buf;\r
225     /*\r
226      * The strange-looking "<?""?>" below is there to avoid a\r
227      * trigraph - a double question mark followed by > maps to a\r
228      * closing brace character!\r
229      */\r
230     buf = dupprintf("%s:\t%s %s", sender,\r
231                     (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" :\r
232                      cmd == DO ? "DO" : cmd == DONT ? "DONT" : "<?""?>"),\r
233                     telopt(option));\r
234     logevent(telnet->frontend, buf);\r
235     sfree(buf);\r
238 static void send_opt(Telnet telnet, int cmd, int option)\r
240     unsigned char b[3];\r
242     b[0] = IAC;\r
243     b[1] = cmd;\r
244     b[2] = option;\r
245     telnet->bufsize = sk_write(telnet->s, (char *)b, 3);\r
246     log_option(telnet, "client", cmd, option);\r
249 static void deactivate_option(Telnet telnet, const struct Opt *o)\r
251     if (telnet->opt_states[o->index] == REQUESTED ||\r
252         telnet->opt_states[o->index] == ACTIVE)\r
253         send_opt(telnet, o->nsend, o->option);\r
254     telnet->opt_states[o->index] = REALLY_INACTIVE;\r
257 /*\r
258  * Generate side effects of enabling or disabling an option.\r
259  */\r
260 static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled)\r
262     if (o->option == TELOPT_ECHO && o->send == DO)\r
263         telnet->echoing = !enabled;\r
264     else if (o->option == TELOPT_SGA && o->send == DO)\r
265         telnet->editing = !enabled;\r
266     if (telnet->ldisc)                 /* cause ldisc to notice the change */\r
267         ldisc_send(telnet->ldisc, NULL, 0, 0);\r
269     /* Ensure we get the minimum options */\r
270     if (!telnet->activated) {\r
271         if (telnet->opt_states[o_echo.index] == INACTIVE) {\r
272             telnet->opt_states[o_echo.index] = REQUESTED;\r
273             send_opt(telnet, o_echo.send, o_echo.option);\r
274         }\r
275         if (telnet->opt_states[o_we_sga.index] == INACTIVE) {\r
276             telnet->opt_states[o_we_sga.index] = REQUESTED;\r
277             send_opt(telnet, o_we_sga.send, o_we_sga.option);\r
278         }\r
279         if (telnet->opt_states[o_they_sga.index] == INACTIVE) {\r
280             telnet->opt_states[o_they_sga.index] = REQUESTED;\r
281             send_opt(telnet, o_they_sga.send, o_they_sga.option);\r
282         }\r
283         telnet->activated = TRUE;\r
284     }\r
287 static void activate_option(Telnet telnet, const struct Opt *o)\r
289     if (o->send == WILL && o->option == TELOPT_NAWS)\r
290         telnet_size(telnet, telnet->term_width, telnet->term_height);\r
291     if (o->send == WILL &&\r
292         (o->option == TELOPT_NEW_ENVIRON ||\r
293          o->option == TELOPT_OLD_ENVIRON)) {\r
294         /*\r
295          * We may only have one kind of ENVIRON going at a time.\r
296          * This is a hack, but who cares.\r
297          */\r
298         deactivate_option(telnet, o->option ==\r
299                           TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv);\r
300     }\r
301     option_side_effects(telnet, o, 1);\r
304 static void refused_option(Telnet telnet, const struct Opt *o)\r
306     if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&\r
307         telnet->opt_states[o_oenv.index] == INACTIVE) {\r
308         send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);\r
309         telnet->opt_states[o_oenv.index] = REQUESTED;\r
310     }\r
311     option_side_effects(telnet, o, 0);\r
314 static void proc_rec_opt(Telnet telnet, int cmd, int option)\r
316     const struct Opt *const *o;\r
318     log_option(telnet, "server", cmd, option);\r
319     for (o = opts; *o; o++) {\r
320         if ((*o)->option == option && (*o)->ack == cmd) {\r
321             switch (telnet->opt_states[(*o)->index]) {\r
322               case REQUESTED:\r
323                 telnet->opt_states[(*o)->index] = ACTIVE;\r
324                 activate_option(telnet, *o);\r
325                 break;\r
326               case ACTIVE:\r
327                 break;\r
328               case INACTIVE:\r
329                 telnet->opt_states[(*o)->index] = ACTIVE;\r
330                 send_opt(telnet, (*o)->send, option);\r
331                 activate_option(telnet, *o);\r
332                 break;\r
333               case REALLY_INACTIVE:\r
334                 send_opt(telnet, (*o)->nsend, option);\r
335                 break;\r
336             }\r
337             return;\r
338         } else if ((*o)->option == option && (*o)->nak == cmd) {\r
339             switch (telnet->opt_states[(*o)->index]) {\r
340               case REQUESTED:\r
341                 telnet->opt_states[(*o)->index] = INACTIVE;\r
342                 refused_option(telnet, *o);\r
343                 break;\r
344               case ACTIVE:\r
345                 telnet->opt_states[(*o)->index] = INACTIVE;\r
346                 send_opt(telnet, (*o)->nsend, option);\r
347                 option_side_effects(telnet, *o, 0);\r
348                 break;\r
349               case INACTIVE:\r
350               case REALLY_INACTIVE:\r
351                 break;\r
352             }\r
353             return;\r
354         }\r
355     }\r
356     /*\r
357      * If we reach here, the option was one we weren't prepared to\r
358      * cope with. If the request was positive (WILL or DO), we send\r
359      * a negative ack to indicate refusal. If the request was\r
360      * negative (WONT / DONT), we must do nothing.\r
361      */\r
362     if (cmd == WILL || cmd == DO)\r
363         send_opt(telnet, (cmd == WILL ? DONT : WONT), option);\r
366 static void process_subneg(Telnet telnet)\r
368     unsigned char *b, *p, *q;\r
369     int var, value, n, bsize;\r
370     char *e, *eval, *ekey, *user;\r
372     switch (telnet->sb_opt) {\r
373       case TELOPT_TSPEED:\r
374         if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {\r
375             char *logbuf;\r
376             char *termspeed = conf_get_str(telnet->conf, CONF_termspeed);\r
377             b = snewn(20 + strlen(termspeed), unsigned char);\r
378             b[0] = IAC;\r
379             b[1] = SB;\r
380             b[2] = TELOPT_TSPEED;\r
381             b[3] = TELQUAL_IS;\r
382             strcpy((char *)(b + 4), termspeed);\r
383             n = 4 + strlen(termspeed);\r
384             b[n] = IAC;\r
385             b[n + 1] = SE;\r
386             telnet->bufsize = sk_write(telnet->s, (char *)b, n + 2);\r
387             logevent(telnet->frontend, "server:\tSB TSPEED SEND");\r
388             logbuf = dupprintf("client:\tSB TSPEED IS %s", termspeed);\r
389             logevent(telnet->frontend, logbuf);\r
390             sfree(logbuf);\r
391             sfree(b);\r
392         } else\r
393             logevent(telnet->frontend, "server:\tSB TSPEED <something weird>");\r
394         break;\r
395       case TELOPT_TTYPE:\r
396         if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {\r
397             char *logbuf;\r
398             char *termtype = conf_get_str(telnet->conf, CONF_termtype);\r
399             b = snewn(20 + strlen(termtype), unsigned char);\r
400             b[0] = IAC;\r
401             b[1] = SB;\r
402             b[2] = TELOPT_TTYPE;\r
403             b[3] = TELQUAL_IS;\r
404             for (n = 0; termtype[n]; n++)\r
405                 b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ?\r
406                             termtype[n] + 'A' - 'a' :\r
407                             termtype[n]);\r
408             b[n + 4] = IAC;\r
409             b[n + 5] = SE;\r
410             telnet->bufsize = sk_write(telnet->s, (char *)b, n + 6);\r
411             b[n + 4] = 0;\r
412             logevent(telnet->frontend, "server:\tSB TTYPE SEND");\r
413             logbuf = dupprintf("client:\tSB TTYPE IS %s", b + 4);\r
414             logevent(telnet->frontend, logbuf);\r
415             sfree(logbuf);\r
416             sfree(b);\r
417         } else\r
418             logevent(telnet->frontend, "server:\tSB TTYPE <something weird>\r\n");\r
419         break;\r
420       case TELOPT_OLD_ENVIRON:\r
421       case TELOPT_NEW_ENVIRON:\r
422         p = telnet->sb_buf;\r
423         q = p + telnet->sb_len;\r
424         if (p < q && *p == TELQUAL_SEND) {\r
425             char *logbuf;\r
426             p++;\r
427             logbuf = dupprintf("server:\tSB %s SEND", telopt(telnet->sb_opt));\r
428             logevent(telnet->frontend, logbuf);\r
429             sfree(logbuf);\r
430             if (telnet->sb_opt == TELOPT_OLD_ENVIRON) {\r
431                 if (conf_get_int(telnet->conf, CONF_rfc_environ)) {\r
432                     value = RFC_VALUE;\r
433                     var = RFC_VAR;\r
434                 } else {\r
435                     value = BSD_VALUE;\r
436                     var = BSD_VAR;\r
437                 }\r
438                 /*\r
439                  * Try to guess the sense of VAR and VALUE.\r
440                  */\r
441                 while (p < q) {\r
442                     if (*p == RFC_VAR) {\r
443                         value = RFC_VALUE;\r
444                         var = RFC_VAR;\r
445                     } else if (*p == BSD_VAR) {\r
446                         value = BSD_VALUE;\r
447                         var = BSD_VAR;\r
448                     }\r
449                     p++;\r
450                 }\r
451             } else {\r
452                 /*\r
453                  * With NEW_ENVIRON, the sense of VAR and VALUE\r
454                  * isn't in doubt.\r
455                  */\r
456                 value = RFC_VALUE;\r
457                 var = RFC_VAR;\r
458             }\r
459             bsize = 20;\r
460             for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
461                                          NULL, &ekey);\r
462                  eval != NULL;\r
463                  eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
464                                          ekey, &ekey))\r
465                  bsize += strlen(ekey) + strlen(eval) + 2;\r
466             user = get_remote_username(telnet->conf);\r
467             if (user)\r
468                 bsize += 6 + strlen(user);\r
470             b = snewn(bsize, unsigned char);\r
471             b[0] = IAC;\r
472             b[1] = SB;\r
473             b[2] = telnet->sb_opt;\r
474             b[3] = TELQUAL_IS;\r
475             n = 4;\r
476             for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
477                                          NULL, &ekey);\r
478                  eval != NULL;\r
479                  eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
480                                          ekey, &ekey)) {\r
481                 b[n++] = var;\r
482                 for (e = ekey; *e; e++)\r
483                     b[n++] = *e;\r
484                 b[n++] = value;\r
485                 for (e = eval; *e; e++)\r
486                     b[n++] = *e;\r
487             }\r
488             if (user) {\r
489                 b[n++] = var;\r
490                 b[n++] = 'U';\r
491                 b[n++] = 'S';\r
492                 b[n++] = 'E';\r
493                 b[n++] = 'R';\r
494                 b[n++] = value;\r
495                 for (e = user; *e; e++)\r
496                     b[n++] = *e;\r
497             }\r
498             b[n++] = IAC;\r
499             b[n++] = SE;\r
500             telnet->bufsize = sk_write(telnet->s, (char *)b, n);\r
501             if (n == 6) {\r
502                 logbuf = dupprintf("client:\tSB %s IS <nothing>",\r
503                                    telopt(telnet->sb_opt));\r
504                 logevent(telnet->frontend, logbuf);\r
505                 sfree(logbuf);\r
506             } else {\r
507                 logbuf = dupprintf("client:\tSB %s IS:",\r
508                                    telopt(telnet->sb_opt));\r
509                 logevent(telnet->frontend, logbuf);\r
510                 sfree(logbuf);\r
511                 for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
512                                              NULL, &ekey);\r
513                      eval != NULL;\r
514                      eval = conf_get_str_strs(telnet->conf, CONF_environmt,\r
515                                              ekey, &ekey)) {\r
516                     logbuf = dupprintf("\t%s=%s", ekey, eval);\r
517                     logevent(telnet->frontend, logbuf);\r
518                     sfree(logbuf);\r
519                 }\r
520                 if (user) {\r
521                     logbuf = dupprintf("\tUSER=%s", user);\r
522                     logevent(telnet->frontend, logbuf);\r
523                     sfree(logbuf);\r
524                 }\r
525             }\r
526             sfree(b);\r
527             sfree(user);\r
528         }\r
529         break;\r
530     }\r
533 static void do_telnet_read(Telnet telnet, char *buf, int len)\r
535     char *outbuf = NULL;\r
536     int outbuflen = 0, outbufsize = 0;\r
538 #define ADDTOBUF(c) do { \\r
539     if (outbuflen >= outbufsize) { \\r
540         outbufsize = outbuflen + 256; \\r
541         outbuf = sresize(outbuf, outbufsize, char); \\r
542     } \\r
543     outbuf[outbuflen++] = (c); \\r
544 } while (0)\r
546     while (len--) {\r
547         int c = (unsigned char) *buf++;\r
549         switch (telnet->state) {\r
550           case TOP_LEVEL:\r
551           case SEENCR:\r
552             if (c == NUL && telnet->state == SEENCR)\r
553                 telnet->state = TOP_LEVEL;\r
554             else if (c == IAC)\r
555                 telnet->state = SEENIAC;\r
556             else {\r
557                 if (!telnet->in_synch)\r
558                     ADDTOBUF(c);\r
560 #if 1\r
561                 /* I can't get the F***ing winsock to insert the urgent IAC\r
562                  * into the right position! Even with SO_OOBINLINE it gives\r
563                  * it to recv too soon. And of course the DM byte (that\r
564                  * arrives in the same packet!) appears several K later!!\r
565                  *\r
566                  * Oh well, we do get the DM in the right place so I'll\r
567                  * just stop hiding on the next 0xf2 and hope for the best.\r
568                  */\r
569                 else if (c == DM)\r
570                     telnet->in_synch = 0;\r
571 #endif\r
572                 if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE)\r
573                     telnet->state = SEENCR;\r
574                 else\r
575                     telnet->state = TOP_LEVEL;\r
576             }\r
577             break;\r
578           case SEENIAC:\r
579             if (c == DO)\r
580                 telnet->state = SEENDO;\r
581             else if (c == DONT)\r
582                 telnet->state = SEENDONT;\r
583             else if (c == WILL)\r
584                 telnet->state = SEENWILL;\r
585             else if (c == WONT)\r
586                 telnet->state = SEENWONT;\r
587             else if (c == SB)\r
588                 telnet->state = SEENSB;\r
589             else if (c == DM) {\r
590                 telnet->in_synch = 0;\r
591                 telnet->state = TOP_LEVEL;\r
592             } else {\r
593                 /* ignore everything else; print it if it's IAC */\r
594                 if (c == IAC) {\r
595                     ADDTOBUF(c);\r
596                 }\r
597                 telnet->state = TOP_LEVEL;\r
598             }\r
599             break;\r
600           case SEENWILL:\r
601             proc_rec_opt(telnet, WILL, c);\r
602             telnet->state = TOP_LEVEL;\r
603             break;\r
604           case SEENWONT:\r
605             proc_rec_opt(telnet, WONT, c);\r
606             telnet->state = TOP_LEVEL;\r
607             break;\r
608           case SEENDO:\r
609             proc_rec_opt(telnet, DO, c);\r
610             telnet->state = TOP_LEVEL;\r
611             break;\r
612           case SEENDONT:\r
613             proc_rec_opt(telnet, DONT, c);\r
614             telnet->state = TOP_LEVEL;\r
615             break;\r
616           case SEENSB:\r
617             telnet->sb_opt = c;\r
618             telnet->sb_len = 0;\r
619             telnet->state = SUBNEGOT;\r
620             break;\r
621           case SUBNEGOT:\r
622             if (c == IAC)\r
623                 telnet->state = SUBNEG_IAC;\r
624             else {\r
625               subneg_addchar:\r
626                 if (telnet->sb_len >= telnet->sb_size) {\r
627                     telnet->sb_size += SB_DELTA;\r
628                     telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size,\r
629                                              unsigned char);\r
630                 }\r
631                 telnet->sb_buf[telnet->sb_len++] = c;\r
632                 telnet->state = SUBNEGOT;       /* in case we came here by goto */\r
633             }\r
634             break;\r
635           case SUBNEG_IAC:\r
636             if (c != SE)\r
637                 goto subneg_addchar;   /* yes, it's a hack, I know, but... */\r
638             else {\r
639                 process_subneg(telnet);\r
640                 telnet->state = TOP_LEVEL;\r
641             }\r
642             break;\r
643         }\r
644     }\r
646     if (outbuflen)\r
647         c_write(telnet, outbuf, outbuflen);\r
648     sfree(outbuf);\r
651 static void telnet_log(Plug plug, int type, SockAddr addr, int port,\r
652                        const char *error_msg, int error_code)\r
654     Telnet telnet = (Telnet) plug;\r
655     char addrbuf[256], *msg;\r
657     sk_getaddr(addr, addrbuf, lenof(addrbuf));\r
659     if (type == 0)\r
660         msg = dupprintf("Connecting to %s port %d", addrbuf, port);\r
661     else\r
662         msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);\r
664     logevent(telnet->frontend, msg);\r
665     sfree(msg);\r
668 static int telnet_closing(Plug plug, const char *error_msg, int error_code,\r
669                           int calling_back)\r
671     Telnet telnet = (Telnet) plug;\r
673     /*\r
674      * We don't implement independent EOF in each direction for Telnet\r
675      * connections; as soon as we get word that the remote side has\r
676      * sent us EOF, we wind up the whole connection.\r
677      */\r
679     if (telnet->s) {\r
680         sk_close(telnet->s);\r
681         telnet->s = NULL;\r
682         if (error_msg)\r
683             telnet->closed_on_socket_error = TRUE;\r
684         notify_remote_exit(telnet->frontend);\r
685     }\r
686     if (error_msg) {\r
687         logevent(telnet->frontend, error_msg);\r
688         connection_fatal(telnet->frontend, "%s", error_msg);\r
689     }\r
690     /* Otherwise, the remote side closed the connection normally. */\r
691     return 0;\r
694 static int telnet_receive(Plug plug, int urgent, char *data, int len)\r
696     Telnet telnet = (Telnet) plug;\r
697     if (urgent)\r
698         telnet->in_synch = TRUE;\r
699     do_telnet_read(telnet, data, len);\r
700     return 1;\r
703 static void telnet_sent(Plug plug, int bufsize)\r
705     Telnet telnet = (Telnet) plug;\r
706     telnet->bufsize = bufsize;\r
709 /*\r
710  * Called to set up the Telnet connection.\r
711  *\r
712  * Returns an error message, or NULL on success.\r
713  *\r
714  * Also places the canonical host name into `realhost'. It must be\r
715  * freed by the caller.\r
716  */\r
717 static const char *telnet_init(void *frontend_handle, void **backend_handle,\r
718                                Conf *conf, char *host, int port,\r
719                                char **realhost, int nodelay, int keepalive)\r
721     static const struct plug_function_table fn_table = {\r
722         telnet_log,\r
723         telnet_closing,\r
724         telnet_receive,\r
725         telnet_sent\r
726     };\r
727     SockAddr addr;\r
728     const char *err;\r
729     Telnet telnet;\r
730     char *loghost;\r
731     int addressfamily;\r
733     telnet = snew(struct telnet_tag);\r
734     telnet->fn = &fn_table;\r
735     telnet->conf = conf_copy(conf);\r
736     telnet->s = NULL;\r
737     telnet->closed_on_socket_error = FALSE;\r
738     telnet->echoing = TRUE;\r
739     telnet->editing = TRUE;\r
740     telnet->activated = FALSE;\r
741     telnet->sb_buf = NULL;\r
742     telnet->sb_size = 0;\r
743     telnet->frontend = frontend_handle;\r
744     telnet->term_width = conf_get_int(telnet->conf, CONF_width);\r
745     telnet->term_height = conf_get_int(telnet->conf, CONF_height);\r
746     telnet->state = TOP_LEVEL;\r
747     telnet->ldisc = NULL;\r
748     telnet->pinger = NULL;\r
749     *backend_handle = telnet;\r
751     /*\r
752      * Try to find host.\r
753      */\r
754     {\r
755         char *buf;\r
756         addressfamily = conf_get_int(telnet->conf, CONF_addressfamily);\r
757         buf = dupprintf("Looking up host \"%s\"%s", host,\r
758                         (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :\r
759                          (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :\r
760                           "")));\r
761         logevent(telnet->frontend, buf);\r
762         sfree(buf);\r
763     }\r
764     addr = name_lookup(host, port, realhost, telnet->conf, addressfamily);\r
765     if ((err = sk_addr_error(addr)) != NULL) {\r
766         sk_addr_free(addr);\r
767         return err;\r
768     }\r
770     if (port < 0)\r
771         port = 23;                     /* default telnet port */\r
773     /*\r
774      * Open socket.\r
775      */\r
776     telnet->s = new_connection(addr, *realhost, port, 0, 1,\r
777                                nodelay, keepalive, (Plug) telnet, telnet->conf);\r
778     if ((err = sk_socket_error(telnet->s)) != NULL)\r
779         return err;\r
781     telnet->pinger = pinger_new(telnet->conf, &telnet_backend, telnet);\r
783     /*\r
784      * Initialise option states.\r
785      */\r
786     if (conf_get_int(telnet->conf, CONF_passive_telnet)) {\r
787         const struct Opt *const *o;\r
789         for (o = opts; *o; o++)\r
790             telnet->opt_states[(*o)->index] = INACTIVE;\r
791     } else {\r
792         const struct Opt *const *o;\r
794         for (o = opts; *o; o++) {\r
795             telnet->opt_states[(*o)->index] = (*o)->initial_state;\r
796             if (telnet->opt_states[(*o)->index] == REQUESTED)\r
797                 send_opt(telnet, (*o)->send, (*o)->option);\r
798         }\r
799         telnet->activated = TRUE;\r
800     }\r
802     /*\r
803      * Set up SYNCH state.\r
804      */\r
805     telnet->in_synch = FALSE;\r
807     /*\r
808      * We can send special commands from the start.\r
809      */\r
810     update_specials_menu(telnet->frontend);\r
812     /*\r
813      * loghost overrides realhost, if specified.\r
814      */\r
815     loghost = conf_get_str(telnet->conf, CONF_loghost);\r
816     if (*loghost) {\r
817         char *colon;\r
819         sfree(*realhost);\r
820         *realhost = dupstr(loghost);\r
821         colon = strrchr(*realhost, ':');\r
822         if (colon) {\r
823             /*\r
824              * FIXME: if we ever update this aspect of ssh.c for\r
825              * IPv6 literal management, this should change in line\r
826              * with it.\r
827              */\r
828             *colon++ = '\0';\r
829         }\r
830     }\r
832     return NULL;\r
835 static void telnet_free(void *handle)\r
837     Telnet telnet = (Telnet) handle;\r
839     sfree(telnet->sb_buf);\r
840     if (telnet->s)\r
841         sk_close(telnet->s);\r
842     if (telnet->pinger)\r
843         pinger_free(telnet->pinger);\r
844     conf_free(telnet->conf);\r
845     sfree(telnet);\r
847 /*\r
848  * Reconfigure the Telnet backend. There's no immediate action\r
849  * necessary, in this backend: we just save the fresh config for\r
850  * any subsequent negotiations.\r
851  */\r
852 static void telnet_reconfig(void *handle, Conf *conf)\r
854     Telnet telnet = (Telnet) handle;\r
855     pinger_reconfig(telnet->pinger, telnet->conf, conf);\r
856     conf_free(telnet->conf);\r
857     telnet->conf = conf_copy(conf);\r
860 /*\r
861  * Called to send data down the Telnet connection.\r
862  */\r
863 static int telnet_send(void *handle, char *buf, int len)\r
865     Telnet telnet = (Telnet) handle;\r
866     unsigned char *p, *end;\r
867     static const unsigned char iac[2] = { IAC, IAC };\r
868     static const unsigned char cr[2] = { CR, NUL };\r
869 #if 0\r
870     static const unsigned char nl[2] = { CR, LF };\r
871 #endif\r
873     if (telnet->s == NULL)\r
874         return 0;\r
876     p = (unsigned char *)buf;\r
877     end = (unsigned char *)(buf + len);\r
878     while (p < end) {\r
879         unsigned char *q = p;\r
881         while (p < end && iswritable(*p))\r
882             p++;\r
883         telnet->bufsize = sk_write(telnet->s, (char *)q, p - q);\r
885         while (p < end && !iswritable(*p)) {\r
886             telnet->bufsize = \r
887                 sk_write(telnet->s, (char *)(*p == IAC ? iac : cr), 2);\r
888             p++;\r
889         }\r
890     }\r
892     return telnet->bufsize;\r
895 /*\r
896  * Called to query the current socket sendability status.\r
897  */\r
898 static int telnet_sendbuffer(void *handle)\r
900     Telnet telnet = (Telnet) handle;\r
901     return telnet->bufsize;\r
904 /*\r
905  * Called to set the size of the window from Telnet's POV.\r
906  */\r
907 static void telnet_size(void *handle, int width, int height)\r
909     Telnet telnet = (Telnet) handle;\r
910     unsigned char b[24];\r
911     int n;\r
912     char *logbuf;\r
914     telnet->term_width = width;\r
915     telnet->term_height = height;\r
917     if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE)\r
918         return;\r
919     n = 0;\r
920     b[n++] = IAC;\r
921     b[n++] = SB;\r
922     b[n++] = TELOPT_NAWS;\r
923     b[n++] = telnet->term_width >> 8;\r
924     if (b[n-1] == IAC) b[n++] = IAC;   /* duplicate any IAC byte occurs */\r
925     b[n++] = telnet->term_width & 0xFF;\r
926     if (b[n-1] == IAC) b[n++] = IAC;   /* duplicate any IAC byte occurs */\r
927     b[n++] = telnet->term_height >> 8;\r
928     if (b[n-1] == IAC) b[n++] = IAC;   /* duplicate any IAC byte occurs */\r
929     b[n++] = telnet->term_height & 0xFF;\r
930     if (b[n-1] == IAC) b[n++] = IAC;   /* duplicate any IAC byte occurs */\r
931     b[n++] = IAC;\r
932     b[n++] = SE;\r
933     telnet->bufsize = sk_write(telnet->s, (char *)b, n);\r
934     logbuf = dupprintf("client:\tSB NAWS %d,%d",\r
935                        telnet->term_width, telnet->term_height);\r
936     logevent(telnet->frontend, logbuf);\r
937     sfree(logbuf);\r
940 /*\r
941  * Send Telnet special codes.\r
942  */\r
943 static void telnet_special(void *handle, Telnet_Special code)\r
945     Telnet telnet = (Telnet) handle;\r
946     unsigned char b[2];\r
948     if (telnet->s == NULL)\r
949         return;\r
951     b[0] = IAC;\r
952     switch (code) {\r
953       case TS_AYT:\r
954         b[1] = AYT;\r
955         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
956         break;\r
957       case TS_BRK:\r
958         b[1] = BREAK;\r
959         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
960         break;\r
961       case TS_EC:\r
962         b[1] = EC;\r
963         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
964         break;\r
965       case TS_EL:\r
966         b[1] = EL;\r
967         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
968         break;\r
969       case TS_GA:\r
970         b[1] = GA;\r
971         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
972         break;\r
973       case TS_NOP:\r
974         b[1] = NOP;\r
975         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
976         break;\r
977       case TS_ABORT:\r
978         b[1] = ABORT;\r
979         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
980         break;\r
981       case TS_AO:\r
982         b[1] = AO;\r
983         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
984         break;\r
985       case TS_IP:\r
986         b[1] = IP;\r
987         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
988         break;\r
989       case TS_SUSP:\r
990         b[1] = SUSP;\r
991         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
992         break;\r
993       case TS_EOR:\r
994         b[1] = EOR;\r
995         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
996         break;\r
997       case TS_EOF:\r
998         b[1] = xEOF;\r
999         telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
1000         break;\r
1001       case TS_EOL:\r
1002         /* In BINARY mode, CR-LF becomes just CR -\r
1003          * and without the NUL suffix too. */\r
1004         if (telnet->opt_states[o_we_bin.index] == ACTIVE)\r
1005             telnet->bufsize = sk_write(telnet->s, "\r", 1);\r
1006         else\r
1007             telnet->bufsize = sk_write(telnet->s, "\r\n", 2);\r
1008         break;\r
1009       case TS_SYNCH:\r
1010         b[1] = DM;\r
1011         telnet->bufsize = sk_write(telnet->s, (char *)b, 1);\r
1012         telnet->bufsize = sk_write_oob(telnet->s, (char *)(b + 1), 1);\r
1013         break;\r
1014       case TS_RECHO:\r
1015         if (telnet->opt_states[o_echo.index] == INACTIVE ||\r
1016             telnet->opt_states[o_echo.index] == REALLY_INACTIVE) {\r
1017             telnet->opt_states[o_echo.index] = REQUESTED;\r
1018             send_opt(telnet, o_echo.send, o_echo.option);\r
1019         }\r
1020         break;\r
1021       case TS_LECHO:\r
1022         if (telnet->opt_states[o_echo.index] == ACTIVE) {\r
1023             telnet->opt_states[o_echo.index] = REQUESTED;\r
1024             send_opt(telnet, o_echo.nsend, o_echo.option);\r
1025         }\r
1026         break;\r
1027       case TS_PING:\r
1028         if (telnet->opt_states[o_they_sga.index] == ACTIVE) {\r
1029             b[1] = NOP;\r
1030             telnet->bufsize = sk_write(telnet->s, (char *)b, 2);\r
1031         }\r
1032         break;\r
1033       default:\r
1034         break;  /* never heard of it */\r
1035     }\r
1038 static const struct telnet_special *telnet_get_specials(void *handle)\r
1040     static const struct telnet_special specials[] = {\r
1041         {"Are You There", TS_AYT},\r
1042         {"Break", TS_BRK},\r
1043         {"Synch", TS_SYNCH},\r
1044         {"Erase Character", TS_EC},\r
1045         {"Erase Line", TS_EL},\r
1046         {"Go Ahead", TS_GA},\r
1047         {"No Operation", TS_NOP},\r
1048         {NULL, TS_SEP},\r
1049         {"Abort Process", TS_ABORT},\r
1050         {"Abort Output", TS_AO},\r
1051         {"Interrupt Process", TS_IP},\r
1052         {"Suspend Process", TS_SUSP},\r
1053         {NULL, TS_SEP},\r
1054         {"End Of Record", TS_EOR},\r
1055         {"End Of File", TS_EOF},\r
1056         {NULL, TS_EXITMENU}\r
1057     };\r
1058     return specials;\r
1061 static int telnet_connected(void *handle)\r
1063     Telnet telnet = (Telnet) handle;\r
1064     return telnet->s != NULL;\r
1067 static int telnet_sendok(void *handle)\r
1069     /* Telnet telnet = (Telnet) handle; */\r
1070     return 1;\r
1073 static void telnet_unthrottle(void *handle, int backlog)\r
1075     Telnet telnet = (Telnet) handle;\r
1076     sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);\r
1079 static int telnet_ldisc(void *handle, int option)\r
1081     Telnet telnet = (Telnet) handle;\r
1082     if (option == LD_ECHO)\r
1083         return telnet->echoing;\r
1084     if (option == LD_EDIT)\r
1085         return telnet->editing;\r
1086     return FALSE;\r
1089 static void telnet_provide_ldisc(void *handle, void *ldisc)\r
1091     Telnet telnet = (Telnet) handle;\r
1092     telnet->ldisc = ldisc;\r
1095 static void telnet_provide_logctx(void *handle, void *logctx)\r
1097     /* This is a stub. */\r
1100 static int telnet_exitcode(void *handle)\r
1102     Telnet telnet = (Telnet) handle;\r
1103     if (telnet->s != NULL)\r
1104         return -1;                     /* still connected */\r
1105     else if (telnet->closed_on_socket_error)\r
1106         return INT_MAX;     /* a socket error counts as an unclean exit */\r
1107     else\r
1108         /* Telnet doesn't transmit exit codes back to the client */\r
1109         return 0;\r
1112 /*\r
1113  * cfg_info for Telnet does nothing at all.\r
1114  */\r
1115 static int telnet_cfg_info(void *handle)\r
1117     return 0;\r
1120 Backend telnet_backend = {\r
1121     telnet_init,\r
1122     telnet_free,\r
1123     telnet_reconfig,\r
1124     telnet_send,\r
1125     telnet_sendbuffer,\r
1126     telnet_size,\r
1127     telnet_special,\r
1128     telnet_get_specials,\r
1129     telnet_connected,\r
1130     telnet_exitcode,\r
1131     telnet_sendok,\r
1132     telnet_ldisc,\r
1133     telnet_provide_ldisc,\r
1134     telnet_provide_logctx,\r
1135     telnet_unthrottle,\r
1136     telnet_cfg_info,\r
1137     "telnet",\r
1138     PROT_TELNET,\r
1139     23\r
1140 };\r