Contents of /tags/mkinitrd-6_3_3/busybox/networking/telnet.c
Parent Directory | Revision Log
Revision 1182 -
(show annotations)
(download)
Wed Dec 15 21:43:57 2010 UTC (13 years, 9 months ago) by niro
File MIME type: text/plain
File size: 12383 byte(s)
Wed Dec 15 21:43:57 2010 UTC (13 years, 9 months ago) by niro
File MIME type: text/plain
File size: 12383 byte(s)
tagged 'mkinitrd-6_3_3'
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * telnet implementation for busybox |
4 | * |
5 | * Author: Tomi Ollila <too@iki.fi> |
6 | * Copyright (C) 1994-2000 by Tomi Ollila |
7 | * |
8 | * Created: Thu Apr 7 13:29:41 1994 too |
9 | * Last modified: Fri Jun 9 14:34:24 2000 too |
10 | * |
11 | * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. |
12 | * |
13 | * HISTORY |
14 | * Revision 3.1 1994/04/17 11:31:54 too |
15 | * initial revision |
16 | * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen <andersen@codepoet.org> |
17 | * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan |
18 | * <jam@ltsp.org> |
19 | * Modified 2004/02/11 to add ability to pass the USER variable to remote host |
20 | * by Fernando Silveira <swrh@gmx.net> |
21 | * |
22 | */ |
23 | |
24 | #include <arpa/telnet.h> |
25 | #include <netinet/in.h> |
26 | #include "libbb.h" |
27 | |
28 | #ifdef DOTRACE |
29 | #define TRACE(x, y) do { if (x) printf y; } while (0) |
30 | #else |
31 | #define TRACE(x, y) |
32 | #endif |
33 | |
34 | enum { |
35 | DATABUFSIZE = 128, |
36 | IACBUFSIZE = 128, |
37 | |
38 | CHM_TRY = 0, |
39 | CHM_ON = 1, |
40 | CHM_OFF = 2, |
41 | |
42 | UF_ECHO = 0x01, |
43 | UF_SGA = 0x02, |
44 | |
45 | TS_0 = 1, |
46 | TS_IAC = 2, |
47 | TS_OPT = 3, |
48 | TS_SUB1 = 4, |
49 | TS_SUB2 = 5, |
50 | }; |
51 | |
52 | typedef unsigned char byte; |
53 | |
54 | enum { netfd = 3 }; |
55 | |
56 | struct globals { |
57 | int iaclen; /* could even use byte, but it's a loss on x86 */ |
58 | byte telstate; /* telnet negotiation state from network input */ |
59 | byte telwish; /* DO, DONT, WILL, WONT */ |
60 | byte charmode; |
61 | byte telflags; |
62 | byte do_termios; |
63 | #if ENABLE_FEATURE_TELNET_TTYPE |
64 | char *ttype; |
65 | #endif |
66 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
67 | const char *autologin; |
68 | #endif |
69 | #if ENABLE_FEATURE_AUTOWIDTH |
70 | unsigned win_width, win_height; |
71 | #endif |
72 | /* same buffer used both for network and console read/write */ |
73 | char buf[DATABUFSIZE]; |
74 | /* buffer to handle telnet negotiations */ |
75 | char iacbuf[IACBUFSIZE]; |
76 | struct termios termios_def; |
77 | struct termios termios_raw; |
78 | } FIX_ALIASING; |
79 | #define G (*(struct globals*)&bb_common_bufsiz1) |
80 | #define INIT_G() do { \ |
81 | struct G_sizecheck { \ |
82 | char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \ |
83 | }; \ |
84 | } while (0) |
85 | |
86 | /* Function prototypes */ |
87 | static void rawmode(void); |
88 | static void cookmode(void); |
89 | static void do_linemode(void); |
90 | static void will_charmode(void); |
91 | static void telopt(byte c); |
92 | static int subneg(byte c); |
93 | |
94 | static void iac_flush(void) |
95 | { |
96 | write(netfd, G.iacbuf, G.iaclen); |
97 | G.iaclen = 0; |
98 | } |
99 | |
100 | #define write_str(fd, str) write(fd, str, sizeof(str) - 1) |
101 | |
102 | static void doexit(int ev) NORETURN; |
103 | static void doexit(int ev) |
104 | { |
105 | cookmode(); |
106 | exit(ev); |
107 | } |
108 | |
109 | static void con_escape(void) |
110 | { |
111 | char b; |
112 | |
113 | if (bb_got_signal) /* came from line mode... go raw */ |
114 | rawmode(); |
115 | |
116 | write_str(1, "\r\nConsole escape. Commands are:\r\n\n" |
117 | " l go to line mode\r\n" |
118 | " c go to character mode\r\n" |
119 | " z suspend telnet\r\n" |
120 | " e exit telnet\r\n"); |
121 | |
122 | if (read(STDIN_FILENO, &b, 1) <= 0) |
123 | doexit(EXIT_FAILURE); |
124 | |
125 | switch (b) { |
126 | case 'l': |
127 | if (!bb_got_signal) { |
128 | do_linemode(); |
129 | goto ret; |
130 | } |
131 | break; |
132 | case 'c': |
133 | if (bb_got_signal) { |
134 | will_charmode(); |
135 | goto ret; |
136 | } |
137 | break; |
138 | case 'z': |
139 | cookmode(); |
140 | kill(0, SIGTSTP); |
141 | rawmode(); |
142 | break; |
143 | case 'e': |
144 | doexit(EXIT_SUCCESS); |
145 | } |
146 | |
147 | write_str(1, "continuing...\r\n"); |
148 | |
149 | if (bb_got_signal) |
150 | cookmode(); |
151 | ret: |
152 | bb_got_signal = 0; |
153 | |
154 | } |
155 | |
156 | static void handle_net_output(int len) |
157 | { |
158 | /* here we could do smart tricks how to handle 0xFF:s in output |
159 | * stream like writing twice every sequence of FF:s (thus doing |
160 | * many write()s. But I think interactive telnet application does |
161 | * not need to be 100% 8-bit clean, so changing every 0xff:s to |
162 | * 0x7f:s |
163 | * |
164 | * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com) |
165 | * I don't agree. |
166 | * first - I cannot use programs like sz/rz |
167 | * second - the 0x0D is sent as one character and if the next |
168 | * char is 0x0A then it's eaten by a server side. |
169 | * third - why do you have to make 'many write()s'? |
170 | * I don't understand. |
171 | * So I implemented it. It's really useful for me. I hope that |
172 | * other people will find it interesting too. |
173 | */ |
174 | |
175 | int i, j; |
176 | byte *p = (byte*)G.buf; |
177 | byte outbuf[4*DATABUFSIZE]; |
178 | |
179 | for (i = len, j = 0; i > 0; i--, p++) { |
180 | if (*p == 0x1d) { |
181 | con_escape(); |
182 | return; |
183 | } |
184 | outbuf[j++] = *p; |
185 | if (*p == 0xff) |
186 | outbuf[j++] = 0xff; |
187 | else if (*p == 0x0d) |
188 | outbuf[j++] = 0x00; |
189 | } |
190 | if (j > 0) |
191 | write(netfd, outbuf, j); |
192 | } |
193 | |
194 | static void handle_net_input(int len) |
195 | { |
196 | int i; |
197 | int cstart = 0; |
198 | |
199 | for (i = 0; i < len; i++) { |
200 | byte c = G.buf[i]; |
201 | |
202 | if (G.telstate == 0) { /* most of the time state == 0 */ |
203 | if (c == IAC) { |
204 | cstart = i; |
205 | G.telstate = TS_IAC; |
206 | } |
207 | continue; |
208 | } |
209 | switch (G.telstate) { |
210 | case TS_0: |
211 | if (c == IAC) |
212 | G.telstate = TS_IAC; |
213 | else |
214 | G.buf[cstart++] = c; |
215 | break; |
216 | |
217 | case TS_IAC: |
218 | if (c == IAC) { /* IAC IAC -> 0xFF */ |
219 | G.buf[cstart++] = c; |
220 | G.telstate = TS_0; |
221 | break; |
222 | } |
223 | /* else */ |
224 | switch (c) { |
225 | case SB: |
226 | G.telstate = TS_SUB1; |
227 | break; |
228 | case DO: |
229 | case DONT: |
230 | case WILL: |
231 | case WONT: |
232 | G.telwish = c; |
233 | G.telstate = TS_OPT; |
234 | break; |
235 | default: |
236 | G.telstate = TS_0; /* DATA MARK must be added later */ |
237 | } |
238 | break; |
239 | case TS_OPT: /* WILL, WONT, DO, DONT */ |
240 | telopt(c); |
241 | G.telstate = TS_0; |
242 | break; |
243 | case TS_SUB1: /* Subnegotiation */ |
244 | case TS_SUB2: /* Subnegotiation */ |
245 | if (subneg(c)) |
246 | G.telstate = TS_0; |
247 | break; |
248 | } |
249 | } |
250 | if (G.telstate) { |
251 | if (G.iaclen) |
252 | iac_flush(); |
253 | if (G.telstate == TS_0) |
254 | G.telstate = 0; |
255 | len = cstart; |
256 | } |
257 | |
258 | if (len) |
259 | write(STDOUT_FILENO, G.buf, len); |
260 | } |
261 | |
262 | static void put_iac(int c) |
263 | { |
264 | G.iacbuf[G.iaclen++] = c; |
265 | } |
266 | |
267 | static void put_iac2(byte wwdd, byte c) |
268 | { |
269 | if (G.iaclen + 3 > IACBUFSIZE) |
270 | iac_flush(); |
271 | |
272 | put_iac(IAC); |
273 | put_iac(wwdd); |
274 | put_iac(c); |
275 | } |
276 | |
277 | #if ENABLE_FEATURE_TELNET_TTYPE |
278 | static void put_iac_subopt(byte c, char *str) |
279 | { |
280 | int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) |
281 | |
282 | if (G.iaclen + len > IACBUFSIZE) |
283 | iac_flush(); |
284 | |
285 | put_iac(IAC); |
286 | put_iac(SB); |
287 | put_iac(c); |
288 | put_iac(0); |
289 | |
290 | while (*str) |
291 | put_iac(*str++); |
292 | |
293 | put_iac(IAC); |
294 | put_iac(SE); |
295 | } |
296 | #endif |
297 | |
298 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
299 | static void put_iac_subopt_autologin(void) |
300 | { |
301 | int len = strlen(G.autologin) + 6; // (2 + 1 + 1 + strlen + 2) |
302 | const char *p = "USER"; |
303 | |
304 | if (G.iaclen + len > IACBUFSIZE) |
305 | iac_flush(); |
306 | |
307 | put_iac(IAC); |
308 | put_iac(SB); |
309 | put_iac(TELOPT_NEW_ENVIRON); |
310 | put_iac(TELQUAL_IS); |
311 | put_iac(NEW_ENV_VAR); |
312 | |
313 | while (*p) |
314 | put_iac(*p++); |
315 | |
316 | put_iac(NEW_ENV_VALUE); |
317 | |
318 | p = G.autologin; |
319 | while (*p) |
320 | put_iac(*p++); |
321 | |
322 | put_iac(IAC); |
323 | put_iac(SE); |
324 | } |
325 | #endif |
326 | |
327 | #if ENABLE_FEATURE_AUTOWIDTH |
328 | static void put_iac_naws(byte c, int x, int y) |
329 | { |
330 | if (G.iaclen + 9 > IACBUFSIZE) |
331 | iac_flush(); |
332 | |
333 | put_iac(IAC); |
334 | put_iac(SB); |
335 | put_iac(c); |
336 | |
337 | put_iac((x >> 8) & 0xff); |
338 | put_iac(x & 0xff); |
339 | put_iac((y >> 8) & 0xff); |
340 | put_iac(y & 0xff); |
341 | |
342 | put_iac(IAC); |
343 | put_iac(SE); |
344 | } |
345 | #endif |
346 | |
347 | static char const escapecharis[] ALIGN1 = "\r\nEscape character is "; |
348 | |
349 | static void setConMode(void) |
350 | { |
351 | if (G.telflags & UF_ECHO) { |
352 | if (G.charmode == CHM_TRY) { |
353 | G.charmode = CHM_ON; |
354 | printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis); |
355 | rawmode(); |
356 | } |
357 | } else { |
358 | if (G.charmode != CHM_OFF) { |
359 | G.charmode = CHM_OFF; |
360 | printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); |
361 | cookmode(); |
362 | } |
363 | } |
364 | } |
365 | |
366 | static void will_charmode(void) |
367 | { |
368 | G.charmode = CHM_TRY; |
369 | G.telflags |= (UF_ECHO | UF_SGA); |
370 | setConMode(); |
371 | |
372 | put_iac2(DO, TELOPT_ECHO); |
373 | put_iac2(DO, TELOPT_SGA); |
374 | iac_flush(); |
375 | } |
376 | |
377 | static void do_linemode(void) |
378 | { |
379 | G.charmode = CHM_TRY; |
380 | G.telflags &= ~(UF_ECHO | UF_SGA); |
381 | setConMode(); |
382 | |
383 | put_iac2(DONT, TELOPT_ECHO); |
384 | put_iac2(DONT, TELOPT_SGA); |
385 | iac_flush(); |
386 | } |
387 | |
388 | static void to_notsup(char c) |
389 | { |
390 | if (G.telwish == WILL) |
391 | put_iac2(DONT, c); |
392 | else if (G.telwish == DO) |
393 | put_iac2(WONT, c); |
394 | } |
395 | |
396 | static void to_echo(void) |
397 | { |
398 | /* if server requests ECHO, don't agree */ |
399 | if (G.telwish == DO) { |
400 | put_iac2(WONT, TELOPT_ECHO); |
401 | return; |
402 | } |
403 | if (G.telwish == DONT) |
404 | return; |
405 | |
406 | if (G.telflags & UF_ECHO) { |
407 | if (G.telwish == WILL) |
408 | return; |
409 | } else if (G.telwish == WONT) |
410 | return; |
411 | |
412 | if (G.charmode != CHM_OFF) |
413 | G.telflags ^= UF_ECHO; |
414 | |
415 | if (G.telflags & UF_ECHO) |
416 | put_iac2(DO, TELOPT_ECHO); |
417 | else |
418 | put_iac2(DONT, TELOPT_ECHO); |
419 | |
420 | setConMode(); |
421 | full_write1_str("\r\n"); /* sudden modec */ |
422 | } |
423 | |
424 | static void to_sga(void) |
425 | { |
426 | /* daemon always sends will/wont, client do/dont */ |
427 | |
428 | if (G.telflags & UF_SGA) { |
429 | if (G.telwish == WILL) |
430 | return; |
431 | } else if (G.telwish == WONT) |
432 | return; |
433 | |
434 | G.telflags ^= UF_SGA; /* toggle */ |
435 | if (G.telflags & UF_SGA) |
436 | put_iac2(DO, TELOPT_SGA); |
437 | else |
438 | put_iac2(DONT, TELOPT_SGA); |
439 | } |
440 | |
441 | #if ENABLE_FEATURE_TELNET_TTYPE |
442 | static void to_ttype(void) |
443 | { |
444 | /* Tell server we will (or won't) do TTYPE */ |
445 | if (G.ttype) |
446 | put_iac2(WILL, TELOPT_TTYPE); |
447 | else |
448 | put_iac2(WONT, TELOPT_TTYPE); |
449 | } |
450 | #endif |
451 | |
452 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
453 | static void to_new_environ(void) |
454 | { |
455 | /* Tell server we will (or will not) do AUTOLOGIN */ |
456 | if (G.autologin) |
457 | put_iac2(WILL, TELOPT_NEW_ENVIRON); |
458 | else |
459 | put_iac2(WONT, TELOPT_NEW_ENVIRON); |
460 | } |
461 | #endif |
462 | |
463 | #if ENABLE_FEATURE_AUTOWIDTH |
464 | static void to_naws(void) |
465 | { |
466 | /* Tell server we will do NAWS */ |
467 | put_iac2(WILL, TELOPT_NAWS); |
468 | } |
469 | #endif |
470 | |
471 | static void telopt(byte c) |
472 | { |
473 | switch (c) { |
474 | case TELOPT_ECHO: |
475 | to_echo(); break; |
476 | case TELOPT_SGA: |
477 | to_sga(); break; |
478 | #if ENABLE_FEATURE_TELNET_TTYPE |
479 | case TELOPT_TTYPE: |
480 | to_ttype(); break; |
481 | #endif |
482 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
483 | case TELOPT_NEW_ENVIRON: |
484 | to_new_environ(); break; |
485 | #endif |
486 | #if ENABLE_FEATURE_AUTOWIDTH |
487 | case TELOPT_NAWS: |
488 | to_naws(); |
489 | put_iac_naws(c, G.win_width, G.win_height); |
490 | break; |
491 | #endif |
492 | default: |
493 | to_notsup(c); |
494 | break; |
495 | } |
496 | } |
497 | |
498 | /* subnegotiation -- ignore all (except TTYPE,NAWS) */ |
499 | static int subneg(byte c) |
500 | { |
501 | switch (G.telstate) { |
502 | case TS_SUB1: |
503 | if (c == IAC) |
504 | G.telstate = TS_SUB2; |
505 | #if ENABLE_FEATURE_TELNET_TTYPE |
506 | else |
507 | if (c == TELOPT_TTYPE && G.ttype) |
508 | put_iac_subopt(TELOPT_TTYPE, G.ttype); |
509 | #endif |
510 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
511 | else |
512 | if (c == TELOPT_NEW_ENVIRON && G.autologin) |
513 | put_iac_subopt_autologin(); |
514 | #endif |
515 | break; |
516 | case TS_SUB2: |
517 | if (c == SE) |
518 | return TRUE; |
519 | G.telstate = TS_SUB1; |
520 | /* break; */ |
521 | } |
522 | return FALSE; |
523 | } |
524 | |
525 | static void rawmode(void) |
526 | { |
527 | if (G.do_termios) |
528 | tcsetattr(0, TCSADRAIN, &G.termios_raw); |
529 | } |
530 | |
531 | static void cookmode(void) |
532 | { |
533 | if (G.do_termios) |
534 | tcsetattr(0, TCSADRAIN, &G.termios_def); |
535 | } |
536 | |
537 | /* poll gives smaller (-70 bytes) code */ |
538 | #define USE_POLL 1 |
539 | |
540 | int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
541 | int telnet_main(int argc UNUSED_PARAM, char **argv) |
542 | { |
543 | char *host; |
544 | int port; |
545 | int len; |
546 | #ifdef USE_POLL |
547 | struct pollfd ufds[2]; |
548 | #else |
549 | fd_set readfds; |
550 | int maxfd; |
551 | #endif |
552 | |
553 | INIT_G(); |
554 | |
555 | #if ENABLE_FEATURE_AUTOWIDTH |
556 | get_terminal_width_height(0, &G.win_width, &G.win_height); |
557 | #endif |
558 | |
559 | #if ENABLE_FEATURE_TELNET_TTYPE |
560 | G.ttype = getenv("TERM"); |
561 | #endif |
562 | |
563 | if (tcgetattr(0, &G.termios_def) >= 0) { |
564 | G.do_termios = 1; |
565 | G.termios_raw = G.termios_def; |
566 | cfmakeraw(&G.termios_raw); |
567 | } |
568 | |
569 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
570 | if (1 & getopt32(argv, "al:", &G.autologin)) |
571 | G.autologin = getenv("USER"); |
572 | argv += optind; |
573 | #else |
574 | argv++; |
575 | #endif |
576 | if (!*argv) |
577 | bb_show_usage(); |
578 | host = *argv++; |
579 | port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23); |
580 | if (*argv) /* extra params?? */ |
581 | bb_show_usage(); |
582 | |
583 | xmove_fd(create_and_connect_stream_or_die(host, port), netfd); |
584 | |
585 | setsockopt(netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); |
586 | |
587 | signal(SIGINT, record_signo); |
588 | |
589 | #ifdef USE_POLL |
590 | ufds[0].fd = 0; ufds[1].fd = netfd; |
591 | ufds[0].events = ufds[1].events = POLLIN; |
592 | #else |
593 | FD_ZERO(&readfds); |
594 | FD_SET(STDIN_FILENO, &readfds); |
595 | FD_SET(netfd, &readfds); |
596 | maxfd = netfd + 1; |
597 | #endif |
598 | |
599 | while (1) { |
600 | #ifndef USE_POLL |
601 | fd_set rfds = readfds; |
602 | |
603 | switch (select(maxfd, &rfds, NULL, NULL, NULL)) |
604 | #else |
605 | switch (poll(ufds, 2, -1)) |
606 | #endif |
607 | { |
608 | case 0: |
609 | /* timeout */ |
610 | case -1: |
611 | /* error, ignore and/or log something, bay go to loop */ |
612 | if (bb_got_signal) |
613 | con_escape(); |
614 | else |
615 | sleep(1); |
616 | break; |
617 | default: |
618 | |
619 | #ifdef USE_POLL |
620 | if (ufds[0].revents) |
621 | #else |
622 | if (FD_ISSET(STDIN_FILENO, &rfds)) |
623 | #endif |
624 | { |
625 | len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE); |
626 | if (len <= 0) |
627 | doexit(EXIT_SUCCESS); |
628 | TRACE(0, ("Read con: %d\n", len)); |
629 | handle_net_output(len); |
630 | } |
631 | |
632 | #ifdef USE_POLL |
633 | if (ufds[1].revents) |
634 | #else |
635 | if (FD_ISSET(netfd, &rfds)) |
636 | #endif |
637 | { |
638 | len = safe_read(netfd, G.buf, DATABUFSIZE); |
639 | if (len <= 0) { |
640 | full_write1_str("Connection closed by foreign host\r\n"); |
641 | doexit(EXIT_FAILURE); |
642 | } |
643 | TRACE(0, ("Read netfd (%d): %d\n", netfd, len)); |
644 | handle_net_input(len); |
645 | } |
646 | } |
647 | } /* while (1) */ |
648 | } |