hush: remove superfluous assignment
[oweals/busybox.git] / networking / telnet.c
index 076728fb02e10bbd9d1b4b86f038e31dc71b3ca9..77e17479d75f0dc71054b1671983bcab75a86cd9 100644 (file)
 /* vi: set sw=4 ts=4: */
 /*
- * $Id: telnet.c,v 1.1 2000/02/22 17:17:45 erik Exp $
- * Mini telnet implementation for busybox
+ * telnet implementation for busybox
  *
- * Copyright (C) 2000 by Randolph Chung <tausq@debian.org>
+ * Author: Tomi Ollila <too@iki.fi>
+ * Copyright (C) 1994-2000 by Tomi Ollila
  *
- * 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.
+ * Created: Thu Apr  7 13:29:41 1994 too
+ * Last modified: Fri Jun  9 14:34:24 2000 too
  *
- * 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.
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
  *
- * 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 <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>
  *
- * This version of telnet is adapted (but very heavily modified) from the 
- * telnet in netkit-telnet 0.16, which is:
- *
- * Copyright (c) 1989 The Regents of the University of California.
- * All rights reserved.
- *
- * Original copyright notice is retained at the end of this file.
  */
 
-#include "internal.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <ctype.h>
-#include <signal.h>
-#include <errno.h>
-#include <netdb.h>
-#include <termios.h>
-#include <netinet/in.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
-#define TELOPTS
 #include <arpa/telnet.h>
