ntpd: fix a case when discipline_jitter = 0 if we step
[oweals/busybox.git] / networking / telnet.c
index 8b6d5f5dc28a1232ea4e52af0b5bd7ea23528f76..e8e51dce4aaa7cc314155cf0bf2fd5eaa71731c5 100644 (file)
+/* vi: set sw=4 ts=4: */
 /*
- * $Id: telnet.c,v 1.3 2000/05/12 19:41:47 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 GPLv2 or later, see file LICENSE in this source tree.
  *
- * 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
+//usage:#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+//usage:#define telnet_trivial_usage
+//usage:       "[-a] [-l USER] HOST [PORT]"
+//usage:#define telnet_full_usage "\n\n"
+//usage:       "Connect to telnet server\n"
+//usage:     "\n       -a      Automatic login with $USER variable"
+//usage:     "\n       -l USER Automatic login as USER"
+//usage:
+//usage:#else
+//usage:#define telnet_trivial_usage
+//usage:       "HOST [PORT]"
+//usage:#define telnet_full_usage "\n\n"
+//usage:       "Connect to telnet server"
+//usage:#endif
+
 #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"
-#ifndef BB_FEATURE_TRIVIAL_HELP
-       "\nProvides interactive communication with another\n"
-       "networked host using the TELNET protocol\n"
+#include <netinet/in.h>
+#include "libbb.h"
+
+#ifdef __BIONIC__
+/* should be in arpa/telnet.h */
+# define IAC         255  /* interpret as command: */
+# define DONT        254  /* you are not to use option */
+# define DO          253  /* please, you use option */
+# define WONT        252  /* I won't use option */
+# define WILL        251  /* I will use option */
+# define SB          250  /* interpret as subnegotiation */
+# define SE          240  /* end sub negotiation */
+# define TELOPT_ECHO   1  /* echo */
+# define TELOPT_SGA    3  /* suppress go ahead */
+# define TELOPT_TTYPE 24  /* terminal type */
+# define TELOPT_NAWS  31  /* window size */
 #endif
-       ;
-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)
+
+#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_NORMAL = 0,
+       TS_COPY = 1,
+       TS_IAC = 2,
+       TS_OPT = 3,
+       TS_SUB1 = 4,
+       TS_SUB2 = 5,
+       TS_CR = 6,
+};
+
+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;
+} FIX_ALIASING;
+#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)
+
+
+static void rawmode(void);
+static void cookmode(void);
+static void do_linemode(void);
+static void will_charmode(void);
+static void telopt(byte c);
+static void subneg(byte c);
+
+static void iac_flush(void)
 {
-       /*
-       telnet_sendwill(s, TELOPT_NAWS); TFLAG_SET(TELOPT_NAWS, WILL);
-       telnet_sendwill(s, TELOPT_TSPEED); TFLAG_SET(TELOPT_TSPEED, 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_SGA); TFLAG_SET(TELOPT_SGA, DO);
-       telnet_sendwill(s, TELOPT_LFLOW); TFLAG_SET(TELOPT_LFLOW, WILL);
-       telnet_sendwill(s, TELOPT_LINEMODE); TFLAG_SET(TELOPT_LINEMODE, 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);
-                   */
+       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.
+        */
+       byte outbuf[2 * DATABUFSIZE];
+       byte *p = (byte*)G.buf;
+       int j = 0;
+
+       for (; len > 0; len--, p++) {
+               byte c = *p;
+               if (c == 0x1d) {
+                       con_escape();
+                       return;
+               }
+               outbuf[j++] = c;
+               if (c == IAC)
+                       outbuf[j++] = c; /* IAC -> IAC IAC */
+               else if (c == '\r')
+                       outbuf[j++] = '\0'; /* CR -> CR NUL */
+       }
+       if (j > 0)
+               full_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 == TS_NORMAL) { /* most typical state */
+                       if (c == IAC) {
+                               cstart = i;
+                               G.telstate = TS_IAC;
                        }
-               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;
+                       else if (c == '\r') {
+                               cstart = i + 1;
+                               G.telstate = TS_CR;
                        }
-               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;
+                       /* No IACs were seen so far, no need to copy
+                        * bytes within G.buf: */
+                       continue;
+               }
+
+               switch (G.telstate) {
+               case TS_CR:
+                       /* Prev char was CR. If cur one is NUL, ignore it.
+                        * See RFC 1123 section 3.3.1 for discussion of telnet EOL handling.
+                        */
+                       G.telstate = TS_COPY;
+                       if (c == '\0')
+                               break;
+                       /* else: fall through - need to handle CR IAC ... properly */
+
+               case TS_COPY: /* Prev char was ordinary */
+                       /* Similar to NORMAL, but in TS_COPY we need to copy bytes */
+                       if (c == IAC)
+                               G.telstate = TS_IAC;
+                       else
+                               G.buf[cstart++] = c;
+                       if (c == '\r')
+                               G.telstate = TS_CR;
+                       break;
+
+               case TS_IAC: /* Prev char was IAC */
+                       if (c == IAC) { /* IAC IAC -> one IAC */
+                               G.buf[cstart++] = c;
+                               G.telstate = TS_COPY;
+                               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;
+                       /* DATA MARK must be added later */
+                       default:
+                               G.telstate = TS_COPY;
                        }
-                       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: /* Prev chars were IAC WILL/WONT/DO/DONT */
+                       telopt(c);
+                       G.telstate = TS_COPY;
+                       break;
+
+               case TS_SUB1: /* Subnegotiation */
+               case TS_SUB2: /* Subnegotiation */
+                       subneg(c); /* can change G.telstate */
                        break;
+               }
        }
