X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Ftelnet.c;h=58a691916e37454a60b58c463c3d522c0726d3a1;hb=11181335f9a97feddb30da4d09f9cd3739b8badd;hp=12d1970fa98ab318a1a24dd440c61c4610492519;hpb=0ef64bdb40c54681e8dd5ab8df42ac88e4ab1d4a;p=oweals%2Fbusybox.git diff --git a/networking/telnet.c b/networking/telnet.c index 12d1970fa..58a691916 100644 --- a/networking/telnet.c +++ b/networking/telnet.c @@ -21,14 +21,44 @@ * */ +//usage:#if ENABLE_FEATURE_TELNET_AUTOLOGIN +//usage:#define telnet_trivial_usage +//usage: "[-a] [-l USER] HOST [PORT]" +//usage:#define telnet_full_usage "\n\n" +//usage: "Connect to telnet server\n" +//usage: "\n -a Automatic login with $USER variable" +//usage: "\n -l USER Automatic login as USER" +//usage: +//usage:#else +//usage:#define telnet_trivial_usage +//usage: "HOST [PORT]" +//usage:#define telnet_full_usage "\n\n" +//usage: "Connect to telnet server" +//usage:#endif + #include #include #include "libbb.h" +#ifdef __BIONIC__ +/* should be in arpa/telnet.h */ +# define IAC 255 /* interpret as command: */ +# define DONT 254 /* you are not to use option */ +# define DO 253 /* please, you use option */ +# define WONT 252 /* I won't use option */ +# define WILL 251 /* I will use option */ +# define SB 250 /* interpret as subnegotiation */ +# define SE 240 /* end sub negotiation */ +# define TELOPT_ECHO 1 /* echo */ +# define TELOPT_SGA 3 /* suppress go ahead */ +# define TELOPT_TTYPE 24 /* terminal type */ +# define TELOPT_NAWS 31 /* window size */ +#endif + #ifdef DOTRACE -#define TRACE(x, y) do { if (x) printf y; } while (0) +# define TRACE(x, y) do { if (x) printf y; } while (0) #else -#define TRACE(x, y) +# define TRACE(x, y) #endif enum { @@ -42,11 +72,13 @@ enum { UF_ECHO = 0x01, UF_SGA = 0x02, - TS_0 = 1, + TS_NORMAL = 0, + TS_COPY = 1, TS_IAC = 2, TS_OPT = 3, TS_SUB1 = 4, TS_SUB2 = 5, + TS_CR = 6, }; typedef unsigned char byte; @@ -83,13 +115,13 @@ struct globals { }; \ } while (0) -/* Function prototypes */ + static void rawmode(void); static void cookmode(void); static void do_linemode(void); static void will_charmode(void); static void telopt(byte c); -static int subneg(byte c); +static void subneg(byte c); static void iac_flush(void) { @@ -154,40 +186,35 @@ static void con_escape(void) static void handle_net_output(int len) { - /* here we could do smart tricks how to handle 0xFF:s in output - * stream like writing twice every sequence of FF:s (thus doing - * many write()s. But I think interactive telnet application does - * not need to be 100% 8-bit clean, so changing every 0xff:s to - * 0x7f:s - * - * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com) - * I don't agree. - * first - I cannot use programs like sz/rz - * second - the 0x0D is sent as one character and if the next - * char is 0x0A then it's eaten by a server side. - * third - why do you have to make 'many write()s'? - * I don't understand. - * So I implemented it. It's really useful for me. I hope that - * other people will find it interesting too. - */ - - int i, j; - byte *p = (byte*)G.buf; - byte outbuf[4*DATABUFSIZE]; - - for (i = len, j = 0; i > 0; i--, p++) { - if (*p == 0x1d) { + byte outbuf[2 * DATABUFSIZE]; + byte *dst = outbuf; + byte *src = (byte*)G.buf; + byte *end = src + len; + + while (src < end) { + byte c = *src++; + if (c == 0x1d) { con_escape(); return; } - outbuf[j++] = *p; - if (*p == 0xff) - outbuf[j++] = 0xff; - else if (*p == 0x0d) - outbuf[j++] = 0x00; + *dst = c; + if (c == IAC) + *++dst = c; /* IAC -> IAC IAC */ + else + if (c == '\r' || c == '\n') { + /* Enter key sends '\r' in raw mode and '\n' in cooked one. + * + * See RFC 1123 3.3.1 Telnet End-of-Line Convention. + * Using CR LF instead of other allowed possibilities + * like CR NUL - easier to talk to HTTP/SMTP servers. + */ + *dst = '\r'; /* Enter -> CR LF */ + *++dst = '\n'; + } + dst++; } - if (j > 0) - write(netfd, outbuf, j); + if (dst - outbuf != 0) + full_write(netfd, outbuf, dst - outbuf); } static void handle_net_input(int len) @@ -198,25 +225,44 @@ static void handle_net_input(int len) for (i = 0; i < len; i++) { byte c = G.buf[i]; - if (G.telstate == 0) { /* most of the time state == 0 */ + if (G.telstate == TS_NORMAL) { /* most typical state */ if (c == IAC) { cstart = i; G.telstate = TS_IAC; } + else if (c == '\r') { + cstart = i + 1; + G.telstate = TS_CR; + } + /* No IACs were seen so far, no need to copy + * bytes within G.buf: */ continue; } + switch (G.telstate) { - case TS_0: + case TS_CR: + /* Prev char was CR. If cur one is NUL, ignore it. + * See RFC 1123 section 3.3.1 for discussion of telnet EOL handling. + */ + G.telstate = TS_COPY; + if (c == '\0') + break; + /* else: fall through - need to handle CR IAC ... properly */ + + case TS_COPY: /* Prev char was ordinary */ + /* Similar to NORMAL, but in TS_COPY we need to copy bytes */ if (c == IAC) G.telstate = TS_IAC; else G.buf[cstart++] = c; + if (c == '\r') + G.telstate = TS_CR; break; - case TS_IAC: - if (c == IAC) { /* IAC IAC -> 0xFF */ + case TS_IAC: /* Prev char was IAC */ + if (c == IAC) { /* IAC IAC -> one IAC */ G.buf[cstart++] = c; - G.telstate = TS_0; + G.telstate = TS_COPY; break; } /* else */ @@ -228,34 +274,38 @@ static void handle_net_input(int len) case DONT: case WILL: case WONT: - G.telwish = c; + G.telwish = c; G.telstate = TS_OPT; break; + /* DATA MARK must be added later */ default: - G.telstate = TS_0; /* DATA MARK must be added later */ + G.telstate = TS_COPY; } break; - case TS_OPT: /* WILL, WONT, DO, DONT */ + + case TS_OPT: /* Prev chars were IAC WILL/WONT/DO/DONT */ telopt(c); - G.telstate = TS_0; + G.telstate = TS_COPY; break; + case TS_SUB1: /* Subnegotiation */ case TS_SUB2: /* Subnegotiation */ - if (subneg(c)) - G.telstate = TS_0; + subneg(c); /* can change G.telstate */ break; } } - if (G.telstate) { + + if (G.telstate != TS_NORMAL) { + /* We had some IACs, or CR */ if (G.iaclen) iac_flush(); - if (G.telstate == TS_0) - G.telstate = 0; + if (G.telstate == TS_COPY) /* we aren't in the middle of IAC */ + G.telstate = TS_NORMAL; len = cstart; } if (len) - write(STDOUT_FILENO, G.buf, len); + full_write(STDOUT_FILENO, G.buf, len); } static void put_iac(int c) @@ -343,20 +393,20 @@ static void put_iac_naws(byte c, int x, int y) } #endif -static char const escapecharis[] ALIGN1 = "\r\nEscape character is "; - static void setConMode(void) { if (G.telflags & UF_ECHO) { if (G.charmode == CHM_TRY) { G.charmode = CHM_ON; - printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis); + printf("\r\nEntering %s mode" + "\r\nEscape character is '^%c'.\r\n", "character", ']'); rawmode(); } } else { if (G.charmode != CHM_OFF) { G.charmode = CHM_OFF; - printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); + printf("\r\nEntering %s mode" + "\r\nEscape character is '^%c'.\r\n", "line", 'C'); cookmode(); } } @@ -495,7 +545,7 @@ static void telopt(byte c) } /* subnegotiation -- ignore all (except TTYPE,NAWS) */ -static int subneg(byte c) +static void subneg(byte c) { switch (G.telstate) { case TS_SUB1: @@ -513,12 +563,13 @@ static int subneg(byte c) #endif break; case TS_SUB2: - if (c == SE) - return TRUE; + if (c == SE) { + G.telstate = TS_COPY; + return; + } G.telstate = TS_SUB1; - /* break; */ + break; } - return FALSE; } static void rawmode(void) @@ -533,21 +584,13 @@ static void cookmode(void) tcsetattr(0, TCSADRAIN, &G.termios_def); } -/* poll gives smaller (-70 bytes) code */ -#define USE_POLL 1 - int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int telnet_main(int argc UNUSED_PARAM, char **argv) { char *host; int port; int len; -#ifdef USE_POLL struct pollfd ufds[2]; -#else - fd_set readfds; - int maxfd; -#endif INIT_G(); @@ -585,63 +628,39 @@ int telnet_main(int argc UNUSED_PARAM, char **argv) signal(SIGINT, record_signo); -#ifdef USE_POLL - ufds[0].fd = 0; ufds[1].fd = netfd; - ufds[0].events = ufds[1].events = POLLIN; -#else - FD_ZERO(&readfds); - FD_SET(STDIN_FILENO, &readfds); - FD_SET(netfd, &readfds); - maxfd = netfd + 1; -#endif + ufds[0].fd = STDIN_FILENO; + ufds[0].events = POLLIN; + ufds[1].fd = netfd; + ufds[1].events = POLLIN; while (1) { -#ifndef USE_POLL - fd_set rfds = readfds; - - switch (select(maxfd, &rfds, NULL, NULL, NULL)) -#else - switch (poll(ufds, 2, -1)) -#endif - { - case 0: - /* timeout */ - case -1: + if (poll(ufds, 2, -1) < 0) { /* error, ignore and/or log something, bay go to loop */ if (bb_got_signal) con_escape(); else sleep(1); - break; - default: + continue; + } -#ifdef USE_POLL - if (ufds[0].revents) -#else - if (FD_ISSET(STDIN_FILENO, &rfds)) -#endif - { - len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE); - if (len <= 0) - doexit(EXIT_SUCCESS); - TRACE(0, ("Read con: %d\n", len)); - handle_net_output(len); - } +// FIXME: reads can block. Need full bidirectional buffering. -#ifdef USE_POLL - if (ufds[1].revents) -#else - if (FD_ISSET(netfd, &rfds)) -#endif - { - len = safe_read(netfd, G.buf, DATABUFSIZE); - if (len <= 0) { - full_write1_str("Connection closed by foreign host\r\n"); - doexit(EXIT_FAILURE); - } - TRACE(0, ("Read netfd (%d): %d\n", netfd, len)); - handle_net_input(len); + if (ufds[0].revents) { + len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE); + if (len <= 0) + doexit(EXIT_SUCCESS); + TRACE(0, ("Read con: %d\n", len)); + handle_net_output(len); + } + + if (ufds[1].revents) { + len = safe_read(netfd, G.buf, DATABUFSIZE); + if (len <= 0) { + full_write1_str("Connection closed by foreign host\r\n"); + doexit(EXIT_FAILURE); } + TRACE(0, ("Read netfd (%d): %d\n", netfd, len)); + handle_net_input(len); } } /* while (1) */ }