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