Sync up a few things. Add in a new telnet implementation (still
authorErik Andersen <andersen@codepoet.org>
Tue, 22 Feb 2000 17:17:45 +0000 (17:17 -0000)
committerErik Andersen <andersen@codepoet.org>
Tue, 22 Feb 2000 17:17:45 +0000 (17:17 -0000)
not perfect though).
 -Erik

applets/busybox.c
busybox.c
busybox.def.h
internal.h
networking/telnet.c [new file with mode: 0644]
telnet.c [new file with mode: 0644]

index a013f8ca1840e5809043c6e4a489bf2df21576cb..13f8db909dd613ab461d6f2d616d599639b650fc 100644 (file)
@@ -249,6 +249,9 @@ static const struct Applet applets[] = {
 #ifdef BB_TAR                                  //bin
        {"tar", tar_main},
 #endif
+#ifdef BB_TELNET                               //usr/bin
+       {"telnet", telnet_main},
+#endif
 #ifdef BB_TEE                                  //bin
        {"tee", tee_main},
 #endif
index a013f8ca1840e5809043c6e4a489bf2df21576cb..13f8db909dd613ab461d6f2d616d599639b650fc 100644 (file)
--- a/busybox.c
+++ b/busybox.c
@@ -249,6 +249,9 @@ static const struct Applet applets[] = {
 #ifdef BB_TAR                                  //bin
        {"tar", tar_main},
 #endif
+#ifdef BB_TELNET                               //usr/bin
+       {"telnet", telnet_main},
+#endif
 #ifdef BB_TEE                                  //bin
        {"tee", tee_main},
 #endif
index e531f1fed0a51bedf2f1d52b68c0ab1b0d708242..8ed5a9c667e50bcb80ff25a11bc7ecfb5b2e1156 100644 (file)
@@ -80,6 +80,8 @@
 #define BB_TAIL
 #define BB_TAR
 #define BB_TEE
+// Don't turn BB_TELNET on.  It doesn't work.
+#define BB_TELNET
 #define BB_TOUCH
 #define BB_TRUE_FALSE
 #define BB_TTY
index 5b71560c5db171406a0b87ab65891473f5f1cac4..4d035c962aa8ae29ef7c4d21e598db4119993ac3 100644 (file)
@@ -131,6 +131,7 @@ extern int syslogd_main(int argc, char **argv);
 extern int tail_main(int argc, char** argv);
 extern int tar_main(int argc, char** argv);
 extern int tee_main(int argc, char** argv);
+extern int telnet_main(int argc, char** argv);
 extern int touch_main(int argc, char** argv);
 extern int true_main(int argc, char** argv);
 extern int tput_main(int argc, char** argv);
diff --git a/networking/telnet.c b/networking/telnet.c
new file mode 100644 (file)
index 0000000..076728f
--- /dev/null
@@ -0,0 +1,503 @@
+/* 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
+ *
+ * Copyright (C) 2000 by Randolph Chung <tausq@debian.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * 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)
+{
+       /*
+       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);
+}
+
+static void telnet_suboptions(int net)
+{
+       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;
+                       }
+               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;
+                       }
+               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;
+                       }
+                       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:
+                       break;
+       }
+}
+
+static void sighandler(int sig)
+{
+       telnet_shutdown();
+       exit(0);
+}
+
+static int telnet_send(int tty, int net)
+{
+       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;
+}
+
+static int telnet_recv(int net, int tty)
+{
+       /* 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;
+               }
+                                       
+       }
+       if (ret == -1 && errno == EWOULDBLOCK) return 1;
+       return ret;
+}
+
+/* ******************************************************************* */
+static void telnet_init(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);
+}
+
+static void telnet_start(char *hostname, int port)
+{
+    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);
+               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;
+}
+
+static void telnet_shutdown(void)
+{
+       printf("\n");
+       tcsetattr(STDIN, TCSANOW, &saved_tc);
+}
+
+#ifdef STANDALONE_TELNET
+void usage(const char *msg)
+{
+       printf("%s", msg);
+       exit(0);
+}
+
+int main(int argc, char **argv)
+#else
+int telnet_main(int argc, char **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;
+}
+
+/*
+ * 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.
+ */
diff --git a/telnet.c b/telnet.c
new file mode 100644 (file)
index 0000000..076728f
--- /dev/null
+++ b/telnet.c
@@ -0,0 +1,503 @@
+/* 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
+ *
+ * Copyright (C) 2000 by Randolph Chung <tausq@debian.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * 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)
+{
+       /*
+       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);
+}
+
+static void telnet_suboptions(int net)
+{
+       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;
+                       }
+               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;
+                       }
+               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;
+                       }
+                       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:
+                       break;
+       }
+}
+
+static void sighandler(int sig)
+{
+       telnet_shutdown();
+       exit(0);
+}
+
+static int telnet_send(int tty, int net)
+{
+       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;
+}
+
+static int telnet_recv(int net, int tty)
+{
+       /* 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;
+               }
+                                       
+       }
+       if (ret == -1 && errno == EWOULDBLOCK) return 1;
+       return ret;
+}
+
+/* ******************************************************************* */
+static void telnet_init(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);
+}
+
+static void telnet_start(char *hostname, int port)
+{
+    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);
+               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;
+}
+
+static void telnet_shutdown(void)
+{
+       printf("\n");
+       tcsetattr(STDIN, TCSANOW, &saved_tc);
+}
+
+#ifdef STANDALONE_TELNET
+void usage(const char *msg)
+{
+       printf("%s", msg);
+       exit(0);
+}
+
+int main(int argc, char **argv)
+#else
+int telnet_main(int argc, char **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;
+}
+
+/*
+ * 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.
+ */