aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--toys/pending/telnet.c416
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();
}
}