From ec1ea16337623824e3e71bb5dc0e011259664d7e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 7 Oct 2016 15:56:47 +0200 Subject: tcpsvd: don't keep shared fd open if fd limit is reached. closes 9331 Also, much improved help text. function old new delta packed_usage 30652 30851 +199 tcpudpsvd_main 1782 1784 +2 Signed-off-by: Denys Vlasenko --- networking/tcpudp.c | 69 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/networking/tcpudp.c b/networking/tcpudp.c index fbd1f1c45..b27cf3ea9 100644 --- a/networking/tcpudp.c +++ b/networking/tcpudp.c @@ -34,37 +34,56 @@ /* with not-implemented options: */ /* //usage: "[-hpEvv] [-c N] [-C N[:MSG]] [-b N] [-u USER] [-l NAME] [-i DIR|-x CDB] [-t SEC] IP PORT PROG" */ //usage:#define tcpsvd_full_usage "\n\n" -//usage: "Create TCP socket, bind to IP:PORT and listen\n" -//usage: "for incoming connection. Run PROG for each connection.\n" -//usage: "\n IP IP to listen on, 0 = all" -//usage: "\n PORT Port to listen on" +//usage: "Create TCP socket, bind to IP:PORT and listen for incoming connections.\n" +//usage: "Run PROG for each connection.\n" +//usage: "\n IP PORT IP:PORT to listen on" //usage: "\n PROG ARGS Program to run" -//usage: "\n -l NAME Local hostname (else looks up local hostname in DNS)" //usage: "\n -u USER[:GRP] Change to user/group after bind" -//usage: "\n -c N Handle up to N connections simultaneously" -//usage: "\n -b N Allow a backlog of approximately N TCP SYNs" -//usage: "\n -C N[:MSG] Allow only up to N connections from the same IP" -//usage: "\n New connections from this IP address are closed" -//usage: "\n immediately. MSG is written to the peer before close" +//usage: "\n -c N Up to N connections simultaneously (default 30)" +//usage: "\n -b N Allow backlog of approximately N TCP SYNs (default 20)" +//usage: "\n -C N[:MSG] Allow only up to N connections from the same IP:" +//usage: "\n new connections from this IP address are closed" +//usage: "\n immediately, MSG is written to the peer before close" +//usage: "\n -E Don't set up environment" //usage: "\n -h Look up peer's hostname" -//usage: "\n -E Don't set up environment variables" +//usage: "\n -l NAME Local hostname (else look up local hostname in DNS)" //usage: "\n -v Verbose" +//usage: "\n" +//usage: "\nEnvironment if no -E:" +//usage: "\nPROTO='TCP'" +//usage: "\nTCPREMOTEADDR='ip:port'" IF_FEATURE_IPV6(" ('[ip]:port' for IPv6)") +//usage: "\nTCPLOCALADDR='ip:port'" +//usage: "\nTCPORIGDSTADDR='ip:port' of destination before firewall" +//usage: "\n Useful for REDIRECTed-to-local connections:" +//usage: "\n iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to 8080" +//usage: "\nTCPCONCURRENCY=num_of_connects_from_this_ip" +//usage: "\nIf -h:" +//usage: "\nTCPLOCALHOST='hostname' (-l NAME is used if specified)" +//usage: "\nTCPREMOTEHOST='hostname'" + //usage: //usage:#define udpsvd_trivial_usage //usage: "[-hEv] [-c N] [-u USER] [-l NAME] IP PORT PROG" //usage:#define udpsvd_full_usage "\n\n" -//usage: "Create UDP socket, bind to IP:PORT and wait\n" -//usage: "for incoming packets. Run PROG for each packet,\n" -//usage: "redirecting all further packets with same peer ip:port to it.\n" -//usage: "\n IP IP to listen on, 0 = all" -//usage: "\n PORT Port to listen on" +//usage: "Create UDP socket, bind to IP:PORT and wait for incoming packets.\n" +//usage: "Run PROG for each packet, redirecting all further packets with same\n" +//usage: "peer ip:port to it.\n" +//usage: "\n IP PORT IP:PORT to listen on" //usage: "\n PROG ARGS Program to run" -//usage: "\n -l NAME Local hostname (else looks up local hostname in DNS)" //usage: "\n -u USER[:GRP] Change to user/group after bind" -//usage: "\n -c N Handle up to N connections simultaneously" +//usage: "\n -c N Up to N connections simultaneously (default 30)" +//usage: "\n -E Don't set up environment" //usage: "\n -h Look up peer's hostname" -//usage: "\n -E Don't set up environment variables" +//usage: "\n -l NAME Local hostname (else look up local hostname in DNS)" //usage: "\n -v Verbose" +//usage: "\n" +//usage: "\nEnvironment if no -E:" +//usage: "\nPROTO='UDP'" +//usage: "\nUDPREMOTEADDR='ip:port'" IF_FEATURE_IPV6(" ('[ip]:port' for IPv6)") +//usage: "\nUDPLOCALADDR='ip:port'" +//usage: "\nIf -h:" +//usage: "\nUDPLOCALHOST='hostname' (-l NAME is used if specified)" +//usage: "\nUDPREMOTEHOST='hostname'" #include "libbb.h" #include "common_bufsiz.h" @@ -240,7 +259,7 @@ int tcpudpsvd_main(int argc UNUSED_PARAM, char **argv) ); #else /* "+": stop on first non-option */ - opts = getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:v", + opts = getopt32(argv, "+c:+C:i:x:u:l:Eb:hpt:v", &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname, &backlog, &str_t, &verbose ); @@ -349,16 +368,20 @@ int tcpudpsvd_main(int argc UNUSED_PARAM, char **argv) again: hccp = NULL; + again1: + close(0); + /* It's important to close(0) _before_ wait loop: + * fd#0 can be a shared connection fd. + * If kept open by us, peer can't detect PROG closing it. + */ while (cnum >= cmax) wait_for_any_sig(); /* expecting SIGCHLD */ - /* Accept a connection to fd #0 */ - again1: - close(0); again2: sig_unblock(SIGCHLD); local.len = remote.len = sa_len; if (tcp) { + /* Accept a connection to fd #0 */ conn = accept(sock, &remote.u.sa, &remote.len); } else { /* In case recv_from_to won't be able to recover local addr. -- cgit v1.2.3-55-g6feb From 3720a61daf2e03e42e34902c2636ce3e3d6b8485 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 9 Oct 2016 23:04:16 +0200 Subject: ifupdown: rewrite state file atomically By user's request. Decided to not use fcntl(F_SETLKW) in lieu of problems with locking on networked filesystems. The existence of /var/run/ifstate.new is treated as a write lock. rename() provides atomicity. function old new delta ifupdown_main 1019 1122 +103 Signed-off-by: Denys Vlasenko --- networking/ifupdown.c | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/networking/ifupdown.c b/networking/ifupdown.c index b0bc0d70f..1d0fc53cf 100644 --- a/networking/ifupdown.c +++ b/networking/ifupdown.c @@ -56,6 +56,7 @@ #endif #define UDHCPC_CMD_OPTIONS CONFIG_IFUPDOWN_UDHCPC_CMD_OPTIONS +#define IFSTATE_FILE_PATH CONFIG_IFUPDOWN_IFSTATE_PATH #define debug_noise(args...) /*fprintf(stderr, args)*/ @@ -1200,7 +1201,7 @@ static llist_t *find_iface_state(llist_t *state_list, const char *iface) static llist_t *read_iface_state(void) { llist_t *state_list = NULL; - FILE *state_fp = fopen_for_read(CONFIG_IFUPDOWN_IFSTATE_PATH); + FILE *state_fp = fopen_for_read(IFSTATE_FILE_PATH); if (state_fp) { char *start, *end_ptr; @@ -1215,6 +1216,38 @@ static llist_t *read_iface_state(void) return state_list; } +/* read the previous state from the state file */ +static FILE *open_new_state_file(void) +{ + int fd, flags, cnt; + + cnt = 0; + flags = (O_WRONLY | O_CREAT | O_EXCL); + for (;;) { + fd = open(IFSTATE_FILE_PATH".new", flags, 0666); + if (fd >= 0) + break; + if (errno != EEXIST + || flags == (O_WRONLY | O_CREAT | O_TRUNC) + ) { + bb_perror_msg_and_die("can't open '%s'", + IFSTATE_FILE_PATH".new"); + } + /* Someone else created the .new file */ + if (cnt > 30 * 1000) { + /* Waited for 30*30/2 = 450 milliseconds, still EEXIST. + * Assuming a stale file, rewriting it. + */ + flags = (O_WRONLY | O_CREAT | O_TRUNC); + continue; + } + usleep(cnt); + cnt += 1000; + } + + return xfdopen_for_write(fd); +} + int ifupdown_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int ifupdown_main(int argc UNUSED_PARAM, char **argv) @@ -1348,7 +1381,7 @@ int ifupdown_main(int argc UNUSED_PARAM, char **argv) any_failures = 1; } else if (!NO_ACT) { /* update the state file */ - FILE *state_fp; + FILE *new_state_fp = open_new_state_file(); llist_t *state; llist_t *state_list = read_iface_state(); llist_t *iface_state = find_iface_state(state_list, iface); @@ -1368,15 +1401,15 @@ int ifupdown_main(int argc UNUSED_PARAM, char **argv) } /* Actually write the new state */ - state_fp = xfopen_for_write(CONFIG_IFUPDOWN_IFSTATE_PATH); state = state_list; while (state) { if (state->data) { - fprintf(state_fp, "%s\n", state->data); + fprintf(new_state_fp, "%s\n", state->data); } state = state->link; } - fclose(state_fp); + fclose(new_state_fp); + xrename(IFSTATE_FILE_PATH".new", IFSTATE_FILE_PATH); llist_free(state_list, free); } next: -- cgit v1.2.3-55-g6feb From 713b5133534d4bd4cfb49caba85eb3f655b6d8fd Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 11 Oct 2016 15:29:38 +0200 Subject: more: accept and ignore a bunch of options Alpine Linux stumbled over "more -s": http://bugs.alpinelinux.org/issues/5190 function old new delta more_main 857 872 +15 Signed-off-by: Denys Vlasenko --- util-linux/more.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/util-linux/more.c b/util-linux/more.c index 95cbdd994..07836e29d 100644 --- a/util-linux/more.c +++ b/util-linux/more.c @@ -73,7 +73,16 @@ int more_main(int argc UNUSED_PARAM, char **argv) INIT_G(); - argv++; + /* Parse options */ + /* Accepted but ignored: */ + /* -d Display help instead of ringing bell is pressed */ + /* -f Count logical lines (IOW: long lines are not folded) */ + /* -l Do not pause after any line containing a ^L (form feed) */ + /* -s Squeeze blank lines into one */ + /* -u Suppress underlining */ + getopt32(argv, "dflsu"); + argv += optind; + /* Another popular pager, most, detects when stdout * is not a tty and turns into cat. This makes sense. */ if (!isatty(STDOUT_FILENO)) -- cgit v1.2.3-55-g6feb From 2a54b3e86ebf71d5d5dce7eb95d1aa04a636e780 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 12 Oct 2016 14:54:10 +0200 Subject: telnetd: fix handling of short writes to pty If a write to pty is short, remove_iacs() can be run on a buffer repeatedly. This, for example, can eat 0xff chars (IACs, in telnet terms). Rework the logic to handle IACs in a special "write to pty" function. function old new delta telnetd_main 1662 1750 +88 Signed-off-by: Denys Vlasenko --- networking/telnetd.c | 231 ++++++++++++++++++++++++++------------------------- 1 file changed, 120 insertions(+), 111 deletions(-) diff --git a/networking/telnetd.c b/networking/telnetd.c index 2fbdc3bb3..68fccdca8 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -91,107 +91,133 @@ struct globals { } while (0) -/* - Remove all IAC's from buf1 (received IACs are ignored and must be removed - so as to not be interpreted by the terminal). Make an uninterrupted - string of characters fit for the terminal. Do this by packing - all characters meant for the terminal sequentially towards the end of buf. +/* Write some buf1 data to pty, processing IAC's. + * Update wridx1 and size1. Return < 0 on error. + * Buggy if IAC is present but incomplete: skips them. + */ +static ssize_t +safe_write_to_pty_decode_iac(struct tsession *ts) +{ + unsigned wr; + ssize_t rc; + unsigned char *buf; + unsigned char *found; + + buf = TS_BUF1(ts) + ts->wridx1; + wr = MIN(BUFSIZE - ts->wridx1, ts->size1); + found = memchr(buf, IAC, wr); + if (found != buf) { + /* There is a "prefix" of non-IAC chars. + * Write only them, and return. + */ + if (found) + wr = found - buf; - Return a pointer to the beginning of the characters meant for the terminal - and make *num_totty the number of characters that should be sent to - the terminal. + /* We map \r\n ==> \r for pragmatic reasons: + * many client implementations send \r\n when + * the user hits the CarriageReturn key. + * See RFC 1123 3.3.1 Telnet End-of-Line Convention. + */ + rc = wr; + found = memchr(buf, '\r', wr); + if (found) + rc = found - buf + 1; + rc = safe_write(ts->ptyfd, buf, rc); + if (rc <= 0) + return rc; + if (rc < wr && (buf[rc] == '\n' || buf[rc] == '\0')) + rc++; - Note - if an IAC (3 byte quantity) starts before (bf + len) but extends - past (bf + len) then that IAC will be left unprocessed and *processed - will be less than len. + goto update_and_return; + } - CR-LF ->'s CR mapping is also done here, for convenience. + /* buf starts with IAC char. Process that sequence. + * Example: we get this from our own (bbox) telnet client: + * read(5, "\377\374\1""\377\373\37""\377\372\37\0\262\0@\377\360""\377\375\1""\377\375\3") = 21 + */ + if (wr <= 1) { + /* Only the beginning of the IAC is in the + * buffer we were asked to process, we can't + * process this char */ + rc = 1; + goto update_and_return; + } - NB: may fail to remove iacs which wrap around buffer! - */ -static unsigned char * -remove_iacs(struct tsession *ts, int *pnum_totty) -{ - unsigned char *ptr0 = TS_BUF1(ts) + ts->wridx1; - unsigned char *ptr = ptr0; - unsigned char *totty = ptr; - unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); - int num_totty; - - while (ptr < end) { - if (*ptr != IAC) { - char c = *ptr; - - *totty++ = c; - ptr++; - /* We map \r\n ==> \r for pragmatic reasons. - * Many client implementations send \r\n when - * the user hits the CarriageReturn key. - * See RFC 1123 3.3.1 Telnet End-of-Line Convention. - */ - if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0')) - ptr++; - continue; - } + if (buf[1] == IAC) { /* Literal IAC (emacs M-DEL) */ + rc = safe_write(ts->ptyfd, buf, 1); + if (rc <= 0) + return rc; + rc = 2; + goto update_and_return; + } - if ((ptr+1) >= end) - break; - if (ptr[1] == NOP) { /* Ignore? (putty keepalive, etc.) */ - ptr += 2; - continue; - } - if (ptr[1] == IAC) { /* Literal IAC? (emacs M-DEL) */ - *totty++ = ptr[1]; - ptr += 2; - continue; - } + if (buf[1] == NOP) { /* NOP. Ignore (putty keepalive, etc) */ + rc = 2; + goto update_and_return; + } - /* - * TELOPT_NAWS support! - */ - if ((ptr+2) >= end) { - /* Only the beginning of the IAC is in the - buffer we were asked to process, we can't - process this char */ - break; - } - /* - * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE - */ - if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) { - struct winsize ws; - if ((ptr+8) >= end) - break; /* incomplete, can't process */ - ws.ws_col = (ptr[3] << 8) | ptr[4]; - ws.ws_row = (ptr[5] << 8) | ptr[6]; - ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); - ptr += 9; - continue; + if (wr <= 2) { + rc = 2; + goto update_and_return; + } + + /* TELOPT_NAWS support */ + /* IAC, SB, TELOPT_NAWS, 4-byte, IAC, SE */ + if (buf[1] == SB && buf[2] == TELOPT_NAWS) { + struct winsize ws; + if (wr <= 8) { + rc = wr; /* incomplete, can't process */ + goto update_and_return; } - /* skip 3-byte IAC non-SB cmd */ + memset(&ws, 0, sizeof(ws)); + ws.ws_col = (buf[3] << 8) | buf[4]; + ws.ws_row = (buf[5] << 8) | buf[6]; + ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); + rc = 9; + goto update_and_return; + } + /* Skip 3-byte IAC non-SB cmds */ #if DEBUG - fprintf(stderr, "Ignoring IAC %s,%s\n", - TELCMD(ptr[1]), TELOPT(ptr[2])); + fprintf(stderr, "Ignoring IAC %s,%s\n", + TELCMD(buf[1]), TELOPT(buf[2])); #endif - ptr += 3; + rc = 3; + + update_and_return: + ts->wridx1 += rc; + if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */ + ts->wridx1 = 0; + ts->size1 -= rc; + /* + * Hack. We cannot process iacs which wrap around buffer's end. + * Since properly fixing it requires writing bigger code, + * we rely instead on this code making it virtually impossible + * to have wrapped iac (people don't type at 2k/second). + * It also allows for bigger reads in common case. + */ + if (ts->size1 == 0) { /* very typical */ + //bb_error_msg("zero size1"); + ts->rdidx1 = 0; + ts->wridx1 = 0; + return rc; } - - num_totty = totty - ptr0; - *pnum_totty = num_totty; - /* The difference between ptr and totty is number of iacs - we removed from the stream. Adjust buf1 accordingly */ - if ((ptr - totty) == 0) /* 99.999% of cases */ - return ptr0; - ts->wridx1 += ptr - totty; - ts->size1 -= ptr - totty; - /* Move chars meant for the terminal towards the end of the buffer */ - return memmove(ptr - num_totty, ptr0, num_totty); + wr = ts->wridx1; + if (wr != 0 && wr < ts->rdidx1) { + /* Buffer is not wrapped yet. + * We can easily move it to the beginning. + */ + //bb_error_msg("moved %d", wr); + memmove(TS_BUF1(ts), TS_BUF1(ts) + wr, ts->size1); + ts->rdidx1 -= wr; + ts->wridx1 = 0; + } + return rc; } /* * Converting single IAC into double on output */ -static size_t iac_safe_write(int fd, const char *buf, size_t count) +static size_t safe_write_double_iac(int fd, const char *buf, size_t count) { const char *IACptr; size_t wr, rc, total; @@ -298,7 +324,7 @@ make_new_session( IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA }; - /* This confuses iac_safe_write(), it will try to duplicate + /* This confuses safe_write_double_iac(), it will try to duplicate * each IAC... */ //memcpy(TS_BUF2(ts), iacs_to_send, sizeof(iacs_to_send)); //ts->rdidx2 = sizeof(iacs_to_send); @@ -649,51 +675,34 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) struct tsession *next = ts->next; /* in case we free ts */ if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) { - int num_totty; - unsigned char *ptr; /* Write to pty from buffer 1 */ - ptr = remove_iacs(ts, &num_totty); - count = safe_write(ts->ptyfd, ptr, num_totty); + count = safe_write_to_pty_decode_iac(ts); if (count < 0) { if (errno == EAGAIN) goto skip1; goto kill_session; } - ts->size1 -= count; - ts->wridx1 += count; - if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */ - ts->wridx1 = 0; } skip1: if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) { /* Write to socket from buffer 2 */ count = MIN(BUFSIZE - ts->wridx2, ts->size2); - count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2(ts) + ts->wridx2), count); + count = safe_write_double_iac(ts->sockfd_write, (void*)(TS_BUF2(ts) + ts->wridx2), count); if (count < 0) { if (errno == EAGAIN) goto skip2; goto kill_session; } - ts->size2 -= count; ts->wridx2 += count; if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */ ts->wridx2 = 0; + ts->size2 -= count; + if (ts->size2 == 0) { + ts->rdidx2 = 0; + ts->wridx2 = 0; + } } skip2: - /* Should not be needed, but... remove_iacs is actually buggy - * (it cannot process iacs which wrap around buffer's end)! - * Since properly fixing it requires writing bigger code, - * we rely instead on this code making it virtually impossible - * to have wrapped iac (people don't type at 2k/second). - * It also allows for bigger reads in common case. */ - if (ts->size1 == 0) { - ts->rdidx1 = 0; - ts->wridx1 = 0; - } - if (ts->size2 == 0) { - ts->rdidx2 = 0; - ts->wridx2 = 0; - } if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) { /* Read from socket to buffer 1 */ -- cgit v1.2.3-55-g6feb From 0190c41bb297e8120e217cb531fb34d5035f17d2 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 12 Oct 2016 17:36:57 +0200 Subject: telnetd: fix a corner case where CRLF->CR translation can misbehave Signed-off-by: Denys Vlasenko --- networking/telnetd.c | 49 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/networking/telnetd.c b/networking/telnetd.c index 68fccdca8..fa618a9d7 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -125,48 +125,67 @@ safe_write_to_pty_decode_iac(struct tsession *ts) rc = safe_write(ts->ptyfd, buf, rc); if (rc <= 0) return rc; - if (rc < wr && (buf[rc] == '\n' || buf[rc] == '\0')) + if (rc < wr /* don't look past available data */ + && buf[rc-1] == '\r' /* need this: imagine that write was _short_ */ + && (buf[rc] == '\n' || buf[rc] == '\0') + ) { rc++; - + } goto update_and_return; } /* buf starts with IAC char. Process that sequence. * Example: we get this from our own (bbox) telnet client: - * read(5, "\377\374\1""\377\373\37""\377\372\37\0\262\0@\377\360""\377\375\1""\377\375\3") = 21 + * read(5, "\377\374\1""\377\373\37""\377\372\37\0\262\0@\377\360""\377\375\1""\377\375\3"): + * IAC WONT ECHO, IAC WILL NAWS, IAC SB NAWS IAC SE, IAC DO SGA */ if (wr <= 1) { - /* Only the beginning of the IAC is in the - * buffer we were asked to process, we can't - * process this char */ +/* BUG: only the single IAC byte is in the buffer, we just eat IAC */ rc = 1; goto update_and_return; } - if (buf[1] == IAC) { /* Literal IAC (emacs M-DEL) */ + /* 2-byte commands (240..250 and 255): + * IAC IAC (255) Literal 255. Supported. + * IAC NOP (241) NOP. Supported. + * IAC BRK (243) Break. Like serial line break. TODO via tcsendbreak()? + * IAC AYT (246) Are you there. Send back evidence that AYT was seen. TODO (send NOP back)? + * Implemented only as part of NAWS: + * IAC SB (250) Subnegotiation of an option follows. + * IAC SE (240) End of subnegotiation. + * These don't look useful: + * IAC DM (242) Data mark. What is this? + * IAC IP (244) Suspend, interrupt or abort the process. (Ancient cousin of ^C). + * IAC AO (245) Abort output. "You can continue running, but do not send me the output". + * IAC EC (247) Erase character. The receiver should delete the last received char. + * IAC EL (248) Erase line. The receiver should delete everything up tp last newline. + * IAC GA (249) Go ahead. For half-duplex lines: "now you talk". + */ + if (buf[1] == IAC) { /* Literal 255 (emacs M-DEL) */ rc = safe_write(ts->ptyfd, buf, 1); if (rc <= 0) return rc; rc = 2; goto update_and_return; } - - if (buf[1] == NOP) { /* NOP. Ignore (putty keepalive, etc) */ + if (buf[1] == NOP) { /* NOP (241). Ignore (putty keepalive, etc) */ rc = 2; goto update_and_return; } if (wr <= 2) { +/* BUG: only 2 bytes of the IAC is in the buffer, we just eat them */ rc = 2; goto update_and_return; } /* TELOPT_NAWS support */ - /* IAC, SB, TELOPT_NAWS, 4-byte, IAC, SE */ + /* IAC SB, TELOPT_NAWS, 4-byte, IAC SE */ if (buf[1] == SB && buf[2] == TELOPT_NAWS) { struct winsize ws; if (wr <= 8) { - rc = wr; /* incomplete, can't process */ +/* BUG: incomplete, can't process */ + rc = wr; goto update_and_return; } memset(&ws, 0, sizeof(ws)); @@ -176,7 +195,8 @@ safe_write_to_pty_decode_iac(struct tsession *ts) rc = 9; goto update_and_return; } - /* Skip 3-byte IAC non-SB cmds */ + + /* Skip 3-byte cmds (assume they are WILL/WONT/DO/DONT 251..254 codes) */ #if DEBUG fprintf(stderr, "Ignoring IAC %s,%s\n", TELCMD(buf[1]), TELOPT(buf[2])); @@ -189,10 +209,10 @@ safe_write_to_pty_decode_iac(struct tsession *ts) ts->wridx1 = 0; ts->size1 -= rc; /* - * Hack. We cannot process iacs which wrap around buffer's end. + * Hack. We cannot process IACs which wrap around buffer's end. * Since properly fixing it requires writing bigger code, * we rely instead on this code making it virtually impossible - * to have wrapped iac (people don't type at 2k/second). + * to have wrapped IAC (people don't type at 2k/second). * It also allows for bigger reads in common case. */ if (ts->size1 == 0) { /* very typical */ @@ -229,6 +249,7 @@ static size_t safe_write_double_iac(int fd, const char *buf, size_t count) if (*buf == (char)IAC) { static const char IACIAC[] ALIGN1 = { IAC, IAC }; rc = safe_write(fd, IACIAC, 2); +/* BUG: if partial write was only 1 byte long, we end up emitting just one IAC */ if (rc != 2) break; buf++; -- cgit v1.2.3-55-g6feb From 122c47ac02e2d2f615b34fe744d958fcd245f3d5 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 12 Oct 2016 19:13:46 +0200 Subject: telnetd: fix corner case of input processing of 0xff bytes I managed to reproduce the bug, with some difficulty. function old new delta telnetd_main 1780 1791 +11 Signed-off-by: Denys Vlasenko --- networking/telnetd.IAC_test.sh | 87 ++++++++++++++++++++++++++++++++++++++++++ networking/telnetd.c | 34 +++++++++++++++-- 2 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 networking/telnetd.IAC_test.sh diff --git a/networking/telnetd.IAC_test.sh b/networking/telnetd.IAC_test.sh new file mode 100644 index 000000000..a36ee3aa0 --- /dev/null +++ b/networking/telnetd.IAC_test.sh @@ -0,0 +1,87 @@ +#!/bin/sh +# Testcase for IAC input processing. +# The bug also required a small and odd BUFSIZE ("enum { BUFSIZE = 37 };") +# in telnetd.c to trigger easily. + +echo "\ +Run telnetd like this: + busybox telnetd -l./save.sh -F +where save.sh is + #!/bin/sh + stty -echo + exec cat >save.dat +Now I'll try to connect to it and feed it 2048 0xff bytes. +Check that save.dat does contain 2048 0xff bytes. +" + +ff() +{ +echo -en \ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'\ +'\r\n'; } + +ff | wc -c +{ ff; sleep 2; } | busybox telnet 127.0.0.1 diff --git a/networking/telnetd.c b/networking/telnetd.c index fa618a9d7..0da29410e 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -60,6 +60,7 @@ struct tsession { int sockfd_read; int sockfd_write; int ptyfd; + smallint buffered_IAC_for_pty; /* two circular buffers */ /*char *buf1, *buf2;*/ @@ -91,7 +92,7 @@ struct globals { } while (0) -/* Write some buf1 data to pty, processing IAC's. +/* Write some buf1 data to pty, processing IACs. * Update wridx1 and size1. Return < 0 on error. * Buggy if IAC is present but incomplete: skips them. */ @@ -105,6 +106,21 @@ safe_write_to_pty_decode_iac(struct tsession *ts) buf = TS_BUF1(ts) + ts->wridx1; wr = MIN(BUFSIZE - ts->wridx1, ts->size1); + /* wr is at least 1 here */ + + if (ts->buffered_IAC_for_pty) { + /* Last time we stopped on a "dangling" IAC byte. + * We removed it from the buffer back then. + * Now pretend it's still there, and jump to IAC processing. + */ + ts->buffered_IAC_for_pty = 0; + wr++; + ts->size1++; + buf--; /* Yes, this can point before the buffer. It's ok */ + ts->wridx1--; + goto handle_iac; + } + found = memchr(buf, IAC, wr); if (found != buf) { /* There is a "prefix" of non-IAC chars. @@ -140,11 +156,17 @@ safe_write_to_pty_decode_iac(struct tsession *ts) * IAC WONT ECHO, IAC WILL NAWS, IAC SB NAWS IAC SE, IAC DO SGA */ if (wr <= 1) { -/* BUG: only the single IAC byte is in the buffer, we just eat IAC */ + /* Only the single IAC byte is in the buffer, eat it + * and set a flag "process the rest of the sequence + * next time we are here". + */ + //bb_error_msg("dangling IAC!"); + ts->buffered_IAC_for_pty = 1; rc = 1; goto update_and_return; } + handle_iac: /* 2-byte commands (240..250 and 255): * IAC IAC (255) Literal 255. Supported. * IAC NOP (241) NOP. Supported. @@ -162,7 +184,8 @@ safe_write_to_pty_decode_iac(struct tsession *ts) * IAC GA (249) Go ahead. For half-duplex lines: "now you talk". */ if (buf[1] == IAC) { /* Literal 255 (emacs M-DEL) */ - rc = safe_write(ts->ptyfd, buf, 1); + //bb_error_msg("255!"); + rc = safe_write(ts->ptyfd, &buf[1], 1); if (rc <= 0) return rc; rc = 2; @@ -174,7 +197,10 @@ safe_write_to_pty_decode_iac(struct tsession *ts) } if (wr <= 2) { -/* BUG: only 2 bytes of the IAC is in the buffer, we just eat them */ +/* BUG: only 2 bytes of the IAC is in the buffer, we just eat them. + * This is not a practical problem since >2 byte IACs are seen only + * in initial negotiation, when buffer is empty + */ rc = 2; goto update_and_return; } -- cgit v1.2.3-55-g6feb From b6d421b635670939e4ec047543f84cb507e5fe9d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 12 Oct 2016 19:41:33 +0200 Subject: telnetd: treat all 2-byte IACs in 240..249 range as NOPs. A bit of future-proofing. Some of them can stand just being ignored. function old new delta telnetd_main 1791 1798 +7 Signed-off-by: Denys Vlasenko --- networking/telnetd.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/networking/telnetd.c b/networking/telnetd.c index 0da29410e..eaaf29bce 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -169,12 +169,10 @@ safe_write_to_pty_decode_iac(struct tsession *ts) handle_iac: /* 2-byte commands (240..250 and 255): * IAC IAC (255) Literal 255. Supported. + * IAC SE (240) End of subnegotiation. Treated as NOP. * IAC NOP (241) NOP. Supported. * IAC BRK (243) Break. Like serial line break. TODO via tcsendbreak()? * IAC AYT (246) Are you there. Send back evidence that AYT was seen. TODO (send NOP back)? - * Implemented only as part of NAWS: - * IAC SB (250) Subnegotiation of an option follows. - * IAC SE (240) End of subnegotiation. * These don't look useful: * IAC DM (242) Data mark. What is this? * IAC IP (244) Suspend, interrupt or abort the process. (Ancient cousin of ^C). @@ -182,8 +180,11 @@ safe_write_to_pty_decode_iac(struct tsession *ts) * IAC EC (247) Erase character. The receiver should delete the last received char. * IAC EL (248) Erase line. The receiver should delete everything up tp last newline. * IAC GA (249) Go ahead. For half-duplex lines: "now you talk". + * Implemented only as part of NAWS: + * IAC SB (250) Subnegotiation of an option follows. */ - if (buf[1] == IAC) { /* Literal 255 (emacs M-DEL) */ + if (buf[1] == IAC) { + /* Literal 255 (emacs M-DEL) */ //bb_error_msg("255!"); rc = safe_write(ts->ptyfd, &buf[1], 1); if (rc <= 0) @@ -191,7 +192,9 @@ safe_write_to_pty_decode_iac(struct tsession *ts) rc = 2; goto update_and_return; } - if (buf[1] == NOP) { /* NOP (241). Ignore (putty keepalive, etc) */ + if (buf[1] >= 240 && buf[1] <= 249) { + /* NOP (241). Ignore (putty keepalive, etc) */ + /* All other 2-byte commands also treated as NOPs here */ rc = 2; goto update_and_return; } @@ -205,24 +208,27 @@ safe_write_to_pty_decode_iac(struct tsession *ts) goto update_and_return; } - /* TELOPT_NAWS support */ - /* IAC SB, TELOPT_NAWS, 4-byte, IAC SE */ - if (buf[1] == SB && buf[2] == TELOPT_NAWS) { - struct winsize ws; - if (wr <= 8) { + if (buf[1] == SB) { + if (buf[2] == TELOPT_NAWS) { + /* IAC SB, TELOPT_NAWS, 4-byte, IAC SE */ + struct winsize ws; + if (wr <= 6) { /* BUG: incomplete, can't process */ - rc = wr; + rc = wr; + goto update_and_return; + } + memset(&ws, 0, sizeof(ws)); /* pixel sizes are set to 0 */ + ws.ws_col = (buf[3] << 8) | buf[4]; + ws.ws_row = (buf[5] << 8) | buf[6]; + ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); + rc = 7; + /* trailing IAC SE will be eaten separately, as 2-byte NOP */ goto update_and_return; } - memset(&ws, 0, sizeof(ws)); - ws.ws_col = (buf[3] << 8) | buf[4]; - ws.ws_row = (buf[5] << 8) | buf[6]; - ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); - rc = 9; - goto update_and_return; + /* else: other subnegs not supported yet */ } - /* Skip 3-byte cmds (assume they are WILL/WONT/DO/DONT 251..254 codes) */ + /* Assume it is a 3-byte WILL/WONT/DO/DONT 251..254 command and skip it */ #if DEBUG fprintf(stderr, "Ignoring IAC %s,%s\n", TELCMD(buf[1]), TELOPT(buf[2])); -- cgit v1.2.3-55-g6feb From 26d88d6bbda628f0005115bbe0f85722db267793 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 12 Oct 2016 20:09:22 +0200 Subject: telnetd: add another handshake example in comments, no code changes Signed-off-by: Denys Vlasenko --- networking/telnetd.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/networking/telnetd.c b/networking/telnetd.c index eaaf29bce..1de3abcc7 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -154,6 +154,11 @@ safe_write_to_pty_decode_iac(struct tsession *ts) * Example: we get this from our own (bbox) telnet client: * read(5, "\377\374\1""\377\373\37""\377\372\37\0\262\0@\377\360""\377\375\1""\377\375\3"): * IAC WONT ECHO, IAC WILL NAWS, IAC SB NAWS IAC SE, IAC DO SGA + * Another example (telnet-0.17 from old-netkit): + * read(4, "\377\375\3""\377\373\30""\377\373\37""\377\373 ""\377\373!""\377\373\"""\377\373'" + * "\377\375\5""\377\373#""\377\374\1""\377\372\37\0\257\0I\377\360""\377\375\1"): + * IAC DO SGA, IAC WILL TTYPE, IAC WILL NAWS, IAC WILL TSPEED, IAC WILL LFLOW, IAC WILL LINEMODE, IAC WILL NEW_ENVIRON, + * IAC DO STATUS, IAC WILL XDISPLOC, IAC WONT ECHO, IAC SB NAWS IAC SE, IAC DO ECHO */ if (wr <= 1) { /* Only the single IAC byte is in the buffer, eat it -- cgit v1.2.3-55-g6feb From 57727d478db55b25bdc33bb9067915b659605ae6 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 12 Oct 2016 20:42:58 +0200 Subject: telnet: code shrink put_iac2(w,c) is mostly used with constants, fold them into one arg function old new delta put_iac2_merged - 46 +46 telnet_main 1603 1583 -20 con_escape 285 257 -28 put_iac2 50 - -50 ------------------------------------------------------------------------------ (add/remove: 1/1 grow/shrink: 0/2 up/down: 46/-98) Total: -52 bytes Signed-off-by: Denys Vlasenko --- networking/telnet.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/networking/telnet.c b/networking/telnet.c index d2daf5c8c..1a6986b94 100644 --- a/networking/telnet.c +++ b/networking/telnet.c @@ -311,15 +311,16 @@ static void put_iac(int c) G.iacbuf[G.iaclen++] = c; } -static void put_iac2(byte wwdd, byte c) +static void put_iac2_merged(unsigned wwdd_and_c) { if (G.iaclen + 3 > IACBUFSIZE) iac_flush(); put_iac(IAC); - put_iac(wwdd); - put_iac(c); + put_iac(wwdd_and_c >> 8); + put_iac(wwdd_and_c & 0xff); } +#define put_iac2(wwdd,c) put_iac2_merged(((wwdd)<<8) + (c)) #if ENABLE_FEATURE_TELNET_TTYPE static void put_iac_subopt(byte c, char *str) -- cgit v1.2.3-55-g6feb From 85100a7067a51c5e6720c0a738317cc2144ab219 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 12 Oct 2016 20:56:46 +0200 Subject: cpio: fix restoration of file ownership, closes 9306 Signed-off-by: Denys Vlasenko --- archival/libarchive/init_handle.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/archival/libarchive/init_handle.c b/archival/libarchive/init_handle.c index cbae06ac3..dcba6666f 100644 --- a/archival/libarchive/init_handle.c +++ b/archival/libarchive/init_handle.c @@ -17,6 +17,10 @@ archive_handle_t* FAST_FUNC init_handle(void) archive_handle->action_data = data_skip; archive_handle->filter = filter_accept_all; archive_handle->seek = seek_by_jump; +#if ENABLE_CPIO || ENABLE_RPM2CPIO || ENABLE_RPM + archive_handle->cpio__owner.uid = (uid_t)-1L; + archive_handle->cpio__owner.gid = (gid_t)-1L; +#endif return archive_handle; } -- cgit v1.2.3-55-g6feb From 662634b82902afa84a8c978c259fa0bbd7bc8c09 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 13 Oct 2016 16:17:06 +0200 Subject: telnetd: ifdef out a buggy error handling code path Here, not handling the error is would just eat one input 0xff char. Correct handling would need even more corner case handling, as-is buggy handling corrupts the buffer. Since we just been told by kernel that pty is ready, EAGAIN should not be happening here anyway. function old new delta telnetd_main 1798 1785 -13 Signed-off-by: Denys Vlasenko --- networking/telnetd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/networking/telnetd.c b/networking/telnetd.c index 1de3abcc7..303ef1be7 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -192,8 +192,16 @@ safe_write_to_pty_decode_iac(struct tsession *ts) /* Literal 255 (emacs M-DEL) */ //bb_error_msg("255!"); rc = safe_write(ts->ptyfd, &buf[1], 1); + /* + * If we went through buffered_IAC_for_pty==1 path, + * bailing out on error like below messes up the buffer. + * EAGAIN is highly unlikely here, other errors will be + * repeated on next write, let's just skip error check. + */ +#if 0 if (rc <= 0) return rc; +#endif rc = 2; goto update_and_return; } -- cgit v1.2.3-55-g6feb From ee2d19445bfa6f0c6581bdcbf304d952d52809bf Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 14 Oct 2016 18:22:50 +0200 Subject: examples: update var_service/README Signed-off-by: Denys Vlasenko --- examples/var_service/README | 154 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 130 insertions(+), 24 deletions(-) diff --git a/examples/var_service/README b/examples/var_service/README index d096ad0b9..52dd781ef 100644 --- a/examples/var_service/README +++ b/examples/var_service/README @@ -1,50 +1,149 @@ -In many cases, network configuration makes it necessary to run several daemons: -dhcp, zeroconf, ppp, openvpn and such. They need to be controlled, -and in many cases you also want to babysit them. runsvdir is a good tool for this. -examples/var_service directory provides a few examples. It is meant to be used -this way: copy it somewhere (say, /var/service) and run something like + Daemontools and runit + +Tired of PID files, needing root access, and writing init scripts just +to have your UNIX apps start when your server boots? Want a simpler, +better alternative that will also restart them if they crash? If so, +this is an introduction to process supervision with runit/daemontools. + + + Background + +Classic init scripts, e.g. /etc/init.d/apache, are widely used for +starting processes at system boot time, when they are executed by init. +Sadly, init scripts are cumbersome and error-prone to write, they must +typically be edited and run as root, and the processes they launch do +not get restarted automatically if they crash. + +In an alternative scheme called "process supervision", each important +process is looked after by a tiny supervising process, which deals with +starting and stopping the important process on request, and re-starting +it when it exits unexpectedly. Those supervising processes can in turn +be supervised by other supervising processes. + +Dan Bernstein wrote the process supervision toolkit, "daemontools", +which is a set of small, reliable programs that cooperate in the +UNIX tradition to manage process supervision trees. + +Runit is a more conveniently licensed and more actively maintained +reimplementation of daemontools, written by Gerrit Pape. + +Here I’ll use runit, however, the ideas are the same for other +daemontools-like projects (there are several). + + + Service directories and scripts -env - PATH=... runsvdir /var/service & +In runit parlance a "service" is simply a directory containing a script +named "run". -from one of system startup scripts. (Google "man runsvdir" and "man runsv" -for more info about these tools). +There are just two key programs in runit. Firstly, runsv supervises the +process for an individual service. Service directories themselves sit +inside a containing directory, and the runsvdir program supervises that +directory, running one child runsv process for the service in each +subdirectory. Out of the box on Debian, for example, an instance of +runsvdir supervises services in subdirectories of /var/service/. -You can try or debug an individual service by running its SERVICE_DIR/run script. +If /var/service/log/ exists, runsv will supervise two services, +and will connect stdout of main service to the stdin of log service. +This is primarily used for logging. + +You can debug an individual service by running its SERVICE_DIR/run script. In this case, its stdout and stderr go to your terminal. You can also run "runsv SERVICE_DIR", which runs both the service and its logger service (SERVICE_DIR/log/run) if logger service exists. If logger service exists, the output will go to it instead of the terminal. -"runsvdir DIR" merely runs "runsv SERVICE_DIR" for every subdirectory in DIR. +"runsvdir /var/service" merely runs "runsv SERVICE_DIR" for every subdirectory +in /var/service. + + + Examples + +This directory contains some examples of services: + + var_service/getty_ + +Runs a getty on . (run script looks at $PWD and extracts suffix +after "_" as tty name). Create copies (or symlinks) of this directory +with different names to run many gettys on many ttys. + + var_service/gpm + +Runs gpm, the cut and paste utility and mouse server for text consoles. + + var_service/inetd + +Runs inetd. This is an example of a service with log. Log service +writes timestamped, rotated log data to /var/log/service/inetd/* +using "svlogd -tt". p_log and w_log scripts demonstrage how you can +"page log" and "watch log". + +Other services which have logs handle them in the same way. -Some existing examples: + var_service/nmeter + +Runs nmeter '%t %c ....' with output to /dev/tty9. This gives you +a 1-second sampling of server load and health on a dedicated text console. + + + Networking examples + +In many cases, network configuration makes it necessary to run several daemons: +dhcp, zeroconf, ppp, openvpn and such. They need to be controlled, +and in many cases you also want to babysit them. + +They present a case where different services need to control (start, stop, +restart) eact other. + + var_service/dhcp_if -var_service/dhcp_if - controls a udhcpc instance which provides dhpc-assigned IP address on interface named "if". Copy/rename this directory as needed to run udhcpc on other interfaces (var_service/dhcp_if/run script uses _foo suffix -of the parent directory as interface name). When IP address is obtained or lost, -var_service/dhcp_if/dhcp_handler is run. It saves new config data to -/var/run/service/fw/dhcp_if.ipconf and (re)starts /var/service/fw service. -This example can be used as a template for other dynamic network link services -(ppp/vpn/zcip). - -var_service/ifplugd_if - -watches link status of interface if. Downs and ups /var/service/dhcp_if +of the parent directory as interface name). + +When IP address is obtained or lost, var_service/dhcp_if/dhcp_handler is run. +It saves new config data to /var/run/service/fw/dhcp_if.ipconf and (re)starts +/var/service/fw service. This example can be used as a template for other +dynamic network link services (ppp/vpn/zcip). + +This is an example of service with has a "finish" script. If downed ("sv d"), +"finish" is executed. For this service, it removes DHCP address from +the interface. + + var_service/zcip_if + +Zeroconf IP service: assigns a 169.254.x.y/16 address to interface "if". +This allows to talk to other divices on a network without DHCP server +(if they also assign 169.254 addresses to themselves). + + var_service/ifplugd_if + +Watches link status of interface "if". Downs and ups /var/service/dhcp_if service accordingly. In effect, it allows you to unplug/plug-to-different-network and have your IP properly re-negotiated at once. -var_service/dhcp_if_pinger - + var_service/dhcp_if_pinger + Uses var_service/dhcp_if's data to determine router IP. Pings it. If ping fails, restarts /var/service/dhcp_if service. Basically, an example of watchdog service for networks which are not reliable and need babysitting. -var_service/fw - -A *one-shot* service which reconfigures network based on current known state -of ALL interfaces. Uses conf/*.ipconf (static config) and /var/run/service/fw/*.ipconf + var_service/supplicant_if + +Wireless supplicant (wifi association and encryption daemon) service for +inteface "if". + + var_service/fw + +This is an example of *one-shot* service. + +It reconfigures network based on current known state of ALL interfaces. +Uses conf/*.ipconf (static config) and /var/run/service/fw/*.ipconf (dynamic config from dhcp/ppp/vpn/etc) to determine what to do. + One-shot-ness of this service means that it shuts itself off after single run. IOW: it is not a constantly running daemon sort of thing. It starts, it configures the network, it shuts down, all done @@ -66,3 +165,10 @@ runsv will rerun it; or start it in a normal way if fw is not running. System administrators are expected to edit fw/run script, since network configuration needs are likely to be very complex and different for non-trivial installations. + + var_service/ftpd + var_service/httpd + var_service/tftpd + var_service/ntpd + +Examples of typical network daemons. -- cgit v1.2.3-55-g6feb From 93ff2b4b5fb58a42a1012bf8cd91d0faa4e10b12 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 14 Oct 2016 18:38:08 +0200 Subject: examples: update var_service/README again Signed-off-by: Denys Vlasenko --- examples/var_service/README | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/examples/var_service/README b/examples/var_service/README index 52dd781ef..41106b5ae 100644 --- a/examples/var_service/README +++ b/examples/var_service/README @@ -40,8 +40,8 @@ There are just two key programs in runit. Firstly, runsv supervises the process for an individual service. Service directories themselves sit inside a containing directory, and the runsvdir program supervises that directory, running one child runsv process for the service in each -subdirectory. Out of the box on Debian, for example, an instance of -runsvdir supervises services in subdirectories of /var/service/. +subdirectory. A typical choice is to start an instance of runsvdir +which supervises services in subdirectories of /var/service/. If /var/service/log/ exists, runsv will supervise two services, and will connect stdout of main service to the stdin of log service. @@ -94,7 +94,7 @@ dhcp, zeroconf, ppp, openvpn and such. They need to be controlled, and in many cases you also want to babysit them. They present a case where different services need to control (start, stop, -restart) eact other. +restart) each other. var_service/dhcp_if @@ -110,7 +110,9 @@ dynamic network link services (ppp/vpn/zcip). This is an example of service with has a "finish" script. If downed ("sv d"), "finish" is executed. For this service, it removes DHCP address from -the interface. +the interface. This is useful when ifplugd detects that the the link is dead +(cable is no longer attached anywhere) and downs us - keeping DHCP configured +addresses on the interface would make kernel still try to use it. var_service/zcip_if @@ -138,6 +140,9 @@ inteface "if". var_service/fw +"Firewall" script, although it is tasked with much more than setting up firewall. +It is responsible for all aspects of network configuration. + This is an example of *one-shot* service. It reconfigures network based on current known state of ALL interfaces. @@ -147,7 +152,7 @@ Uses conf/*.ipconf (static config) and /var/run/service/fw/*.ipconf One-shot-ness of this service means that it shuts itself off after single run. IOW: it is not a constantly running daemon sort of thing. It starts, it configures the network, it shuts down, all done -(unlike infamous NetworkManagers which sit in RAM forever, doing hell knows what). +(unlike infamous NetworkManagers which sit in RAM forever). However, any dhcp/ppp/vpn or similar service can restart it anytime when it senses the change in network configuration. @@ -158,10 +163,13 @@ This is achieved very simply by having # Make ourself one-shot sv o . at the very beginning of fw/run script, not at the end. + Therefore, any "sv u /var/run/service/fw" command by any other script "undoes" o(ne-shot) command if fw still runs, thus runsv will rerun it; or start it in a normal way if fw is not running. +This mechanism is the reason why fw is a service, not just a script. + System administrators are expected to edit fw/run script, since network configuration needs are likely to be very complex and different for non-trivial installations. -- cgit v1.2.3-55-g6feb From e43000f2d46c8d541dc3384e50b80058d1609503 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 14 Oct 2016 18:48:05 +0200 Subject: typo fixes in doc Signed-off-by: Denys Vlasenko --- examples/var_service/README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/var_service/README b/examples/var_service/README index 41106b5ae..268bb2d43 100644 --- a/examples/var_service/README +++ b/examples/var_service/README @@ -117,7 +117,7 @@ addresses on the interface would make kernel still try to use it. var_service/zcip_if Zeroconf IP service: assigns a 169.254.x.y/16 address to interface "if". -This allows to talk to other divices on a network without DHCP server +This allows to talk to other devices on a network without DHCP server (if they also assign 169.254 addresses to themselves). var_service/ifplugd_if @@ -136,7 +136,7 @@ and need babysitting. var_service/supplicant_if Wireless supplicant (wifi association and encryption daemon) service for -inteface "if". +interface "if". var_service/fw -- cgit v1.2.3-55-g6feb From 6bbb48fadf2265c7ed806027781da8039e561865 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 14 Oct 2016 19:02:11 +0200 Subject: examples: update var_service/README again Added "ps -AH e" example Signed-off-by: Denys Vlasenko --- examples/var_service/README | 54 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/examples/var_service/README b/examples/var_service/README index 268bb2d43..938cce91d 100644 --- a/examples/var_service/README +++ b/examples/var_service/README @@ -180,3 +180,57 @@ for non-trivial installations. var_service/ntpd Examples of typical network daemons. + + + Process tree + +Here is an example of the process tree from a live system with these services +(and a few others). An interesting detail are ftpd and vpnc services, where +you can see only logger process. These services are "downed" at the moment: +their daemons are not launched. + +PID TIME COMMAND +553 0:04 runsvdir -P /var/service +561 0:00 runsv sshd +576 0:00 svlogd -tt /var/log/service/sshd +589 0:00 /usr/sbin/sshd -D -e -p22 -u0 -h /var/service/sshd/ssh_host_rsa_key +562 0:00 runsv dhcp_eth0 +568 0:00 svlogd -tt /var/log/service/dhcp_eth0 +850 0:00 udhcpc -vv --foreground --interface=eth0 + --pidfile=/var/service/dhcp_eth0/udhcpc.pid + --script=/var/service/dhcp_eth0/dhcp_handler -x hostname bbox +563 0:00 runsv ntpd +573 0:01 svlogd -tt /var/log/service/ntpd +845 0:00 busybox ntpd -dddnNl -S ./ntp.script -p 10.x.x.x -p 10.x.x.x +564 0:00 runsv ifplugd_wlan0 +598 0:00 svlogd -tt /var/log/service/ifplugd_wlan0 +614 0:05 ifplugd -apqns -t3 -u0 -d0 -i wlan0 + -r /var/service/ifplugd_wlan0/ifplugd_handler +565 0:08 runsv dhcp_wlan0_pinger +911 0:00 sleep 67 +566 0:00 runsv unscd +583 0:03 svlogd -tt /var/log/service/unscd +599 0:02 nscd -dddd +567 0:00 runsv dhcp_wlan0 +591 0:00 svlogd -tt /var/log/service/dhcp_wlan0 +802 0:00 udhcpc -vv -C -o -V --foreground --interface=wlan0 + --pidfile=/var/service/dhcp_wlan0/udhcpc.pid + --script=/var/service/dhcp_wlan0/dhcp_handler +569 0:00 runsv fw +570 0:00 runsv ifplugd_eth0 +597 0:00 svlogd -tt /var/log/service/ifplugd_eth0 +612 0:05 ifplugd -apqns -t3 -u8 -d8 -i eth0 + -r /var/service/ifplugd_eth0/ifplugd_handler +571 0:00 runsv zcip_eth0 +590 0:00 svlogd -tt /var/log/service/zcip_eth0 +607 0:01 zcip -fvv eth0 /var/service/zcip_eth0/zcip_handler +572 0:00 runsv ftpd +604 0:00 svlogd -tt /var/log/service/ftpd +574 0:00 runsv vpnc +603 0:00 svlogd -tt /var/log/service/vpnc +575 0:00 runsv httpd +602 0:00 svlogd -tt /var/log/service/httpd +622 0:00 busybox httpd -p80 -vvv -f -h /home/httpd_root +577 0:00 runsv supplicant_wlan0 +627 0:00 svlogd -tt /var/log/service/supplicant_wlan0 +638 0:03 wpa_supplicant -i wlan0 -c /var/service/supplicant_wlan0/wpa_supplicant.conf -d -- cgit v1.2.3-55-g6feb