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