Steve Grubb writes:
[oweals/busybox.git] / networking / telnet.c
index 495899a7a6d103cf2de04af4505f9a2f36fe238b..6c5f3d15b64bf5f8cc7f6eb787acbc52ab0d9693 100644 (file)
  * HISTORY
  * Revision 3.1  1994/04/17  11:31:54  too
  * initial revision
- * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen
- * <andersen@lineo.com> 
+ * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen <andersen@codepoet.org>
+ * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan
+ * <jam@ltsp.org>
+ * Modified 2004/02/11 to add ability to pass the USER variable to remote host
+ * by Fernando Silveira <swrh@gmx.net>
  *
  */
 
-#warning This applet has moved to netkit-tiny.  After BusyBox 0.49, this
-#warning applet will be removed from BusyBox.  All maintenance efforts
-#warning should be done in the netkit-tiny source tree.
-
-
-#include "busybox.h"
 #include <termios.h>
 #include <unistd.h>
 #include <errno.h>
@@ -47,7 +44,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
-#include <netdb.h>
+#include "busybox.h"
 
 #if 0
 static const int DOTRACE = 1;
@@ -57,7 +54,7 @@ static const int DOTRACE = 1;
 #include <arpa/inet.h> /* for inet_ntoa()... */
 #define TRACE(x, y) do { if (x) printf y; } while (0)
 #else
-#define TRACE(x, y) 
+#define TRACE(x, y)
 #endif
 
 #if 0
@@ -67,8 +64,8 @@ static const int DOTRACE = 1;
 #include <sys/time.h>
 #endif
 
-static const int DATABUFSIZE = 128;
-static const int IACBUFSIZE = 128;
+#define DATABUFSIZE  128
+#define IACBUFSIZE   128
 
 static const int CHM_TRY = 0;
 static const int CHM_ON = 1;
@@ -90,21 +87,20 @@ enum {
 typedef unsigned char byte;
 
 /* use globals to reduce size ??? */ /* test this hypothesis later */
-struct Globalvars {
+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;
+       char    buf[DATABUFSIZE]; /* allocating so static size is smaller */
        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;
+       char    iacbuf[IACBUFSIZE];
        short   iaclen; /* could even use byte */
-       struct termios termios_def;     
-       struct termios termios_raw;     
+       struct termios termios_def;
+       struct termios termios_raw;
 } G;
 
 #define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */
@@ -112,42 +108,44 @@ struct Globalvars {
 #ifdef USE_GLOBALVAR_PTR
 struct Globalvars * Gptr;
 #define G (*Gptr)
-#else
-struct Globalvars G;
 #endif
 
-static inline void iacflush()
+static inline void iacflush(void)
 {
        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 rawmode(void);
+static void cookmode(void);
+static void do_linemode(void);
+static void will_charmode(void);
 static void telopt(byte c);
 static int subneg(byte c);
-#if 0
-static int local_bind(int port);
-#endif
 
 /* Some globals */
 static int one = 1;
 
+#ifdef CONFIG_FEATURE_TELNET_TTYPE
+static char *ttype;
+#endif
+
+#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN
+static char *autologin;
+#endif
+
+#ifdef CONFIG_FEATURE_AUTOWIDTH
+static int win_width, win_height;
+#endif
+
 static void doexit(int ev)
 {
        cookmode();
        exit(ev);
-}      
+}
 
-static void conescape()
+static void conescape(void)
 {
        char b;
 
@@ -192,42 +190,58 @@ static void conescape()
 
        if (G.gotsig)
                cookmode();
-       
+
  rrturn:
        G.gotsig = 0;
-       
+
 }
-static void handlenetoutput()
+static void handlenetoutput(int len)
 {
        /*      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;
+        *      0x7f:s
+        *
+        *      2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com)
+        *      I don't agree.
+        *      first - I cannot use programs like sz/rz
+        *      second - the 0x0D is sent as one character and if the next
+        *               char is 0x0A then it's eaten by a server side.
+        *      third - whay doy you have to make 'many write()s'?
+        *              I don't understand.
+        *      So I implemented it. It's realy useful for me. I hope that
+        *      others people will find it interesting to.
+        */
+
+       int i, j;
        byte * p = G.buf;
+       byte outbuf[4*DATABUFSIZE];
 
-       for (i = G.len; i > 0; i--, p++)
+       for (i = len, j = 0; i > 0; i--, p++)
        {
                if (*p == 0x1d)
                {
                        conescape();
                        return;
                }
+               outbuf[j++] = *p;
                if (*p == 0xff)
-                       *p = 0x7f;
+                   outbuf[j++] = 0xff;
+               else if (*p == 0x0d)
+                   outbuf[j++] = 0x00;
        }
-       write(G.netfd, G.buf, G.len);
+       if (j > 0 )
+           write(G.netfd, outbuf, j);
 }
 
 
-static void handlenetinput()
+static void handlenetinput(int len)
 {
        int i;
        int cstart = 0;
 
-       for (i = 0; i < G.len; i++)
+       for (i = 0; i < len; i++)
        {
                byte c = G.buf[i];
 
@@ -279,7 +293,7 @@ static void handlenetinput()
                                 break;
                         case TS_SUB1: /* Subnegotiation */
                         case TS_SUB2: /* Subnegotiation */
-                                if (subneg(c) == TRUE)
+                                if (subneg(c))
                                         G.telstate = TS_0;
                                 break;
                         }
@@ -289,11 +303,11 @@ static void handlenetinput()
                if (G.iaclen)                   iacflush();
                if (G.telstate == TS_0) G.telstate = 0;
 
-               G.len = cstart;
+               len = cstart;
        }
 
-       if (G.len)
-               write(1, G.buf, G.len);
+       if (len)
+               write(1, G.buf, len);
 }
 
 
@@ -326,13 +340,82 @@ static void putiac1(byte c)
 }
 #endif
 