-#include <arpa/inet.h>
-
-static int STDIN = 0;
-static int STDOUT = 1;
-static const char *telnet_usage = "telnet host [port]\n\n";
-static struct termios saved_tc;
-static unsigned char options[NTELOPTS];
-static char tr_state = 0; /* telnet send and receive state */
-static unsigned char subbuffer[256];
-static unsigned char *subpointer, *subend;
-#define        SB_CLEAR()      subpointer = subbuffer;
-#define        SB_TERM()       { subend = subpointer; SB_CLEAR(); }
-#define        SB_ACCUM(c)     if (subpointer < (subbuffer+sizeof subbuffer)) { *subpointer++ = (c); }
-#define        SB_GET()        (*subpointer++)
-#define        SB_PEEK()       (*subpointer)
-#define        SB_EOF()        (subpointer >= subend)
-#define        SB_LEN()        (subend - subpointer)
-
-#define TELNETPORT             23
-#define MASK_WILL              0x01
-#define MASK_WONT              0x04
-#define MASK_DO                        0x10
-#define MASK_DONT              0x40
-
-#define TFLAG_ISSET(opt, flag) (options[opt] & MASK_##flag)
-#define TFLAG_SET(opt, flag) (options[opt] |= MASK_##flag)
-#define TFLAG_CLR(opt, flag) (options[opt] &= ~MASK_##flag)
-
-#define PERROR(ctx) do { fprintf(stderr, "%s: %s\n", ctx, strerror(errno)); \
-                         return; } while (0)
-                                                
-#define        TS_DATA         0
-#define        TS_IAC          1
-#define        TS_WILL         2
-#define        TS_WONT         3
-#define        TS_DO           4
-#define        TS_DONT         5
-#define        TS_CR           6
-#define        TS_SB           7               /* sub-option collection */
-#define        TS_SE           8               /* looking for sub-option end */
-
-/* prototypes */
-static void telnet_init(void);
-static void telnet_start(char *host, int port);
-static void telnet_shutdown(void);
-/* ******************************************************************* */
-#define SENDCOMMAND(c,o) \
-       char buf[3]; \
-       buf[0] = IAC; buf[1] = c; buf[2] = o; \
-       write(s, buf, sizeof(buf)); 
-
-static inline void telnet_sendwill(int s, int c) { SENDCOMMAND(WILL, c); }
-static inline void telnet_sendwont(int s, int c) { SENDCOMMAND(WONT, c); }
-static inline void telnet_senddo(int s, int c) { SENDCOMMAND(DO, c); }
-static inline void telnet_senddont(int s, int c) { SENDCOMMAND(DONT, c); }
-
-static void telnet_setoptions(int s)
+#include <netinet/in.h>
+#include "libbb.h"
+
+#ifdef DOTRACE
+#define TRACE(x, y) do { if (x) printf y; } while (0)
+#else
+#define TRACE(x, y)
+#endif
+
+enum {
+       DATABUFSIZE = 128,
+       IACBUFSIZE  = 128,
+
+       CHM_TRY = 0,
+       CHM_ON = 1,
+       CHM_OFF = 2,
+
+       UF_ECHO = 0x01,
+       UF_SGA = 0x02,
+
+       TS_0 = 1,
+       TS_IAC = 2,
+       TS_OPT = 3,
+       TS_SUB1 = 4,
+       TS_SUB2 = 5,
+};
+
+typedef unsigned char byte;
+
+enum { netfd = 3 };
+
+struct globals {
+       int     iaclen; /* could even use byte, but it's a loss on x86 */
+       byte    telstate; /* telnet negotiation state from network input */
+       byte    telwish;  /* DO, DONT, WILL, WONT */
+       byte    charmode;
+       byte    telflags;
+       byte    do_termios;
+#if ENABLE_FEATURE_TELNET_TTYPE
+       char    *ttype;
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+       const char *autologin;
+#endif
+#if ENABLE_FEATURE_AUTOWIDTH
+       unsigned win_width, win_height;
+#endif
+       /* same buffer used both for network and console read/write */
+       char    buf[DATABUFSIZE];
+       /* buffer to handle telnet negotiations */
+       char    iacbuf[IACBUFSIZE];
+       struct termios termios_def;
+       struct termios termios_raw;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { \
+       struct G_sizecheck { \
+               char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \
+       }; \
+} while (0)
+
+/* Function prototypes */
+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);
+
+static void iac_flush(void)
 {
-       /*
-       telnet_senddo(s, TELOPT_SGA); TFLAG_SET(TELOPT_SGA, DO);
-       telnet_sendwill(s, TELOPT_NAWS); TFLAG_SET(TELOPT_NAWS, WILL);
-       telnet_sendwill(s, TELOPT_TSPEED); TFLAG_SET(TELOPT_TSPEED, WILL);
-       telnet_sendwill(s, TELOPT_LFLOW); TFLAG_SET(TELOPT_LFLOW, WILL);
-       telnet_sendwill(s, TELOPT_LINEMODE); TFLAG_SET(TELOPT_LINEMODE, WILL);
-       telnet_sendwill(s, TELOPT_NEW_ENVIRON); TFLAG_SET(TELOPT_NEW_ENVIRON, WILL);
-       telnet_senddo(s, TELOPT_STATUS); TFLAG_SET(TELOPT_STATUS, DO);
-       telnet_sendwill(s, TELOPT_TTYPE); TFLAG_SET(TELOPT_TTYPE, WILL);
-       */
-       telnet_senddo(s, TELOPT_BINARY); TFLAG_SET(TELOPT_BINARY, DO);
-       telnet_sendwill(s, TELOPT_BINARY); TFLAG_SET(TELOPT_BINARY, WILL);
+       write(netfd, G.iacbuf, G.iaclen);
+       G.iaclen = 0;
 }
 
