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