telnet: convert CR NUL -> CR on input. Closes bug 2569
No binary (build failed)
—
(log yok)
| @@ -42,11 +42,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 +85,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) | |
| { | |
| @@ -170,24 +172,24 @@ static void handle_net_output(int len) | |
| * So I implemented it. It's really useful for me. I hope that | |
| * other people will find it interesting too. | |
| */ | |
| - | |
| - | int i, j; |
| + | byte outbuf[2 * DATABUFSIZE]; |
| byte *p = (byte*)G.buf; | |
| - | byte outbuf[4*DATABUFSIZE]; |
| + | int j = 0; |
| - | for (i = len, j = 0; i > 0; i--, p++) { |
| - | if (*p == 0x1d) { |
| + | for (; len > 0; len--, p++) { |
| + | byte c = *p; |
| + | if (c == 0x1d) { |
| con_escape(); | |
| return; | |
| } | |
| - | outbuf[j++] = *p; |
| - | if (*p == 0xff) |
| - | outbuf[j++] = 0xff; |
| - | else if (*p == 0x0d) |
| - | outbuf[j++] = 0x00; |
| + | outbuf[j++] = c; |
| + | if (c == IAC) |
| + | outbuf[j++] = c; /* IAC -> IAC IAC */ |
| + | else if (c == '\r') |
| + | outbuf[j++] = '\0'; /* CR -> CR NUL */ |
| } | |
| if (j > 0) | |
| - | write(netfd, outbuf, j); |
| + | full_write(netfd, outbuf, j); |
| } | |
| static void handle_net_input(int len) | |
| @@ -198,25 +200,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 +249,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) | |
| @@ -495,7 +520,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 +538,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 +559,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,25 +603,11 @@ 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 |
| while (1) { | |
| - | #ifndef USE_POLL |
| - | fd_set rfds = readfds; |
| - | |
| - | switch (select(maxfd, &rfds, NULL, NULL, NULL)) |
| - | #else |
| - | switch (poll(ufds, 2, -1)) |
| - | #endif |
| - | { |
| + | switch (poll(ufds, 2, -1)) { |
| case 0: | |
| /* timeout */ | |
| case -1: | |
| @@ -615,12 +619,7 @@ int telnet_main(int argc UNUSED_PARAM, char **argv) | |
| break; | |
| default: | |
| - | #ifdef USE_POLL |
| - | if (ufds[0].revents) |
| - | #else |
| - | if (FD_ISSET(STDIN_FILENO, &rfds)) |
| - | #endif |
| - | { |
| + | if (ufds[0].revents) { |
| len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE); | |
| if (len <= 0) | |
| doexit(EXIT_SUCCESS); | |
| @@ -628,12 +627,7 @@ int telnet_main(int argc UNUSED_PARAM, char **argv) | |
| handle_net_output(len); | |
| } | |
| - | #ifdef USE_POLL |
| - | if (ufds[1].revents) |
| - | #else |
| - | if (FD_ISSET(netfd, &rfds)) |
| - | #endif |
| - | { |
| + | if (ufds[1].revents) { |
| len = safe_read(netfd, G.buf, DATABUFSIZE); | |
| if (len <= 0) { | |
| full_write1_str("Connection closed by foreign host\r\n"); | |