-static void telnet_suboptions(int net)
+#define write_str(fd, str) write(fd, str, sizeof(str) - 1)
+
+static void doexit(int ev) NORETURN;
+static void doexit(int ev)
 {
-       char buf[256];
-       switch (SB_GET()) {
-               case TELOPT_TTYPE:
-                       if (TFLAG_ISSET(TELOPT_TTYPE, WONT)) return;
-                       if (SB_EOF() || SB_GET() != TELQUAL_SEND) {
-                       return;
-               } else {
-                       const char *name = getenv("TERM");
-                               if (name) {
-                                       snprintf(buf, sizeof(buf), "%c%c%c%c%s%c%c", IAC, SB,
-                                               TELOPT_TTYPE, TELQUAL_IS, name, IAC, SE);
-                                       write(net, buf, strlen(name)+6);
-                               }
-                       }
-               break;
-               case TELOPT_TSPEED:
-                       if (TFLAG_ISSET(TELOPT_TSPEED, WONT)) return;
-               if (SB_EOF()) return;
-               if (SB_GET() == TELQUAL_SEND) {
-                               /*
-                       long oospeed, iispeed;
-                       netoring.printf("%c%c%c%c%ld,%ld%c%c", IAC, SB, TELOPT_TSPEED,
-                     TELQUAL_IS, oospeed, iispeed, IAC, SE);
-                   */
-                       }
-               break;
-               /*
-               case TELOPT_LFLOW:
-                       if (TFLAG_ISSET(TELOPT_LFLOW, WONT)) return;
-                       if (SB_EOF()) return;
-               switch(SB_GET()) {
-                       case 1: localflow = 1; break;
-                               case 0: localflow = 0; break;
-                               default: return;
+       cookmode();
+       exit(ev);
+}
+
+static void con_escape(void)
+{
+       char b;
+
+       if (bb_got_signal) /* came from line mode... go raw */
+               rawmode();
+
+       write_str(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(STDIN_FILENO, &b, 1) <= 0)
+               doexit(EXIT_FAILURE);
+
+       switch (b) {
+       case 'l':
+               if (!bb_got_signal) {
+                       do_linemode();
+                       goto ret;
+               }
+               break;
+       case 'c':
+               if (bb_got_signal) {
+                       will_charmode();
+                       goto ret;
+               }
+               break;
+       case 'z':
+               cookmode();
+               kill(0, SIGTSTP);
+               rawmode();
+               break;
+       case 'e':
+               doexit(EXIT_SUCCESS);
+       }
+
+       write_str(1, "continuing...\r\n");
+
+       if (bb_got_signal)
+               cookmode();
+ ret:
+       bb_got_signal = 0;
+
+}
+
+static void handle_net_output(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
+        *
+        * 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 - why do you have to make 'many write()s'?
+        *      I don't understand.
+        * So I implemented it. It's really useful for me. I hope that
+        * other people will find it interesting too.
+        */
+
+       int i, j;
+       byte *p = (byte*)G.buf;
+       byte outbuf[4*DATABUFSIZE];
+
+       for (i = len, j = 0; i > 0; i--, p++) {
+               if (*p == 0x1d) {
+                       con_escape();
+                       return;
+               }
+               outbuf[j++] = *p;
+               if (*p == 0xff)
+                       outbuf[j++] = 0xff;
+               else if (*p == 0x0d)
+                       outbuf[j++] = 0x00;
+       }
+       if (j > 0)
+               write(netfd, outbuf, j);
+}
+
+static void handle_net_input(int len)
+{
+       int i;
+       int cstart = 0;
+
+       for (i = 0; i < 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;
                        }
-               break;
-               case TELOPT_LINEMODE:
-                       if (TFLAG_ISSET(TELOPT_LINEMODE, WONT)) return;
-                       if (SB_EOF()) return;
-                       switch (SB_GET()) {
-                       case WILL: lm_will(subpointer, SB_LEN()); break;
-                               case WONT: lm_wont(subpointer, SB_LEN()); break;
-                               case DO: lm_do(subpointer, SB_LEN()); break;
-                               case DONT: lm_dont(subpointer, SB_LEN()); break;
-                               case LM_SLC: slc(subpointer, SB_LEN()); break;
-                               case LM_MODE: lm_mode(subpointer, SB_LEN(), 0); break;
-                               default: break;
+                       continue;
+               }
+               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;
                        }
-               break;
-               case TELOPT_ENVIRON:
-                       if (SB_EOF()) return;
-                       switch(SB_PEEK()) {
-                               case TELQUAL_IS:
-                               case TELQUAL_INFO:
-                                       if (TFLAG_ISSET(TELOPT_ENVIRON, DONT)) return;
-                                       break;
-                               case TELQUAL_SEND:
-                                       if (TFLAG_ISSET(TELOPT_ENVIRON, WONT)) return;
-                                       break;
-                               default:
-                                       return;
+                       /* 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 */
                        }
-                       env_opt(subpointer, SB_LEN());
                        break;
-               */
-               case TELOPT_XDISPLOC:
-                       if (TFLAG_ISSET(TELOPT_XDISPLOC, WONT)) return;
-               if (SB_EOF()) return;
-                       if (SB_GET() == TELQUAL_SEND) {
-                               const char *dp = getenv("DISPLAY");
-                               if (dp) {
-                                       snprintf(buf, sizeof(buf), "%c%c%c%c%s%c%c", IAC, SB,
-                                               TELOPT_XDISPLOC, TELQUAL_IS, dp, IAC, SE);
-                                       write(net, buf, strlen(dp)+6);
-                               }
-                       }
-               break;
-       default:
+               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))
+                               G.telstate = TS_0;
+                       break;
+               }
+       }
+       if (G.telstate) {
+               if (G.iaclen)
+                       iac_flush();
+               if (G.telstate == TS_0)
+                       G.telstate = 0;
+               len = cstart;
        }
