1 /*
2  * Simple Telnet server code, adapted from PuTTY's own Telnet
3  * client code for use as a Cygwin local pty proxy.
4  */
5 
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <stdbool.h>
9 #include <string.h>
10 
11 #include "sel.h"
12 #include "telnet.h"
13 #include "malloc.h"
14 #include "pty.h"
15 
16 #define IAC     255                    /* interpret as command: */
17 #define DONT    254                    /* you are not to use option */
18 #define DO      253                    /* please, you use option */
19 #define WONT    252                    /* I won't use option */
20 #define WILL    251                    /* I will use option */
21 #define SB      250                    /* interpret as subnegotiation */
22 #define SE      240                    /* end sub negotiation */
23 
24 #define GA      249                    /* you may reverse the line */
25 #define EL      248                    /* erase the current line */
26 #define EC      247                    /* erase the current character */
27 #define AYT     246                    /* are you there */
28 #define AO      245                    /* abort output--but let prog finish */
29 #define IP      244                    /* interrupt process--permanently */
30 #define BREAK   243                    /* break */
31 #define DM      242                    /* data mark--for connect. cleaning */
32 #define NOP     241                    /* nop */
33 #define EOR     239                    /* end of record (transparent mode) */
34 #define ABORT   238                    /* Abort process */
35 #define SUSP    237                    /* Suspend process */
36 #define xEOF    236                    /* End of file: EOF is already used... */
37 
38 #define TELOPTS(X) \
39     X(BINARY, 0)                       /* 8-bit data path */ \
40     X(ECHO, 1)                         /* echo */ \
41     X(RCP, 2)                          /* prepare to reconnect */ \
42     X(SGA, 3)                          /* suppress go ahead */ \
43     X(NAMS, 4)                         /* approximate message size */ \
44     X(STATUS, 5)                       /* give status */ \
45     X(TM, 6)                           /* timing mark */ \
46     X(RCTE, 7)                         /* remote controlled transmission and echo */ \
47     X(NAOL, 8)                         /* negotiate about output line width */ \
48     X(NAOP, 9)                         /* negotiate about output page size */ \
49     X(NAOCRD, 10)                      /* negotiate about CR disposition */ \
50     X(NAOHTS, 11)                      /* negotiate about horizontal tabstops */ \
51     X(NAOHTD, 12)                      /* negotiate about horizontal tab disposition */ \
52     X(NAOFFD, 13)                      /* negotiate about formfeed disposition */ \
53     X(NAOVTS, 14)                      /* negotiate about vertical tab stops */ \
54     X(NAOVTD, 15)                      /* negotiate about vertical tab disposition */ \
55     X(NAOLFD, 16)                      /* negotiate about output LF disposition */ \
56     X(XASCII, 17)                      /* extended ascic character set */ \
57     X(LOGOUT, 18)                      /* force logout */ \
58     X(BM, 19)                          /* byte macro */ \
59     X(DET, 20)                         /* data entry terminal */ \
60     X(SUPDUP, 21)                      /* supdup protocol */ \
61     X(SUPDUPOUTPUT, 22)                /* supdup output */ \
62     X(SNDLOC, 23)                      /* send location */ \
63     X(TTYPE, 24)                       /* terminal type */ \
64     X(EOR, 25)                         /* end or record */ \
65     X(TUID, 26)                        /* TACACS user identification */ \
66     X(OUTMRK, 27)                      /* output marking */ \
67     X(TTYLOC, 28)                      /* terminal location number */ \
68     X(3270REGIME, 29)                  /* 3270 regime */ \
69     X(X3PAD, 30)                       /* X.3 PAD */ \
70     X(NAWS, 31)                        /* window size */ \
71     X(TSPEED, 32)                      /* terminal speed */ \
72     X(LFLOW, 33)                       /* remote flow control */ \
73     X(LINEMODE, 34)                    /* Linemode option */ \
74     X(XDISPLOC, 35)                    /* X Display Location */ \
75     X(OLD_ENVIRON, 36)                 /* Old - Environment variables */ \
76     X(AUTHENTICATION, 37)              /* Authenticate */ \
77     X(ENCRYPT, 38)                     /* Encryption option */ \
78     X(NEW_ENVIRON, 39)                 /* New - Environment variables */ \
79     X(TN3270E, 40)                     /* TN3270 enhancements */ \
80     X(XAUTH, 41)                       \
81     X(CHARSET, 42)                     /* Character set */ \
82     X(RSP, 43)                         /* Remote serial port */ \
83     X(COM_PORT_OPTION, 44)             /* Com port control */ \
84     X(SLE, 45)                         /* Suppress local echo */ \
85     X(STARTTLS, 46)                    /* Start TLS */ \
86     X(KERMIT, 47)                      /* Automatic Kermit file transfer */ \
87     X(SEND_URL, 48)                    \
88     X(FORWARD_X, 49)                   \
89     X(PRAGMA_LOGON, 138)               \
90     X(SSPI_LOGON, 139)                 \
91     X(PRAGMA_HEARTBEAT, 140)           \
92     X(EXOPL, 255)                      /* extended-options-list */
93 
94 #define telnet_enum(x,y) TELOPT_##x = y,
95 enum { TELOPTS(telnet_enum) dummy=0 };
96 #undef telnet_enum
97 
98 #define TELQUAL_IS      0              /* option is... */
99 #define TELQUAL_SEND    1              /* send option */
100 #define TELQUAL_INFO    2              /* ENVIRON: informational version of IS */
101 #define BSD_VAR 1
102 #define BSD_VALUE 0
103 #define RFC_VAR 0
104 #define RFC_VALUE 1
105 
106 #define CR 13
107 #define LF 10
108 #define NUL 0
109 
110 #define iswritable(x) ( (x) != IAC && (x) != CR )
111 
telopt(int opt)112 static char *telopt(int opt)
113 {
114 #define telnet_str(x,y) case TELOPT_##x: return #x;
115     switch (opt) {
116         TELOPTS(telnet_str)
117       default:
118         return "<unknown>";
119     }
120 #undef telnet_str
121 }
122 
123 static void telnet_size(void *handle, int width, int height);
124 
125 struct Opt {
126     int send;                          /* what we initially send */
127     int nsend;                         /* -ve send if requested to stop it */
128     int ack, nak;                      /* +ve and -ve acknowledgements */
129     int option;                        /* the option code */
130     int index;                         /* index into telnet->opt_states[] */
131     enum {
132         REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE
133     } initial_state;
134 };
135 
136 enum {
137     OPTINDEX_NAWS,
138     OPTINDEX_TSPEED,
139     OPTINDEX_TTYPE,
140     OPTINDEX_OENV,
141     OPTINDEX_NENV,
142     OPTINDEX_ECHO,
143     OPTINDEX_WE_SGA,
144     OPTINDEX_THEY_SGA,
145     OPTINDEX_WE_BIN,
146     OPTINDEX_THEY_BIN,
147     NUM_OPTS
148 };
149 
150 static const struct Opt o_naws =
151     { DO, DONT, WILL, WONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED };
152 static const struct Opt o_ttype =
153     { DO, DONT, WILL, WONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED };
154 static const struct Opt o_oenv =
155     { DO, DONT, WILL, WONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE };
156 static const struct Opt o_nenv =
157     { DO, DONT, WILL, WONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED };
158 static const struct Opt o_echo =
159     { WILL, WONT, DO, DONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED };
160 static const struct Opt o_they_sga =
161     { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED };
162 static const struct Opt o_we_sga =
163     { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED };
164 
165 static const struct Opt *const opts[] = {
166     &o_echo, &o_we_sga, &o_they_sga, &o_naws, &o_ttype, &o_oenv, &o_nenv, NULL
167 };
168 
169 struct Telnet {
170     int opt_states[NUM_OPTS];
171 
172     int sb_opt, sb_len;
173     unsigned char *sb_buf;
174     int sb_size;
175 
176     enum {
177         TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
178             SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR
179     } state;
180 
181     sel_wfd *net, *pty;
182 
183     /*
184      * Options we must finish processing before launching the shell
185      */
186     int old_environ_done, new_environ_done, ttype_done;
187 
188     /*
189      * Ready to start shell?
190      */
191     int shell_ok;
192     int envvarsize;
193     struct shell_data shdata;
194 };
195 
196 #define TELNET_MAX_BACKLOG 4096
197 
198 #define SB_DELTA 1024
199 
send_opt(Telnet * telnet,int cmd,int option)200 static void send_opt(Telnet *telnet, int cmd, int option)
201 {
202     unsigned char b[3];
203 
204     b[0] = IAC;
205     b[1] = cmd;
206     b[2] = option;
207     sel_write(telnet->net, b, 3);
208 }
209 
deactivate_option(Telnet * telnet,const struct Opt * o)210 static void deactivate_option(Telnet *telnet, const struct Opt *o)
211 {
212     if (telnet->opt_states[o->index] == REQUESTED ||
213         telnet->opt_states[o->index] == ACTIVE)
214         send_opt(telnet, o->nsend, o->option);
215     telnet->opt_states[o->index] = REALLY_INACTIVE;
216 }
217 
218 /*
219  * Generate side effects of enabling or disabling an option.
220  */
option_side_effects(Telnet * telnet,const struct Opt * o,int enabled)221 static void option_side_effects(Telnet *telnet, const struct Opt *o, int enabled)
222 {
223 }
224 
activate_option(Telnet * telnet,const struct Opt * o)225 static void activate_option(Telnet *telnet, const struct Opt *o)
226 {
227     if (o->option == TELOPT_NEW_ENVIRON ||
228         o->option == TELOPT_OLD_ENVIRON ||
229         o->option == TELOPT_TTYPE) {
230         char buf[6];
231         buf[0] = IAC;
232         buf[1] = SB;
233         buf[2] = o->option;
234         buf[3] = TELQUAL_SEND;
235         buf[4] = IAC;
236         buf[5] = SE;
237         sel_write(telnet->net, buf, 6);
238     }
239     option_side_effects(telnet, o, 1);
240 }
241 
done_option(Telnet * telnet,int option)242 static void done_option(Telnet *telnet, int option)
243 {
244     if (option == TELOPT_OLD_ENVIRON)
245         telnet->old_environ_done = 1;
246     else if (option == TELOPT_NEW_ENVIRON)
247         telnet->new_environ_done = 1;
248     else if (option == TELOPT_TTYPE)
249         telnet->ttype_done = 1;
250 
251     if (telnet->old_environ_done && telnet->new_environ_done &&
252         telnet->ttype_done) {
253         telnet->shell_ok = 1;
254     }
255 }
256 
refused_option(Telnet * telnet,const struct Opt * o)257 static void refused_option(Telnet *telnet, const struct Opt *o)
258 {
259     done_option(telnet, o->option);
260     if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&
261         telnet->opt_states[o_oenv.index] == INACTIVE) {
262         send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);
263         telnet->opt_states[o_oenv.index] = REQUESTED;
264         telnet->old_environ_done = 0;
265     }
266     option_side_effects(telnet, o, 0);
267 }
268 
proc_rec_opt(Telnet * telnet,int cmd,int option)269 static void proc_rec_opt(Telnet *telnet, int cmd, int option)
270 {
271     const struct Opt *const *o;
272 
273     for (o = opts; *o; o++) {
274         if ((*o)->option == option && (*o)->ack == cmd) {
275             switch (telnet->opt_states[(*o)->index]) {
276               case REQUESTED:
277                 telnet->opt_states[(*o)->index] = ACTIVE;
278                 activate_option(telnet, *o);
279                 break;
280               case ACTIVE:
281                 break;
282               case INACTIVE:
283                 telnet->opt_states[(*o)->index] = ACTIVE;
284                 send_opt(telnet, (*o)->send, option);
285                 activate_option(telnet, *o);
286                 break;
287               case REALLY_INACTIVE:
288                 send_opt(telnet, (*o)->nsend, option);
289                 break;
290             }
291             return;
292         } else if ((*o)->option == option && (*o)->nak == cmd) {
293             switch (telnet->opt_states[(*o)->index]) {
294               case REQUESTED:
295                 telnet->opt_states[(*o)->index] = INACTIVE;
296                 refused_option(telnet, *o);
297                 break;
298               case ACTIVE:
299                 telnet->opt_states[(*o)->index] = INACTIVE;
300                 send_opt(telnet, (*o)->nsend, option);
301                 option_side_effects(telnet, *o, 0);
302                 break;
303               case INACTIVE:
304               case REALLY_INACTIVE:
305                 break;
306             }
307             return;
308         }
309     }
310     /*
311      * If we reach here, the option was one we weren't prepared to
312      * cope with. If the request was positive (WILL or DO), we send
313      * a negative ack to indicate refusal. If the request was
314      * negative (WONT / DONT), we must do nothing.
315      */
316     if (cmd == WILL || cmd == DO)
317         send_opt(telnet, (cmd == WILL ? DONT : WONT), option);
318 }
319 
process_subneg(Telnet * telnet)320 static void process_subneg(Telnet *telnet)
321 {
322     int var, value, n;
323 
324     switch (telnet->sb_opt) {
325       case TELOPT_OLD_ENVIRON:
326       case TELOPT_NEW_ENVIRON:
327         if (telnet->sb_buf[0] == TELQUAL_IS) {
328             if (telnet->sb_opt == TELOPT_NEW_ENVIRON) {
329                 var = RFC_VAR;
330                 value = RFC_VALUE;
331             } else {
332                 if (telnet->sb_len > 1 && !(telnet->sb_buf[0] &~ 1)) {
333                     var = telnet->sb_buf[0];
334                     value = BSD_VAR ^ BSD_VALUE ^ var;
335                 } else {
336                     var = BSD_VAR;
337                     value = BSD_VALUE;
338                 }
339             }
340         }
341         n = 1;
342         while (n < telnet->sb_len && telnet->sb_buf[n] == var) {
343             int varpos, varlen, valpos, vallen;
344             char *result;
345 
346             varpos = ++n;
347             while (n < telnet->sb_len && telnet->sb_buf[n] != value)
348                 n++;
349             if (n == telnet->sb_len)
350                 break;
351             varlen = n - varpos;
352             valpos = ++n;
353             while (n < telnet->sb_len && telnet->sb_buf[n] != var)
354                 n++;
355             vallen = n - valpos;
356 
357             result = snewn(varlen + vallen + 2, char);
358             sprintf(result, "%.*s=%.*s",
359                     varlen, telnet->sb_buf+varpos,
360                     vallen, telnet->sb_buf+valpos);
361             if (telnet->shdata.nenvvars >= telnet->envvarsize) {
362                 telnet->envvarsize = telnet->shdata.nenvvars * 3 / 2 + 16;
363                 telnet->shdata.envvars = sresize(telnet->shdata.envvars,
364                                                  telnet->envvarsize, char *);
365             }
366             telnet->shdata.envvars[telnet->shdata.nenvvars++] = result;
367         }
368         done_option(telnet, telnet->sb_opt);
369         break;
370       case TELOPT_TTYPE:
371         if (telnet->sb_len >= 1 && telnet->sb_buf[0] == TELQUAL_IS) {
372             telnet->shdata.termtype = snewn(5 + telnet->sb_len, char);
373             strcpy(telnet->shdata.termtype, "TERM=");
374             for (n = 0; n < telnet->sb_len-1; n++) {
375                 char c = telnet->sb_buf[n+1];
376                 if (c >= 'A' && c <= 'Z')
377                     c = c + 'a' - 'A';
378                 telnet->shdata.termtype[n+5] = c;
379             }
380             telnet->shdata.termtype[telnet->sb_len+5-1] = '\0';
381         }
382         done_option(telnet, telnet->sb_opt);
383         break;
384       case TELOPT_NAWS:
385         if (telnet->sb_len == 4) {
386             int w, h;
387             w = (unsigned char)telnet->sb_buf[0];
388             w = (w << 8) | (unsigned char)telnet->sb_buf[1];
389             h = (unsigned char)telnet->sb_buf[2];
390             h = (h << 8) | (unsigned char)telnet->sb_buf[3];
391             pty_resize(w, h);
392         }
393         break;
394     }
395 }
396 
telnet_from_net(Telnet * telnet,char * buf,int len)397 void telnet_from_net(Telnet *telnet, char *buf, int len)
398 {
399     while (len--) {
400         int c = (unsigned char) *buf++;
401 
402         switch (telnet->state) {
403           case TOP_LEVEL:
404           case SEENCR:
405             /*
406              * PuTTY sends Telnet's new line sequence (CR LF on
407              * the wire) in response to the return key. We must
408              * therefore treat that as equivalent to CR NUL, and
409              * send CR to the pty.
410              */
411             if ((c == NUL || c == '\n') && telnet->state == SEENCR)
412                 telnet->state = TOP_LEVEL;
413             else if (c == IAC)
414                 telnet->state = SEENIAC;
415             else {
416                 char cc = c;
417                 sel_write(telnet->pty, &cc, 1);
418 
419                 if (c == CR)
420                     telnet->state = SEENCR;
421                 else
422                     telnet->state = TOP_LEVEL;
423             }
424             break;
425           case SEENIAC:
426             if (c == DO)
427                 telnet->state = SEENDO;
428             else if (c == DONT)
429                 telnet->state = SEENDONT;
430             else if (c == WILL)
431                 telnet->state = SEENWILL;
432             else if (c == WONT)
433                 telnet->state = SEENWONT;
434             else if (c == SB)
435                 telnet->state = SEENSB;
436             else if (c == DM)
437                 telnet->state = TOP_LEVEL;
438             else {
439                 /* ignore everything else; print it if it's IAC */
440                 if (c == IAC) {
441                     char cc = c;
442                     sel_write(telnet->pty, &cc, 1);
443                 }
444                 telnet->state = TOP_LEVEL;
445             }
446             break;
447           case SEENWILL:
448             proc_rec_opt(telnet, WILL, c);
449             telnet->state = TOP_LEVEL;
450             break;
451           case SEENWONT:
452             proc_rec_opt(telnet, WONT, c);
453             telnet->state = TOP_LEVEL;
454             break;
455           case SEENDO:
456             proc_rec_opt(telnet, DO, c);
457             telnet->state = TOP_LEVEL;
458             break;
459           case SEENDONT:
460             proc_rec_opt(telnet, DONT, c);
461             telnet->state = TOP_LEVEL;
462             break;
463           case SEENSB:
464             telnet->sb_opt = c;
465             telnet->sb_len = 0;
466             telnet->state = SUBNEGOT;
467             break;
468           case SUBNEGOT:
469             if (c == IAC)
470                 telnet->state = SUBNEG_IAC;
471             else {
472               subneg_addchar:
473                 if (telnet->sb_len >= telnet->sb_size) {
474                     telnet->sb_size += SB_DELTA;
475                     telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size,
476                                              unsigned char);
477                 }
478                 telnet->sb_buf[telnet->sb_len++] = c;
479                 telnet->state = SUBNEGOT;       /* in case we came here by goto */
480             }
481             break;
482           case SUBNEG_IAC:
483             if (c != SE)
484                 goto subneg_addchar;   /* yes, it's a hack, I know, but... */
485             else {
486                 process_subneg(telnet);
487                 telnet->state = TOP_LEVEL;
488             }
489             break;
490         }
491     }
492 }
493 
telnet_new(sel_wfd * net,sel_wfd * pty)494 Telnet *telnet_new(sel_wfd *net, sel_wfd *pty)
495 {
496     Telnet *telnet;
497 
498     telnet = snew(Telnet);
499     telnet->sb_buf = NULL;
500     telnet->sb_size = 0;
501     telnet->state = TOP_LEVEL;
502     telnet->net = net;
503     telnet->pty = pty;
504     telnet->shdata.envvars = NULL;
505     telnet->shdata.nenvvars = telnet->envvarsize = 0;
506     telnet->shdata.termtype = NULL;
507 
508     /*
509      * Initialise option states.
510      */
511     {
512         const struct Opt *const *o;
513 
514         for (o = opts; *o; o++) {
515             telnet->opt_states[(*o)->index] = (*o)->initial_state;
516             if (telnet->opt_states[(*o)->index] == REQUESTED)
517                 send_opt(telnet, (*o)->send, (*o)->option);
518         }
519     }
520 
521     telnet->old_environ_done = 1;      /* initially don't want to bother */
522     telnet->new_environ_done = 0;
523     telnet->ttype_done = 0;
524     telnet->shell_ok = 0;
525 
526     return telnet;
527 }
528 
telnet_free(Telnet * telnet)529 void telnet_free(Telnet *telnet)
530 {
531     sfree(telnet->sb_buf);
532     sfree(telnet);
533 }
534 
telnet_from_pty(Telnet * telnet,char * buf,int len)535 void telnet_from_pty(Telnet *telnet, char *buf, int len)
536 {
537     unsigned char *p, *end;
538     static const unsigned char iac[2] = { IAC, IAC };
539     static const unsigned char cr[2] = { CR, NUL };
540 #if 0
541     static const unsigned char nl[2] = { CR, LF };
542 #endif
543 
544     p = (unsigned char *)buf;
545     end = (unsigned char *)(buf + len);
546     while (p < end) {
547         unsigned char *q = p;
548 
549         while (p < end && iswritable(*p))
550             p++;
551         sel_write(telnet->net, q, p - q);
552 
553         while (p < end && !iswritable(*p)) {
554             sel_write(telnet->net, *p == IAC ? iac : cr, 2);
555             p++;
556         }
557     }
558 }
559 
telnet_shell_ok(Telnet * telnet,struct shell_data * shdata)560 int telnet_shell_ok(Telnet *telnet, struct shell_data *shdata)
561 {
562     if (telnet->shell_ok)
563         *shdata = telnet->shdata;      /* structure copy */
564     return telnet->shell_ok;
565 }
566