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