diff options
Diffstat (limited to 'toys')
-rw-r--r-- | toys/pending/telnet.c | 416 |
1 files changed, 187 insertions, 229 deletions
diff --git a/toys/pending/telnet.c b/toys/pending/telnet.c index b4d5c72f..a7ea91e2 100644 --- a/toys/pending/telnet.c +++ b/toys/pending/telnet.c @@ -14,329 +14,287 @@ config TELNET help usage: telnet HOST [PORT] - Connect to telnet server + Connect to telnet server. */ #define FOR_telnet #include "toys.h" #include <arpa/telnet.h> -#include <netinet/in.h> GLOBALS( - int port; - int sfd; - char buff[128]; - int pbuff; - char iac[256]; - int piac; - char *ttype; - struct termios def_term; + int sock; + char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs. + char iac[128]; + int iac_len; + struct termios old_term; struct termios raw_term; - uint8_t term_ok; - uint8_t term_mode; - uint8_t flags; - unsigned win_width; - unsigned win_height; + uint8_t mode; + int echo, sga; + int state, request; ) -#define DATABUFSIZE 128 -#define IACBUFSIZE 256 +#define NORMAL 0 +#define SAW_IAC 1 +#define SAW_WWDD 2 +#define SAW_SB 3 +#define SAW_SB_TTYPE 4 +#define WANT_IAC 5 +#define WANT_SE 6 +#define SAW_CR 10 + #define CM_TRY 0 #define CM_ON 1 #define CM_OFF 2 -#define UF_ECHO 0x01 -#define UF_SGA 0x02 -// sets terminal mode: LINE or CHARACTER based om internal stat. -static char const es[] = "\r\nEscape character is "; -static void set_mode(void) +static void raw(int raw) { - if (TT.flags & UF_ECHO) { - if (TT.term_mode == CM_TRY) { - TT.term_mode = CM_ON; - printf("\r\nEntering character mode%s'^]'.\r\n", es); - if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term); - } - } else { - if (TT.term_mode != CM_OFF) { - TT.term_mode = CM_OFF; - printf("\r\nEntering line mode%s'^C'.\r\n", es); - if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); - } - } + tcsetattr(0, TCSADRAIN, raw ? &TT.raw_term : &TT.old_term); } -// flushes all data in IAC buff to server. static void flush_iac(void) { - int wlen = write(TT.sfd, TT.iac, TT.piac); - - if(wlen <= 0) error_msg("IAC : send failed."); - TT.piac = 0; + xwrite(TT.sock, TT.iac, TT.iac_len); + TT.iac_len = 0; } -// puts DATA in iac buff of length LEN and updates iac buff pointer. -static void put_iac(int len, ...) +static void iac(int n, ...) { va_list va; - if(TT.piac + len >= IACBUFSIZE) flush_iac(); - va_start(va, len); - for(;len > 0; TT.iac[TT.piac++] = (uint8_t)va_arg(va, int), len--); + if (TT.iac_len + n >= sizeof(TT.iac)) flush_iac(); + va_start(va, n); + while (n--) TT.iac[TT.iac_len++] = va_arg(va, int); va_end(va); } -// puts string STR in iac buff and updates iac buff pointer. -static void str_iac(char *str) +static void iacstr(char *str) { - int len = strlen(str); + if (TT.iac_len) flush_iac(); + xwrite(TT.sock, str, strlen(str)); + TT.iac_len = 0; +} - if(TT.piac + len + 1 >= IACBUFSIZE) flush_iac(); - strcpy(&TT.iac[TT.piac], str); - TT.piac += len+1; +static void slc(int line) +{ + TT.mode = line ? CM_OFF : CM_ON; + xprintf("Entering %s mode\r\nEscape character is '^%c'.\r\n", + line ? "line" : "character", line ? 'C' : ']'); + raw(!line); +} + +static void set_mode(void) +{ + if (TT.echo) { + if (TT.mode == CM_TRY) slc(0); + } else if (TT.mode != CM_OFF) slc(1); } static void handle_esc(void) { char input; - if(toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term); - xwrite(1,"\r\nConsole escape. Commands are:\r\n\n" + if (toys.signal) raw(1); + + // This matches busybox telnet, not BSD telnet. + xputsn("\r\n" + "Console escape. Commands are:\r\n" + "\r\n" " l go to line mode\r\n" " c go to character mode\r\n" " z suspend telnet\r\n" - " e exit telnet\r\n", 114); - - if (read(0, &input, 1) <= 0) { - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); - exit(0); + " e exit telnet\r\n" + "\r\n" + "telnet> "); + // In particular, the boxes only read a single character, not a line. + if (read(0, &input, 1) <= 0 || input == 4 || input == 'e') { + xprintf("Connection closed.\r\n"); + xexit(); } - switch (input) { - case 'l': + if (input == 'l') { if (!toys.signal) { - TT.term_mode = CM_TRY; - TT.flags &= ~(UF_ECHO | UF_SGA); + TT.mode = CM_TRY; + TT.echo = TT.sga = 0; set_mode(); - put_iac(6, IAC,DONT,TELOPT_ECHO,IAC,DONT, TELOPT_SGA); + iac(6, IAC, DONT, TELOPT_ECHO, IAC, DONT, TELOPT_SGA); flush_iac(); goto ret; } - break; - case 'c': + } else if (input == 'c') { if (toys.signal) { - TT.term_mode = CM_TRY; - TT.flags |= (UF_ECHO | UF_SGA); + TT.mode = CM_TRY; + TT.echo = TT.sga = 1; set_mode(); - put_iac(6, IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA); + iac(6, IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_SGA); flush_iac(); goto ret; } - break; - case 'z': - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); + } else if (input == 'z') { + raw(0); kill(0, SIGTSTP); - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term); - break; - case 'e': - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); - exit(0); - default: break; + raw(1); } - xwrite(1, "continuing...\r\n", 15); - if (toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); + xprintf("telnet %s %s\r\n", toys.optargs[0], toys.optargs[1] ?: "23"); + if (toys.signal) raw(0); ret: toys.signal = 0; } -/* - * handles telnet SUB NEGOTIATIONS - * only terminal type is supported. - */ -static void handle_negotiations(void) +// Handle WILL WONT DO DONT requests from the server. +static void handle_wwdd(char opt) { - char opt = TT.buff[TT.pbuff++]; - - switch(opt) { - case TELOPT_TTYPE: - opt = TT.buff[TT.pbuff++]; - if(opt == TELQUAL_SEND) { - put_iac(4, IAC,SB,TELOPT_TTYPE,TELQUAL_IS); - str_iac(TT.ttype); - put_iac(2, IAC,SE); - } - break; - default: break; - } -} - -/* - * handles server's DO DONT WILL WONT requests. - * supports ECHO, SGA, TTYPE, NAWS - */ -static void handle_ddww(char ddww) -{ - char opt = TT.buff[TT.pbuff++]; - - switch (opt) { - case TELOPT_ECHO: /* ECHO */ - if (ddww == DO) put_iac(3, IAC,WONT,TELOPT_ECHO); - if(ddww == DONT) break; - if (TT.flags & UF_ECHO) { - if (ddww == WILL) return; - } else if (ddww == WONT) return; - if (TT.term_mode != CM_OFF) TT.flags ^= UF_ECHO; - (TT.flags & UF_ECHO)? put_iac(3, IAC,DO,TELOPT_ECHO) : - put_iac(3, IAC,DONT,TELOPT_ECHO); + if (opt == TELOPT_ECHO) { + if (TT.request == DO) iac(3, IAC, WONT, TELOPT_ECHO); + if (TT.request == DONT) return; + if (TT.echo) { + if (TT.request == WILL) return; + } else if (TT.request == WONT) return; + if (TT.mode != CM_OFF) TT.echo ^= 1; + iac(3, IAC, TT.echo ? DO : DONT, TELOPT_ECHO); set_mode(); - printf("\r\n"); - break; - - case TELOPT_SGA: /* Supress GO Ahead */ - if (TT.flags & UF_SGA){ if (ddww == WILL) return; - } else if (ddww == WONT) return; - - TT.flags ^= UF_SGA; - (TT.flags & UF_SGA)? put_iac(3, IAC,DO,TELOPT_SGA) : - put_iac(3, IAC,DONT,TELOPT_SGA); - break; - - case TELOPT_TTYPE: /* Terminal Type */ - (TT.ttype)? put_iac(3, IAC,WILL,TELOPT_TTYPE): - put_iac(3, IAC,WONT,TELOPT_TTYPE); - break; - - case TELOPT_NAWS: /* Window Size */ - put_iac(3, IAC,WILL,TELOPT_NAWS); - put_iac(9, IAC,SB,TELOPT_NAWS,(TT.win_width >> 8) & 0xff, - TT.win_width & 0xff,(TT.win_height >> 8) & 0xff, - TT.win_height & 0xff,IAC,SE); - break; - - default: /* Default behaviour is to say NO */ - if(ddww == WILL) put_iac(3, IAC,DONT,opt); - if(ddww == DO) put_iac(3, IAC,WONT,opt); - break; + } else if (opt == TELOPT_SGA) { // Suppress Go Ahead + if (TT.sga) { + if (TT.request == WILL) return; + } else if (TT.request == WONT) return; + TT.sga ^= 1; + iac(3, IAC, TT.sga ? DO : DONT, TELOPT_SGA); + } else if (opt == TELOPT_TTYPE) { // Terminal TYPE + iac(3, IAC, WILL, TELOPT_TTYPE); + } else if (opt == TELOPT_NAWS) { // Negotiate About Window Size + unsigned cols = 80, rows = 24; + + terminal_size(&cols, &rows); + iac(3, IAC, WILL, TELOPT_NAWS); + iac(7, IAC, SB, TELOPT_NAWS, cols>>8, cols, rows>>8, rows); + iac(2, IAC, SE); + } else { + // Say "no" to anything we don't understand. + iac(3, IAC, (TT.request == WILL) ? DONT : WONT, opt); } } -/* - * parses data which is read from server of length LEN. - * and passes it to console. - */ -static int read_server(int len) +static void handle_server_output(int n) { + char *p = TT.buf, *end = TT.buf + n, ch; int i = 0; - char curr; - TT.pbuff = 0; - - do { - curr = TT.buff[TT.pbuff++]; - if (curr == IAC) { - curr = TT.buff[TT.pbuff++]; - switch (curr) { - case DO: /* FALLTHROUGH */ - case DONT: /* FALLTHROUGH */ - case WILL: /* FALLTHROUGH */ - case WONT: - handle_ddww(curr); - break; - case SB: - handle_negotiations(); - break; - case SE: - break; - default: break; + + // Possibilities: + // + // 1. Regular character + // 2. IAC [WILL|WONT|DO|DONT] option + // 3. IAC SB option arg... IAC SE + // + // The only subnegotiation we support is IAC SB TTYPE SEND IAC SE, so we just + // hard-code that into our state machine rather than having a more general + // "collect the subnegotation into a buffer and handle it after we've seen + // the IAC SE at the end". It's 2021, so we're unlikely to need more. + + while (p < end) { + ch = *p++; + if (TT.state == SAW_IAC) { + if (ch >= WILL && ch <= DONT) { + TT.state = SAW_WWDD; + TT.request = ch; + } else if (ch == SB) { + TT.state = SAW_SB; + } else { + TT.state = NORMAL; } - } else { - toybuf[i++] = curr; - if (curr == '\r') { curr = TT.buff[TT.pbuff++]; - if (curr != '\0') TT.pbuff--; + } else if (TT.state == SAW_WWDD) { + handle_wwdd(ch); + TT.state = NORMAL; + } else if (TT.state == SAW_SB) { + if (ch == TELOPT_TTYPE) TT.state = SAW_SB_TTYPE; + else TT.state = WANT_IAC; + } else if (TT.state == SAW_SB_TTYPE) { + if (ch == TELQUAL_SEND) { + iac(4, IAC, SB, TELOPT_TTYPE, TELQUAL_IS); + iacstr(getenv("TERM") ?: "NVT"); + iac(2, IAC, SE); } + TT.state = WANT_IAC; + } else if (TT.state == WANT_IAC) { + if (ch == IAC) TT.state = WANT_SE; + } else if (TT.state == WANT_SE) { + if (ch == SE) TT.state = NORMAL; + } else if (ch == IAC) { + TT.state = SAW_IAC; + } else { + if (TT.state == SAW_CR && ch == '\0') { + // CR NUL -> CR + } else toybuf[i++] = ch; + if (ch == '\r') TT.state = SAW_CR; + TT.state = NORMAL; } - } while (TT.pbuff < len); - + } if (i) xwrite(0, toybuf, i); - return 0; } -/* - * parses data which is read from console of length LEN - * and passes it to server. - */ -static void write_server(int len) +static void handle_user_input(int n) { - char *c = (char*)TT.buff; + char *p = TT.buf, ch; int i = 0; - for (; len > 0; len--, c++) { - if (*c == 0x1d) { + while (n--) { + ch = *p++; + if (ch == 0x1d) { handle_esc(); return; } - toybuf[i++] = *c; - if (*c == IAC) toybuf[i++] = *c; /* IAC -> IAC IAC */ - else if (*c == '\r') toybuf[i++] = '\0'; /* CR -> CR NUL */ + toybuf[i++] = ch; + if (ch == IAC) toybuf[i++] = IAC; // IAC -> IAC IAC + else if (ch == '\r') toybuf[i++] = '\n'; // CR -> CR LF + else if (ch == '\n') { // LF -> CR LF + toybuf[i-1] = '\r'; + toybuf[i++] = '\n'; + } } - if(i) xwrite(TT.sfd, toybuf, i); + if (i) xwrite(TT.sock, toybuf, i); +} + +static void reset_terminal(void) +{ + raw(0); } void telnet_main(void) { - char *port = "23"; - int set = 1, len; struct pollfd pfds[2]; + int n = 1; - TT.win_width = 80; //columns - TT.win_height = 24; //rows + tcgetattr(0, &TT.old_term); + TT.raw_term = TT.old_term; + cfmakeraw(&TT.raw_term); - if (toys.optc == 2) port = toys.optargs[1]; + TT.sock = xconnectany(xgetaddrinfo(*toys.optargs, toys.optargs[1] ?: "23", 0, + SOCK_STREAM, IPPROTO_TCP, 0)); + xsetsockopt(TT.sock, SOL_SOCKET, SO_KEEPALIVE, &n, sizeof(n)); - TT.ttype = getenv("TERM"); - if(!TT.ttype) TT.ttype = ""; - if(strlen(TT.ttype) > IACBUFSIZE-1) TT.ttype[IACBUFSIZE - 1] = '\0'; + xprintf("Connected to %s.\r\n", *toys.optargs); - if (!tcgetattr(0, &TT.def_term)) { - TT.term_ok = 1; - TT.raw_term = TT.def_term; - cfmakeraw(&TT.raw_term); - } - terminal_size(&TT.win_width, &TT.win_height); - - TT.sfd = xconnectany(xgetaddrinfo(*toys.optargs, port, 0, SOCK_STREAM, - IPPROTO_TCP, 0)); - setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set)); - setsockopt(TT.sfd, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set)); + sigatexit(reset_terminal); + signal(SIGINT, generic_signal); pfds[0].fd = 0; pfds[0].events = POLLIN; - pfds[1].fd = TT.sfd; + pfds[1].fd = TT.sock; pfds[1].events = POLLIN; - - signal(SIGINT, generic_signal); - while(1) { - if(TT.piac) flush_iac(); - if(poll(pfds, 2, -1) < 0) { + for (;;) { + if (poll(pfds, 2, -1) < 0) { if (toys.signal) handle_esc(); - else sleep(1); - - continue; + else perror_exit("poll"); } - if(pfds[0].revents) { - len = read(0, TT.buff, DATABUFSIZE); - if(len > 0) write_server(len); - else return; + if (pfds[0].revents) { + if ((n = read(0, TT.buf, sizeof(TT.buf))) <= 0) xexit(); + handle_user_input(n); } - if(pfds[1].revents) { - len = read(TT.sfd, TT.buff, DATABUFSIZE); - if(len > 0) read_server(len); - else { - printf("Connection closed by foreign host\r\n"); - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); - exit(1); - } + if (pfds[1].revents) { + if ((n = read(TT.sock, TT.buf, sizeof(TT.buf))) <= 0) + error_exit("Connection closed by foreign host\r"); + handle_server_output(n); } + if (TT.iac_len) flush_iac(); } } |