+
+       if (G.telstate != TS_NORMAL) {
+               /* We had some IACs, or CR */
+               if (G.iaclen)
+                       iac_flush();
+               if (G.telstate == TS_COPY) /* we aren't in the middle of IAC */
+                       G.telstate = TS_NORMAL;
+               len = cstart;
+       }
+
+       if (len)
+               full_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 *p = "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 (*p)
+               put_iac(*p++);
+
+       put_iac(NEW_ENV_VALUE);
+
+       p = G.autologin;
+       while (*p)
+               put_iac(*p++);
+
+       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)
+{
+       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)
 {
-       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(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");
-                       }
+       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();
+       full_write1_str("\r\n");  /* sudden modec */
+}
+
+static void to_sga(void)
+{
+       /* 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);
+}
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+static void to_ttype(void)
+{
+       /* Tell server we will (or won't) do TTYPE */
+       if (G.ttype)
+               put_iac2(WILL, TELOPT_TTYPE);
+       else
+               put_iac2(WONT, TELOPT_TTYPE);
+}
+#endif
+
+#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)
+{
+       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;
+       }
+}
+
+/* subnegotiation -- ignore all (except TTYPE,NAWS) */
+static void 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 && G.ttype)
+                       put_iac_subopt(TELOPT_TTYPE, G.ttype);
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+               else
+               if (c == TELOPT_NEW_ENVIRON && G.autologin)
+                       put_iac_subopt_autologin();
+#endif
+               break;
+       case TS_SUB2:
+               if (c == SE) {
+                       G.telstate = TS_COPY;
+                       return;
                }
+               G.telstate = TS_SUB1;
+               break;
        }
-       
-    return;
 }
 
-static void telnet_shutdown(void)
+static void rawmode(void)
 {
-       printf("\n");
-       tcsetattr(STDIN, TCSANOW, &saved_tc);
+       if (G.do_termios)
+               tcsetattr(0, TCSADRAIN, &G.termios_raw);
 }
 
-#ifdef STANDALONE_TELNET
-void usage(const char *msg)
+static void cookmode(void)
 {
-       printf("%s", msg);
-       exit(0);
+       if (G.do_termios)
+               tcsetattr(0, TCSADRAIN, &G.termios_def);
 }
 
-int main(int argc, char **argv)
+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;
+       struct pollfd ufds[2];
+
+       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
-int telnet_main(int argc, char **argv)
+       argv++;
 #endif
-{
-    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;
-}
+       if (!*argv)
+               bb_show_usage();
+       host = *argv++;
+       port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23);
+       if (*argv) /* extra params?? */
+               bb_show_usage();
 
-/*
- * 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.
- */
+       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);
+
+       ufds[0].fd = STDIN_FILENO;
+       ufds[0].events = POLLIN;
+       ufds[1].fd = netfd;
+       ufds[1].events = POLLIN;
+
+       while (1) {
+               if (poll(ufds, 2, -1) < 0) {
+                       /* error, ignore and/or log something, bay go to loop */
+                       if (bb_got_signal)
+                               con_escape();
+                       else
+                               sleep(1);
+                       continue;
+               }
+
+// FIXME: reads can block. Need full bidirectional buffering.
+
+               if (ufds[0].revents) {
+                       len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE);
+                       if (len <= 0)
+                               doexit(EXIT_SUCCESS);
+                       TRACE(0, ("Read con: %d\n", len));
+                       handle_net_output(len);
+               }
+
+               if (ufds[1].revents) {
+                       len = safe_read(netfd, G.buf, DATABUFSIZE);
+                       if (len <= 0) {
+                               full_write1_str("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) */
+}