+
+       if (len)
+               write(STDOUT_FILENO, G.buf, len);
 }
 
-static void sighandler(int sig)
+static void put_iac(int c)
 {
-       telnet_shutdown();
-       exit(0);
+       G.iacbuf[G.iaclen++] = c;
 }
 
-static int telnet_send(int tty, int net)
+static void put_iac2(byte wwdd, byte c)
 {
-       int ret;
-       unsigned char ch;
-
-       while ((ret = read(tty, &ch, 1)) > 0) {
-               if (ch == 29) { /* 29 -- ctrl-] */
-                       /* do something here? */
-                       exit(0);
-               } else {
-                       ret = write(net, &ch, 1);
-                       break;
-               }
-       }
-       if (ret == -1 && errno == EWOULDBLOCK) return 1;
-       return ret;
+       if (G.iaclen + 3 > IACBUFSIZE)
+               iac_flush();
+
+       put_iac(IAC);
+       put_iac(wwdd);
+       put_iac(c);
 }
 
-static int telnet_recv(int net, int tty)
+#if ENABLE_FEATURE_TELNET_TTYPE
+static void put_iac_subopt(byte c, char *str)
 {
-       /* globals: tr_state - telnet receive state */
-       int ret, c = 0;
-       unsigned char ch;
-
-       while ((ret = read(net, &ch, 1)) > 0) {
-               c = ch;
-               /* printf("%02X ", c); fflush(stdout); */
-               switch (tr_state) {
-                       case TS_DATA:
-                               if (c == IAC) {
-                                       tr_state = TS_IAC;
-                                       break;
-                               } else {
-                                       write(tty, &c, 1);
-                               }
-                               break;
-                       case TS_IAC:
-                               switch (c) {
-                                       case WILL:
-                                               tr_state = TS_WILL; break;
-                                       case WONT:
-                                               tr_state = TS_WONT; break;
-                                       case DO:
-                                               tr_state = TS_DO; break;
-                                       case DONT:
-                                               tr_state = TS_DONT; break;
-                                       case SB:
-                                               SB_CLEAR();
-                                               tr_state = TS_SB; break;
-                                       case IAC:
-                                               write(tty, &c, 1); /* fallthrough */
-                                       default:
-                                               tr_state = TS_DATA;
-                               }
-                       
-                       /* subnegotiation -- ignored for now */
-                       case TS_SB:
-                               if (c == IAC) tr_state = TS_SE;
-                               else SB_ACCUM(c);
-                               break;
-                       case TS_SE:
-                               if (c == IAC) {
-                                       SB_ACCUM(IAC);
-                                       tr_state = TS_SB;
-                               } else if (c == SE) {
-                                       SB_ACCUM(IAC);
-                                       SB_ACCUM(SE);
-                                       subpointer -= 2;
-                                       SB_TERM();
-                                       telnet_suboptions(net);
-                                       tr_state = TS_DATA;
-                               }
-                           /* otherwise this is an error, but we ignore it for now */
-                               break;
-                       /* handle incoming requests */
-                       case TS_WILL: 
-                               printf("WILL %s\n", telopts[c]);
-                               if (!TFLAG_ISSET(c, DO)) {
-                                       if (c == TELOPT_BINARY) {
-                                               TFLAG_SET(c, DO);
-                                               TFLAG_CLR(c, DONT);
-                                               telnet_senddo(net, c);
-                                       } else {
-                                               TFLAG_SET(c, DONT);
-                                               telnet_senddont(net, c);
-                                       }
-                               }
-                               telnet_senddont(net, c);
-                               tr_state = TS_DATA;
-                               break;
-                       case TS_WONT:
-                               printf("WONT %s\n", telopts[c]);
-                               if (!TFLAG_ISSET(c, DONT)) {
-                                       TFLAG_SET(c, DONT);
-                                       TFLAG_CLR(c, DO);
-                                       telnet_senddont(net, c);
-                               }
-                               tr_state = TS_DATA;
-                               break;
-                       case TS_DO:
-                               printf("DO %s\n", telopts[c]);
-                               if (!TFLAG_ISSET(c, WILL)) {
-                                       if (c == TELOPT_BINARY) {
-                                               TFLAG_SET(c, WILL);
-                                               TFLAG_CLR(c, WONT);
-                                               telnet_sendwill(net, c);
-                                       } else {
-                                               TFLAG_SET(c, WONT);
-                                               telnet_sendwont(net, c);
-                                       }
-                               }
-                               tr_state = TS_DATA;
-                               break;
-                       case TS_DONT:
-                               printf("DONT %s\n", telopts[c]);
-                               if (!TFLAG_ISSET(c, WONT)) {
-                                       TFLAG_SET(c, WONT);
-                                       TFLAG_CLR(c, WILL);
-                                       telnet_sendwont(net, c);
-                               }
-                               tr_state = TS_DATA;
-                               break;
+       int len = strlen(str) + 6;   // ( 2 + 1 + 1 + strlen + 2 )
+
+       if (G.iaclen + len > IACBUFSIZE)
+               iac_flush();
+
+       put_iac(IAC);
+       put_iac(SB);
+       put_iac(c);
+       put_iac(0);
+
+       while (*str)
+               put_iac(*str++);
+
+       put_iac(IAC);
+       put_iac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+static void put_iac_subopt_autologin(void)
+{
+       int len = strlen(G.autologin) + 6;      // (2 + 1 + 1 + strlen + 2)
+       const char *user = "USER";
+
+       if (G.iaclen + len > IACBUFSIZE)
+               iac_flush();
+
+       put_iac(IAC);
+       put_iac(SB);
+       put_iac(TELOPT_NEW_ENVIRON);
+       put_iac(TELQUAL_IS);
+       put_iac(NEW_ENV_VAR);
+
+       while (*user)
+               put_iac(*user++);
+
+       put_iac(NEW_ENV_VALUE);
+
+       while (*G.autologin)
+               put_iac(*G.autologin++);
+
+       put_iac(IAC);
+       put_iac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void put_iac_naws(byte c, int x, int y)
+{
+       if (G.iaclen + 9 > IACBUFSIZE)
+               iac_flush();
+
+       put_iac(IAC);
+       put_iac(SB);
+       put_iac(c);
+
+       put_iac((x >> 8) & 0xff);
+       put_iac(x & 0xff);
+       put_iac((y >> 8) & 0xff);
+       put_iac(y & 0xff);
+
+       put_iac(IAC);
+       put_iac(SE);
+}
+#endif
+
+static char const escapecharis[] ALIGN1 = "\r\nEscape character is ";
+
+static void setConMode(void)
+{
+       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();
                }
-                                       
        }
-       if (ret == -1 && errno == EWOULDBLOCK) return 1;
-       return ret;
 }
 
-/* ******************************************************************* */
-static void telnet_init(void)
+static void will_charmode(void)
 {
-       struct termios tmp_tc;
-       cc_t esc = (']' & 0x1f); /* ctrl-] */
-       
-       memset(options, 0, sizeof(options));
-       SB_CLEAR();
-
-       tcgetattr(STDIN, &saved_tc);
-
-       tmp_tc = saved_tc;
-    tmp_tc.c_lflag &= ~ECHO; /* echo */
-       tmp_tc.c_oflag |= ONLCR; /* NL->CRLF translation */
-       tmp_tc.c_iflag |= ICRNL; 
-       tmp_tc.c_iflag &= ~(IXANY|IXOFF|IXON); /* no flow control */
-       tmp_tc.c_lflag |= ISIG; /* trap signals */
-       tmp_tc.c_lflag &= ~ICANON; /* edit mode  */
-   
-       /* misc settings, compat with default telnet stuff */
-       tmp_tc.c_oflag &= ~TABDLY;
-       
-       /* 8-bit clean */
-       tmp_tc.c_iflag &= ~ISTRIP;
-       tmp_tc.c_cflag &= ~(CSIZE|PARENB);
-       tmp_tc.c_cflag |= saved_tc.c_cflag & (CSIZE|PARENB);
-       tmp_tc.c_oflag |= OPOST;
-
-       /* set escape character */
-       tmp_tc.c_cc[VEOL] = esc;
-       tcsetattr(STDIN, TCSADRAIN, &tmp_tc);
+       G.charmode = CHM_TRY;
+       G.telflags |= (UF_ECHO | UF_SGA);
+       setConMode();
+
+       put_iac2(DO, TELOPT_ECHO);
+       put_iac2(DO, TELOPT_SGA);
+       iac_flush();
+}
+
+static void do_linemode(void)
+{
+       G.charmode = CHM_TRY;
+       G.telflags &= ~(UF_ECHO | UF_SGA);
+       setConMode();
+
+       put_iac2(DONT, TELOPT_ECHO);
+       put_iac2(DONT, TELOPT_SGA);
+       iac_flush();
 }
 
-static void telnet_start(char *hostname, int port)
+static void to_notsup(char c)
 {
-    struct hostent *host = 0;
-       struct sockaddr_in addr;
-    int s, c;
-       fd_set rfds, wfds;
-       
-       memset(&addr, 0, sizeof(addr));
-       host = gethostbyname(hostname);
-       if (!host) {
-               fprintf(stderr, "Unknown host: %s\n", hostname);
+       if (G.telwish == WILL)
+               put_iac2(DONT, c);
+       else if (G.telwish == DO)
+               put_iac2(WONT, c);
+}
+
+static void to_echo(void)
+{
+       /* if server requests ECHO, don't agree */
+       if (G.telwish == DO) {
+               put_iac2(WONT, TELOPT_ECHO);
                return;
        }
-       addr.sin_family = host->h_addrtype;
-       memcpy(&addr.sin_addr, host->h_addr, sizeof(addr.sin_addr));
-       addr.sin_port = htons(port);
-
-       printf("Trying %s...\n", inet_ntoa(addr.sin_addr));
-       
-       if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) PERROR("socket");
-       if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
-           PERROR("connect");
-       printf("Connected to %s\n", hostname);
-       printf("Escape character is ^]\n");
-
-       signal(SIGINT, sighandler);
-       signal(SIGQUIT, sighandler);
-       signal(SIGPIPE, sighandler);
-       signal(SIGWINCH, sighandler);
-
-       /* make inputs nonblocking */
-       c = 1;
-       ioctl(s, FIONBIO, &c);
-       ioctl(STDIN, FIONBIO, &c);
-       
-       if (port == TELNETPORT) telnet_setoptions(s);
-       
-       /* shuttle data back and forth between tty and socket */
-       while (1) {
-               FD_ZERO(&rfds);
-               FD_ZERO(&wfds);
-       
-               FD_SET(s, &rfds);
-               /* FD_SET(s, &wfds); */
-               FD_SET(STDIN, &rfds);
-               /* FD_SET(STDOUT, &wfds); */
-       
-               if ((c = select(s+1, &rfds, &wfds, 0, 0))) {
-                       if (c == -1) {
-                           /* handle errors */
-                               PERROR("select");
-                       }
-                       if (FD_ISSET(s, &rfds)) {
-                               /* input from network */
-                               FD_CLR(s, &rfds);
-                               c = telnet_recv(s, STDOUT);
-                               if (c == 0) break;
-                               if (c < 0) PERROR("telnet_recv");
-                       }
-                       if (FD_ISSET(STDIN, &rfds)) {
-                               /* input from tty */
-                               FD_CLR(STDIN, &rfds);
-                               c = telnet_send(STDIN, s);
-                               if (c == 0) break;
-                               if (c < 0) PERROR("telnet_send");
-                       }
-               }
-       }
-       
-    return;
+       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)
+               put_iac2(DO, TELOPT_ECHO);
+       else
+               put_iac2(DONT, TELOPT_ECHO);
+
+       setConMode();
+       write_str(1, "\r\n");  /* sudden modec */
 }
 
-static void telnet_shutdown(void)
+static void to_sga(void)
 {
-       printf("\n");
-       tcsetattr(STDIN, TCSANOW, &saved_tc);
+       /* daemon always sends will/wont, client do/dont */
+
+       if (G.telflags & UF_SGA) {
+               if (G.telwish == WILL)
+                       return;
+       } else if (G.telwish == WONT)
+               return;
+
+       G.telflags ^= UF_SGA; /* toggle */
+       if (G.telflags & UF_SGA)
+               put_iac2(DO, TELOPT_SGA);
+       else
+               put_iac2(DONT, TELOPT_SGA);
 }
 
-#ifdef STANDALONE_TELNET
-void usage(const char *msg)
+#if ENABLE_FEATURE_TELNET_TTYPE
+static void to_ttype(void)
 {
-       printf("%s", msg);
-       exit(0);
+       /* Tell server we will (or won't) do TTYPE */
+
+       if (G.ttype)
+               put_iac2(WILL, TELOPT_TTYPE);
+       else
+               put_iac2(WONT, TELOPT_TTYPE);
 }
+#endif
 
-int main(int argc, char **argv)
-#else
-int telnet_main(int argc, char **argv)
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+static void to_new_environ(void)
+{
+       /* Tell server we will (or will not) do AUTOLOGIN */
+
+       if (G.autologin)
+               put_iac2(WILL, TELOPT_NEW_ENVIRON);
+       else
+               put_iac2(WONT, TELOPT_NEW_ENVIRON);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void to_naws(void)
+{
+       /* Tell server we will do NAWS */
+       put_iac2(WILL, TELOPT_NAWS);
+}
 #endif
+
+static void telopt(byte c)
 {
-       int port = TELNETPORT;
-       
-    argc--; argv++;
-    if (argc < 1) usage(telnet_usage);
-       if (argc > 1) port = atoi(argv[1]);
-       telnet_init();
-       atexit(telnet_shutdown);
-
-       telnet_start(argv[0], port);
-       return 0;
+       switch (c) {
+       case TELOPT_ECHO:
+               to_echo(); break;
+       case TELOPT_SGA:
+               to_sga(); break;
+#if ENABLE_FEATURE_TELNET_TTYPE
+       case TELOPT_TTYPE:
+               to_ttype(); break;
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+       case TELOPT_NEW_ENVIRON:
+               to_new_environ(); break;
+#endif
+#if ENABLE_FEATURE_AUTOWIDTH
+       case TELOPT_NAWS:
+               to_naws();
+               put_iac_naws(c, G.win_width, G.win_height);
+               break;
+#endif
+       default:
+               to_notsup(c);
+               break;
+       }
 }
 
-/*
- * Copyright (c) 1988, 1990 Regents of the University of California.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *     This product includes software developed by the University of
- *     California, Berkeley and its contributors.
- * 4. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
+/* subnegotiation -- ignore all (except TTYPE,NAWS) */
+static int subneg(byte c)
+{
+       switch (G.telstate) {
+       case TS_SUB1:
+               if (c == IAC)
+                       G.telstate = TS_SUB2;
+#if ENABLE_FEATURE_TELNET_TTYPE
+               else
+               if (c == TELOPT_TTYPE)
+                       put_iac_subopt(TELOPT_TTYPE, G.ttype);
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+               else
+               if (c == TELOPT_NEW_ENVIRON)
+                       put_iac_subopt_autologin();
+#endif
+               break;
+       case TS_SUB2:
+               if (c == SE)
+                       return TRUE;
+               G.telstate = TS_SUB1;
+               /* break; */
+       }
+       return FALSE;
+}
+
+static void rawmode(void)
+{
+       if (G.do_termios)
+               tcsetattr(0, TCSADRAIN, &G.termios_raw);
+}
+
+static void cookmode(void)
+{
+       if (G.do_termios)
+               tcsetattr(0, TCSADRAIN, &G.termios_def);
+}
+
+/* poll gives smaller (-70 bytes) code */
+#define USE_POLL 1
+
+int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int telnet_main(int argc UNUSED_PARAM, char **argv)
+{
+       char *host;
+       int port;
+       int len;
+#ifdef USE_POLL
+       struct pollfd ufds[2];
+#else
+       fd_set readfds;
+       int maxfd;
+#endif
+
+       INIT_G();
+
+#if ENABLE_FEATURE_AUTOWIDTH
+       get_terminal_width_height(0, &G.win_width, &G.win_height);
+#endif
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+       G.ttype = getenv("TERM");
+#endif
+
+       if (tcgetattr(0, &G.termios_def) >= 0) {
+               G.do_termios = 1;
+               G.termios_raw = G.termios_def;
+               cfmakeraw(&G.termios_raw);
+       }
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+       if (1 & getopt32(argv, "al:", &G.autologin))
+               G.autologin = getenv("USER");
+       argv += optind;
+#else
+       argv++;
+#endif
+       if (!*argv)
+               bb_show_usage();
+       host = *argv++;
+       port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23);
+       if (*argv) /* extra params?? */
+               bb_show_usage();
+
+       xmove_fd(create_and_connect_stream_or_die(host, port), netfd);
+
+       setsockopt(netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+
+       signal(SIGINT, record_signo);
+
+#ifdef USE_POLL
+       ufds[0].fd = 0; ufds[1].fd = netfd;
+       ufds[0].events = ufds[1].events = POLLIN;
+#else
+       FD_ZERO(&readfds);
+       FD_SET(STDIN_FILENO, &readfds);
+       FD_SET(netfd, &readfds);
+       maxfd = 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 (bb_got_signal)
+                               con_escape();
+                       else
+                               sleep(1);
+                       break;
+               default:
+
+#ifdef USE_POLL
+                       if (ufds[0].revents) /* well, should check POLLIN, but ... */
+#else
+                       if (FD_ISSET(STDIN_FILENO, &rfds))
+#endif
+                       {
+                               len = read(STDIN_FILENO, G.buf, DATABUFSIZE);
+                               if (len <= 0)
+                                       doexit(EXIT_SUCCESS);
+                               TRACE(0, ("Read con: %d\n", len));
+                               handle_net_output(len);
+                       }
+
+#ifdef USE_POLL
+                       if (ufds[1].revents) /* well, should check POLLIN, but ... */
+#else
+                       if (FD_ISSET(netfd, &rfds))
+#endif
+                       {
+                               len = read(netfd, G.buf, DATABUFSIZE);
+                               if (len <= 0) {
+                                       write_str(1, "Connection closed by foreign host\r\n");
+                                       doexit(EXIT_FAILURE);
+                               }
+                               TRACE(0, ("Read netfd (%d): %d\n", netfd, len));
+                               handle_net_input(len);
+                       }
+               }
+       } /* while (1) */
+}