/* vi: set sw=4 ts=4: */ /* * telnet implementation for busybox * * Author: Tomi Ollila * Copyright (C) 1994-2000 by Tomi Ollila * * Created: Thu Apr 7 13:29:41 1994 too * Last modified: Fri Jun 9 14:34:24 2000 too * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * HISTORY * Revision 3.1 1994/04/17 11:31:54 too * initial revision * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen * * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan * * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "busybox.h" #if 0 static const int DOTRACE = 1; #endif #ifdef DOTRACE #include /* for inet_ntoa()... */ #define TRACE(x, y) do { if (x) printf y; } while (0) #else #define TRACE(x, y) #endif #if 0 #define USE_POLL #include #else #include #endif static const int DATABUFSIZE = 128; static const int IACBUFSIZE = 128; static const int CHM_TRY = 0; static const int CHM_ON = 1; static const int CHM_OFF = 2; static const int UF_ECHO = 0x01; static const int UF_SGA = 0x02; enum { TS_0 = 1, TS_IAC = 2, TS_OPT = 3, TS_SUB1 = 4, TS_SUB2 = 5, }; #define WriteCS(fd, str) write(fd, str, sizeof str -1) typedef unsigned char byte; /* use globals to reduce size ??? */ /* test this hypothesis later */ static struct Globalvars { int netfd; /* console fd:s are 0 and 1 (and 2) */ /* same buffer used both for network and console read/write */ char * buf; /* allocating so static size is smaller */ short len; byte telstate; /* telnet negotiation state from network input */ byte telwish; /* DO, DONT, WILL, WONT */ byte charmode; byte telflags; byte gotsig; /* buffer to handle telnet negotiations */ char * iacbuf; short iaclen; /* could even use byte */ struct termios termios_def; struct termios termios_raw; } G; #define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */ #ifdef USE_GLOBALVAR_PTR struct Globalvars * Gptr; #define G (*Gptr) #else static struct Globalvars G; #endif static inline void iacflush() { write(G.netfd, G.iacbuf, G.iaclen); G.iaclen = 0; } /* Function prototypes */ static int getport(char * p); static struct in_addr getserver(char * p); static int create_socket(); static void setup_sockaddr_in(struct sockaddr_in * addr, int port); static int remote_connect(struct in_addr addr, int port); static void rawmode(); static void cookmode(); static void do_linemode(); static void will_charmode(); static void telopt(byte c); static int subneg(byte c); #if 0 static int local_bind(int port); #endif /* Some globals */ static int one = 1; #ifdef BB_FEATURE_TELNET_TTYPE static char *ttype; #endif static void doexit(int ev) { cookmode(); exit(ev); } static void conescape() { char b; if (G.gotsig) /* came from line mode... go raw */ rawmode(); WriteCS(1, "\r\nConsole escape. Commands are:\r\n\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"); if (read(0, &b, 1) <= 0) doexit(1); switch (b) { case 'l': if (!G.gotsig) { do_linemode(); goto rrturn; } break; case 'c': if (G.gotsig) { will_charmode(); goto rrturn; } break; case 'z': cookmode(); kill(0, SIGTSTP); rawmode(); break; case 'e': doexit(0); } WriteCS(1, "continuing...\r\n"); if (G.gotsig) cookmode(); rrturn: G.gotsig = 0; } static void handlenetoutput() { /* 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 */ int i; byte * p = G.buf; for (i = G.len; i > 0; i--, p++) { if (*p == 0x1d) { conescape(); return; } if (*p == 0xff) *p = 0x7f; } write(G.netfd, G.buf, G.len); } static void handlenetinput() { int i; int cstart = 0; for (i = 0; i < G.len; i++) { byte c = G.buf[i]; if (G.telstate == 0) /* most of the time state == 0 */ { if (c == IAC) { cstart = i; G.telstate = TS_IAC; } } else switch (G.telstate) { case TS_0: if (c == IAC) G.telstate = TS_IAC; else G.buf[cstart++] = c; break; case TS_IAC: if (c == IAC) /* IAC IAC -> 0xFF */ { G.buf[cstart++] = c; G.telstate = TS_0; break; } /* else */ switch (c) { case SB: G.telstate = TS_SUB1; break; case DO: case DONT: case WILL: case WONT: G.telwish = c; G.telstate = TS_OPT; break; default: G.telstate = TS_0; /* DATA MARK must be added later */ } break; case TS_OPT: /* WILL, WONT, DO, DONT */ telopt(c); G.telstate = TS_0; break; case TS_SUB1: /* Subnegotiation */ case TS_SUB2: /* Subnegotiation */ if (subneg(c) == TRUE) G.telstate = TS_0; break; } } if (G.telstate) { if (G.iaclen) iacflush(); if (G.telstate == TS_0) G.telstate = 0; G.len = cstart; } if (G.len) write(1, G.buf, G.len); } /* ******************************* */ static inline void putiac(int c) { G.iacbuf[G.iaclen++] = c; } static void putiac2(byte wwdd, byte c) { if (G.iaclen + 3 > IACBUFSIZE) iacflush(); putiac(IAC); putiac(wwdd); putiac(c); } #if 0 static void putiac1(byte c) { if (G.iaclen + 2 > IACBUFSIZE) iacflush(); putiac(IAC); putiac(c); } #endif #ifdef BB_FEATURE_TELNET_TTYPE static void putiac_subopt(byte c, char *str) { int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) if (G.iaclen + len > IACBUFSIZE) iacflush(); putiac(IAC); putiac(SB); putiac(c); putiac(0); while(*str) putiac(*str++); putiac(IAC); putiac(SE); } #endif /* void putiacstring (subneg strings) */ /* ******************************* */ static char const escapecharis[] = "\r\nEscape character is "; static void setConMode() { if (G.telflags & UF_ECHO) { if (G.charmode == CHM_TRY) { G.charmode = CHM_ON; printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis); rawmode(); } } else { if (G.charmode != CHM_OFF) { G.charmode = CHM_OFF; printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); cookmode(); } } } /* ******************************* */ static void will_charmode() { G.charmode = CHM_TRY; G.telflags |= (UF_ECHO | UF_SGA); setConMode(); putiac2(DO, TELOPT_ECHO); putiac2(DO, TELOPT_SGA); iacflush(); } static void do_linemode() { G.charmode = CHM_TRY; G.telflags &= ~(UF_ECHO | UF_SGA); setConMode(); putiac2(DONT, TELOPT_ECHO); putiac2(DONT, TELOPT_SGA); iacflush(); } /* ******************************* */ static inline void to_notsup(char c) { if (G.telwish == WILL) putiac2(DONT, c); else if (G.telwish == DO) putiac2(WONT, c); } static inline void to_echo() { /* if server requests ECHO, don't agree */ if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; } else if (G.telwish == DONT) return; if (G.telflags & UF_ECHO) { if (G.telwish == WILL) return; } else if (G.telwish == WONT) return; if (G.charmode != CHM_OFF) G.telflags ^= UF_ECHO; if (G.telflags & UF_ECHO) putiac2(DO, TELOPT_ECHO); else putiac2(DONT, TELOPT_ECHO); setConMode(); WriteCS(1, "\r\n"); /* sudden modec */ } static inline void to_sga() { /* daemon always sends will/wont, client do/dont */ if (G.telflags & UF_SGA) { if (G.telwish == WILL) return; } else if (G.telwish == WONT) return; if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */ putiac2(DO, TELOPT_SGA); else putiac2(DONT, TELOPT_SGA); return; } #ifdef BB_FEATURE_TELNET_TTYPE static inline void to_ttype() { /* Tell server we will (or won't) do TTYPE */ if(ttype) putiac2(WILL, TELOPT_TTYPE); else putiac2(WONT, TELOPT_TTYPE); return; } #endif static void telopt(byte c) { switch (c) { case TELOPT_ECHO: to_echo(c); break; case TELOPT_SGA: to_sga(c); break; #ifdef BB_FEATURE_TELNET_TTYPE case TELOPT_TTYPE: to_ttype(c); break; #endif default: to_notsup(c); break; } } /* ******************************* */ /* subnegotiation -- ignore all (except TTYPE) */ static int subneg(byte c) { switch (G.telstate) { case TS_SUB1: if (c == IAC) G.telstate = TS_SUB2; #ifdef BB_FEATURE_TELNET_TTYPE else if (c == TELOPT_TTYPE) putiac_subopt(TELOPT_TTYPE,ttype); #endif break; case TS_SUB2: if (c == SE) return TRUE; G.telstate = TS_SUB1; /* break; */ } return FALSE; } /* ******************************* */ static void fgotsig(int sig) { G.gotsig = sig; } static void rawmode() { tcsetattr(0, TCSADRAIN, &G.termios_raw); } static void cookmode() { tcsetattr(0, TCSADRAIN, &G.termios_def); } extern int telnet_main(int argc, char** argv) { struct in_addr host; int port; #ifdef USE_POLL struct pollfd ufds[2]; #else fd_set readfds; int maxfd; #endif #ifdef BB_FEATURE_TELNET_TTYPE ttype = getenv("TERM"); #endif memset(&G, 0, sizeof G); if (tcgetattr(0, &G.termios_def) < 0) exit(1); G.termios_raw = G.termios_def; cfmakeraw(&G.termios_raw); if (argc < 2) show_usage(); port = (argc > 2)? getport(argv[2]): 23; G.buf = xmalloc(DATABUFSIZE); G.iacbuf = xmalloc(IACBUFSIZE); host = getserver(argv[1]); G.netfd = remote_connect(host, port); signal(SIGINT, fgotsig); #ifdef USE_POLL ufds[0].fd = 0; ufds[1].fd = G.netfd; ufds[0].events = ufds[1].events = POLLIN; #else FD_ZERO(&readfds); FD_SET(0, &readfds); FD_SET(G.netfd, &readfds); maxfd = G.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 { case 0: /* timeout */ case -1: /* error, ignore and/or log something, bay go to loop */ if (G.gotsig) conescape(); else sleep(1); break; default: #ifdef USE_POLL if (ufds[0].revents) /* well, should check POLLIN, but ... */ #else if (FD_ISSET(0, &rfds)) #endif { G.len = read(0, G.buf, DATABUFSIZE); if (G.len <= 0) doexit(0); TRACE(0, ("Read con: %d\n", G.len)); handlenetoutput(); } #ifdef USE_POLL if (ufds[1].revents) /* well, should check POLLIN, but ... */ #else if (FD_ISSET(G.netfd, &rfds)) #endif { G.len = read(G.netfd, G.buf, DATABUFSIZE); if (G.len <= 0) { WriteCS(1, "Connection closed by foreign host.\r\n"); doexit(1); } TRACE(0, ("Read netfd (%d): %d\n", G.netfd, G.len)); handlenetinput(); } } } } static int getport(char * p) { unsigned int port = atoi(p); if ((unsigned)(port - 1 ) > 65534) { error_msg_and_die("%s: bad port number", p); } return port; } static struct in_addr getserver(char * host) { struct in_addr addr; struct hostent * he; if ((he = gethostbyname(host)) == NULL) { error_msg_and_die("%s: Unknown host", host); } memcpy(&addr, he->h_addr, sizeof addr); TRACE(1, ("addr: %s\n", inet_ntoa(addr))); return addr; } static int create_socket() { return socket(AF_INET, SOCK_STREAM, 0); } static void setup_sockaddr_in(struct sockaddr_in * addr, int port) { memset(addr, 0, sizeof(struct sockaddr_in)); addr->sin_family = AF_INET; addr->sin_port = htons(port); } #if 0 static int local_bind(int port) { struct sockaddr_in s_addr; int s = create_socket(); setup_sockaddr_in(&s_addr, port); setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); if (bind(s, &s_addr, sizeof s_addr) < 0) { char * e = sys_errlist[errno]; syserrorexit("bind"); exit(1); } listen(s, 1); return s; } #endif static int remote_connect(struct in_addr addr, int port) { struct sockaddr_in s_addr; int s = create_socket(); setup_sockaddr_in(&s_addr, port); s_addr.sin_addr = addr; setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); if (connect(s, (struct sockaddr *)&s_addr, sizeof s_addr) < 0) { perror_msg_and_die("Unable to connect to remote host"); } return s; } /* Local Variables: c-file-style: "linux" c-basic-offset: 4 tab-width: 4 End: */