+#ifdef CONFIG_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
+
+#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN
+static void putiac_subopt_autologin(void)
+{
+       int len = strlen(autologin) + 6;        // (2 + 1 + 1 + strlen + 2)
+       char *user = "USER";
+
+       if (G.iaclen + len > IACBUFSIZE)
+               iacflush();
+
+       putiac(IAC);
+       putiac(SB);
+       putiac(TELOPT_NEW_ENVIRON);
+       putiac(TELQUAL_IS);
+       putiac(NEW_ENV_VAR);
+
+       while(*user)
+               putiac(*user++);
+
+       putiac(NEW_ENV_VALUE);
+
+       while(*autologin)
+               putiac(*autologin++);
+
+       putiac(IAC);
+       putiac(SE);
+}
+#endif
+
+#ifdef CONFIG_FEATURE_AUTOWIDTH
+static void putiac_naws(byte c, int x, int y)
+{
+       if (G.iaclen + 9 > IACBUFSIZE)
+               iacflush();
+
+       putiac(IAC);
+       putiac(SB);
+       putiac(c);
+
+       putiac((x >> 8) & 0xff);
+       putiac(x & 0xff);
+       putiac((y >> 8) & 0xff);
+       putiac(y & 0xff);
+
+       putiac(IAC);
+       putiac(SE);
+}
+#endif
+
 /* void putiacstring (subneg strings) */
 
 /* ******************************* */
 
-char const escapecharis[] = "\r\nEscape character is ";
+static char const escapecharis[] = "\r\nEscape character is ";
 
-static void setConMode()
+static void setConMode(void)
 {
        if (G.telflags & UF_ECHO)
        {
@@ -354,18 +437,18 @@ static void setConMode()
 
 /* ******************************* */
 
-static void will_charmode()
+static void will_charmode(void)
 {
        G.charmode = CHM_TRY;
        G.telflags |= (UF_ECHO | UF_SGA);
        setConMode();
-  
+
        putiac2(DO, TELOPT_ECHO);
        putiac2(DO, TELOPT_SGA);
        iacflush();
 }
 
-static void do_linemode()
+static void do_linemode(void)
 {
        G.charmode = CHM_TRY;
        G.telflags &= ~(UF_ECHO | UF_SGA);
@@ -384,12 +467,12 @@ static inline void to_notsup(char c)
        else if (G.telwish == DO)       putiac2(WONT, c);
 }
 
-static inline void to_echo()
+static inline void to_echo(void)
 {
        /* 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)
@@ -411,7 +494,7 @@ static inline void to_echo()
        WriteCS(1, "\r\n");  /* sudden modec */
 }
 
-static inline void to_sga()
+static inline void to_sga(void)
 {
        /* daemon always sends will/wont, client do/dont */
 
@@ -423,7 +506,7 @@ static inline void to_sga()
        else
                if (G.telwish == WONT)
                        return;
-  
+
        if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */
                putiac2(DO, TELOPT_SGA);
        else
@@ -432,20 +515,69 @@ static inline void to_sga()
        return;
 }
 
+#ifdef CONFIG_FEATURE_TELNET_TTYPE
+static inline void to_ttype(void)
+{
+       /* Tell server we will (or won't) do TTYPE */
+
+       if(ttype)
+               putiac2(WILL, TELOPT_TTYPE);
+       else
+               putiac2(WONT, TELOPT_TTYPE);
+
+       return;
+}
+#endif
+
+#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN
+static inline void to_new_environ(void)
+{
+       /* Tell server we will (or will not) do AUTOLOGIN */
+
+       if (autologin)
+               putiac2(WILL, TELOPT_NEW_ENVIRON);
+       else
+               putiac2(WONT, TELOPT_NEW_ENVIRON);
+
+       return;
+}
+#endif
+
+#ifdef CONFIG_FEATURE_AUTOWIDTH
+static inline void to_naws(void)
+{
+       /* Tell server we will do NAWS */
+       putiac2(WILL, TELOPT_NAWS);
+       return;
+}
+#endif
+
 static void telopt(byte c)
 {
        switch (c)
        {
-       case TELOPT_ECHO:               to_echo(c);             break;
-       case TELOPT_SGA:                to_sga(c);              break;
-       default:                                to_notsup(c);   break;
+               case TELOPT_ECHO:               to_echo();      break;
+               case TELOPT_SGA:                to_sga();       break;
+#ifdef CONFIG_FEATURE_TELNET_TTYPE
+               case TELOPT_TTYPE:              to_ttype();break;
+#endif
+#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN
+               case TELOPT_NEW_ENVIRON:        to_new_environ();       break;
+#endif
+#ifdef CONFIG_FEATURE_AUTOWIDTH
+               case TELOPT_NAWS:               to_naws();
+                                                               putiac_naws(c, win_width, win_height);
+                                                               break;
+#endif
+               default:                                to_notsup(c);
+                                                               break;
        }
 }
 
 
 /* ******************************* */
 
-/* subnegotiation -- ignore all */
+/* subnegotiation -- ignore all (except TTYPE,NAWS) */
 
 static int subneg(byte c)
 {
@@ -454,6 +586,16 @@ static int subneg(byte c)
        case TS_SUB1:
                if (c == IAC)
                        G.telstate = TS_SUB2;
+#ifdef CONFIG_FEATURE_TELNET_TTYPE
+               else
+               if (c == TELOPT_TTYPE)
+                       putiac_subopt(TELOPT_TTYPE,ttype);
+#endif
+#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN
+               else
+               if (c == TELOPT_NEW_ENVIRON)
+                       putiac_subopt_autologin();
+#endif
                break;
        case TS_SUB2:
                if (c == SE)
@@ -472,68 +614,103 @@ static void fgotsig(int sig)
 }
 
 
-static void rawmode()
+static void rawmode(void)
 {
        tcsetattr(0, TCSADRAIN, &G.termios_raw);
-}      
+}
 
-static void cookmode()
+static void cookmode(void)
 {
        tcsetattr(0, TCSADRAIN, &G.termios_def);
 }
 
 extern int telnet_main(int argc, char** argv)
 {
-       struct in_addr host;
-       int port;
+       int len;
+       struct sockaddr_in s_in;
 #ifdef USE_POLL
        struct pollfd ufds[2];
-#else  
+#else
        fd_set readfds;
        int maxfd;
-#endif 
+#endif
+
+#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN
+       int opt;
+#endif
 
+#ifdef CONFIG_FEATURE_AUTOWIDTH
+       get_terminal_width_height(0, &win_width, &win_height);
+#endif
+
+#ifdef CONFIG_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;
 
+       G.termios_raw = G.termios_def;
        cfmakeraw(&G.termios_raw);
-       
-       if (argc < 2)   usage(telnet_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);
+       if (argc < 2)
+               bb_show_usage();
+
+#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN
+       autologin = NULL;
+       while ((opt = getopt(argc, argv, "al:")) != EOF) {
+               switch (opt) {
+                       case 'l':
+                               autologin = bb_xstrdup(optarg);
+                               break;
+                       case 'a':
+                               autologin = getenv("USER");
+                               break;
+                       case '?':
+                               bb_show_usage();
+                               break;
+               }
+       }
+       if (optind < argc) {
+               bb_lookup_host(&s_in, argv[optind++]);
+               s_in.sin_port = bb_lookup_port((optind < argc) ? argv[optind++] :
+                               "telnet", "tcp", 23);
+               if (optind < argc)
+                       bb_show_usage();
+       } else
+               bb_show_usage();
+#else
+       bb_lookup_host(&s_in, argv[1]);
+       s_in.sin_port = bb_lookup_port((argc == 3) ? argv[2] : "telnet", "tcp", 23);
+#endif
+
+       G.netfd = xconnect(&s_in);
+
+       setsockopt(G.netfd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one);
 
        signal(SIGINT, fgotsig);
 
 #ifdef USE_POLL
        ufds[0].fd = 0; ufds[1].fd = G.netfd;
        ufds[0].events = ufds[1].events = POLLIN;
-#else  
+#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                 
+#endif
                {
                case 0:
                        /* timeout */
@@ -548,119 +725,41 @@ extern int telnet_main(int argc, char** argv)
 
 #ifdef USE_POLL
                        if (ufds[0].revents) /* well, should check POLLIN, but ... */
-#else                          
+#else
                        if (FD_ISSET(0, &rfds))
-#endif                         
+#endif
                        {
-                               G.len = read(0, G.buf, DATABUFSIZE);
+                               len = read(0, G.buf, DATABUFSIZE);
 
-                               if (G.len <= 0)
+                               if (len <= 0)
                                        doexit(0);
 
-                               TRACE(0, ("Read con: %d\n", G.len));
-                               
-                               handlenetoutput();
+                               TRACE(0, ("Read con: %d\n", len));
+
+                               handlenetoutput(len);
                        }
 
 #ifdef USE_POLL
                        if (ufds[1].revents) /* well, should check POLLIN, but ... */
-#else                          
+#else
                        if (FD_ISSET(G.netfd, &rfds))
-#endif                         
+#endif
                        {
-                               G.len = read(G.netfd, G.buf, DATABUFSIZE);
+                               len = read(G.netfd, G.buf, DATABUFSIZE);
 
-                               if (G.len <= 0)
+                               if (len <= 0)
                                {
                                        WriteCS(1, "Connection closed by foreign host.\r\n");
                                        doexit(1);
                                }
-                               TRACE(0, ("Read netfd (%d): %d\n", G.netfd, G.len));
+                               TRACE(0, ("Read netfd (%d): %d\n", G.netfd, len));
 
-                               handlenetinput();
+                               handlenetinput(len);
                        }
                }
        }
 }
 
-static int getport(char * p)
-{
-       unsigned int port = atoi(p);
-
-       if ((unsigned)(port - 1 ) > 65534)
-       {
-               error_msg_and_die("%s: bad port number\n", 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\n", 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 addr);
-       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"
@@ -668,4 +767,3 @@ c-basic-offset: 4
 tab-width: 4
 End:
 */
-