diff options
author | Eric Andersen | 2000-06-14 20:42:57 +0000 |
---|---|---|
committer | Eric Andersen | 2000-06-14 20:42:57 +0000 |
commit | 28c70b3a16c17319c184a90eb8f164527c98b4f8 (patch) | |
tree | 672d4d34162d111ef8166bfdb45d3cad28545bd4 | |
parent | e97da4007913bd29d8a18d038de29d9d4549163d (diff) | |
download | busybox-28c70b3a16c17319c184a90eb8f164527c98b4f8.zip busybox-28c70b3a16c17319c184a90eb8f164527c98b4f8.tar.gz |
Integrate Tomi Ollila's telnet. Costs 3k. :)
-Erik
-rw-r--r-- | Changelog | 2 | ||||
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | busybox.def.h | 1 | ||||
-rw-r--r-- | networking/telnet.c | 1026 | ||||
-rw-r--r-- | telnet.c | 1026 | ||||
-rw-r--r-- | tests/cp_tests.mk | 133 |
6 files changed, 1274 insertions, 915 deletions
@@ -17,6 +17,8 @@ * Added 'grep -v' option (inverted search) and updated docs accordingly. -beppu * Wrote which + * Replaced the telnet implementation with one written by Tomi Ollila <too@iki.fi> + It works great and costs 3k. * BusyBox sh (lash) now supports being used as a standalone shell. When BB_FEATURE_STANDALONE_SHELL is defined, all the busybox commands may be invoked as shell internals. Best used when compiling staticly @@ -18,7 +18,6 @@ around to it some time. If you have any good ideas, please let me know. Bugs that need fixing before the 0.44 release goes out the door: - - merge telnet client from Tomi Ollila - "more" doesn't accept " " to scroll by one page when BB_FEATURE_USE_TERMIOS is not on. - doc / website updates to tell what the real mailing list is for busybox. diff --git a/busybox.def.h b/busybox.def.h index 50ad88c..3556b1e 100644 --- a/busybox.def.h +++ b/busybox.def.h @@ -89,7 +89,6 @@ #define BB_TAR #define BB_TEE #define BB_TEST -// Don't bother turning BB_TELNET on. It doesn't work properly yet. //#define BB_TELNET #define BB_TOUCH #define BB_TR diff --git a/networking/telnet.c b/networking/telnet.c index 8b6d5f5..f276035 100644 --- a/networking/telnet.c +++ b/networking/telnet.c @@ -1,8 +1,12 @@ +/* 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 + * + * Created: Thu Apr 7 13:29:41 1994 too + * Last modified: Fri Jun 9 14:34:24 2000 too * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,490 +22,652 @@ * 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. + * HISTORY + * Revision 3.1 1994/04/17 11:31:54 too + * initial revision + * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen + * <andersen@lineo.com> * - * Original copyright notice is retained at the end of this file. */ + #include "internal.h" -#include <stdio.h> -#include <stdlib.h> +#include <termios.h> #include <unistd.h> -#include <ctype.h> -#include <signal.h> #include <errno.h> -#include <netdb.h> -#include <termios.h> -#include <netinet/in.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <arpa/telnet.h> #include <sys/types.h> #include <sys/socket.h> -#include <sys/ioctl.h> -#define TELOPTS -#include <arpa/telnet.h> -#include <arpa/inet.h> +#include <netinet/in.h> +#include <netdb.h> + +#if 0 +#define DOTRACE 1 +#endif + +#if DOTRACE +#include <arpa/inet.h> /* for inet_ntoa()... */ +#define TRACE(x, y) do { if (x) printf y; } while (0) +#else +#define TRACE(x, y) +#endif + +#if 0 +#define USE_POLL +#include <sys/poll.h> +#else +#include <sys/time.h> +#endif + +#define DATABUFSIZE 128 +#define IACBUFSIZE 128 -static int STDIN = 0; -static int STDOUT = 1; -static const char *telnet_usage = "telnet host [port]\n" +#define CHM_TRY 0 +#define CHM_ON 1 +#define CHM_OFF 2 + +#define UF_ECHO 0x01 +#define UF_SGA 0x02 + +#define TS_0 1 +#define TS_IAC 2 +#define TS_OPT 3 +#define TS_SUB1 4 +#define TS_SUB2 5 + +#define WriteCS(fd, str) write(fd, str, sizeof str -1) + +typedef unsigned char byte; + +/* use globals to reduce size ??? */ /* test this hypothesis later */ +struct Globalvars { + int netfd; /* console fd:s are 0 and 1 (and 2) */ + /* same buffer used both for network and console read/write */ + char * buf; /* allocating so static size is smaller */ + short len; + byte telstate; /* telnet negotiation state from network input */ + byte telwish; /* DO, DONT, WILL, WONT */ + byte charmode; + byte telflags; + byte gotsig; + /* buffer to handle telnet negotiations */ + char * iacbuf; + short iaclen; /* could even use byte */ + struct termios termios_def; + struct termios termios_raw; +} G; + +#define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */ + +#ifdef USE_GLOBALVAR_PTR +struct Globalvars * Gptr; +#define G (*Gptr) +#else +struct Globalvars G; +#endif + +static inline void iacflush() +{ + write(G.netfd, G.iacbuf, G.iaclen); + G.iaclen = 0; +} + +/* Function prototypes */ +static int getport(char * p); +static struct in_addr getserver(char * p); +static int create_socket(); +static void setup_sockaddr_in(struct sockaddr_in * addr, int port); +static int remote_connect(struct in_addr addr, int port); +static void rawmode(); +static void cookmode(); +static void do_linemode(); +static void will_charmode(); +static void telopt(byte c); +static int subneg(byte c); +#if 0 +static int local_bind(int port); +#endif + +/* Some globals */ +static int one = 1; +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" + "\nTelnet is used to establish interactive communication with another\n" + "computer over a network using the TELNET protocol.\n" #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) + + +static void doexit(int ev) +{ + cookmode(); + exit(ev); +} + +static void conescape() +{ + char b; + + if (G.gotsig) /* came from line mode... go raw */ + rawmode(); + + WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n" + " l go to line mode\r\n" + " c go to character mode\r\n" + " z suspend telnet\r\n" + " e exit telnet\r\n"); + + if (read(0, &b, 1) <= 0) + doexit(1); + + switch (b) + { + case 'l': + if (!G.gotsig) + { + do_linemode(); + goto rrturn; + } + break; + case 'c': + if (G.gotsig) + { + will_charmode(); + goto rrturn; + } + break; + case 'z': + cookmode(); + kill(0, SIGTSTP); + rawmode(); + break; + case 'e': + doexit(0); + } + + WriteCS(1, "continuing...\r\n"); + + if (G.gotsig) + cookmode(); + + rrturn: + G.gotsig = 0; + +} +static void handlenetoutput() { - /* - 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); + /* here we could do smart tricks how to handle 0xFF:s in output + * stream like writing twice every sequence of FF:s (thus doing + * many write()s. But I think interactive telnet application does + * not need to be 100% 8-bit clean, so changing every 0xff:s to + * 0x7f:s */ + + int i; + byte * p = G.buf; + + for (i = G.len; i > 0; i--, p++) + { + if (*p == 0x1d) + { + conescape(); + return; + } + if (*p == 0xff) + *p = 0x7f; + } + write(G.netfd, G.buf, G.len); } -static void telnet_suboptions(int net) + +static void handlenetinput() { - 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); - } + int i; + int cstart = 0; + + for (i = 0; i < G.len; i++) + { + byte c = G.buf[i]; + + if (G.telstate == 0) /* most of the time state == 0 */ + { + if (c == IAC) + { + cstart = i; + G.telstate = TS_IAC; } - break; - default: - break; + } + else + switch (G.telstate) + { + case TS_0: + if (c == IAC) + G.telstate = TS_IAC; + else + G.buf[cstart++] = c; + break; + + case TS_IAC: + if (c == IAC) /* IAC IAC -> 0xFF */ + { + G.buf[cstart++] = c; + G.telstate = TS_0; + break; + } + /* else */ + switch (c) + { + case SB: + G.telstate = TS_SUB1; + break; + case DO: + case DONT: + case WILL: + case WONT: + G.telwish = c; + G.telstate = TS_OPT; + break; + default: + G.telstate = TS_0; /* DATA MARK must be added later */ + } + break; + case TS_OPT: /* WILL, WONT, DO, DONT */ + telopt(c); + G.telstate = TS_0; + break; + case TS_SUB1: /* Subnegotiation */ + case TS_SUB2: /* Subnegotiation */ + if (subneg(c) == TRUE) + G.telstate = TS_0; + break; + } } + if (G.telstate) + { + if (G.iaclen) iacflush(); + if (G.telstate == TS_0) G.telstate = 0; + + G.len = cstart; + } + + if (G.len) + write(1, G.buf, G.len); } -static void sighandler(int sig) + +/* ******************************* */ + +static inline void putiac(int c) { - telnet_shutdown(); - exit(0); + G.iacbuf[G.iaclen++] = c; } -static int telnet_send(int tty, int net) + +static void putiac2(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 (G.iaclen + 3 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(wwdd); + putiac(c); +} + +#if 0 +static void putiac1(byte c) +{ + if (G.iaclen + 2 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(c); +} +#endif + +/* void putiacstring (subneg strings) */ + +/* ******************************* */ + +char const escapecharis[] = "\r\nEscape character is "; + +static void setConMode() +{ + if (G.telflags & UF_ECHO) + { + if (G.charmode == CHM_TRY) { + G.charmode = CHM_ON; + fprintf(stdout, "\r\nEntering character mode%s'^]'.\r\n", escapecharis); + rawmode(); + } + } + else + { + if (G.charmode != CHM_OFF) { + G.charmode = CHM_OFF; + fprintf(stdout, "\r\nEntering line mode%s'^C'.\r\n", escapecharis); + cookmode(); } } - if (ret == -1 && errno == EWOULDBLOCK) return 1; - return ret; } -static int telnet_recv(int net, int tty) +/* ******************************* */ + +static void will_charmode() { - /* 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; - } - + G.charmode = CHM_TRY; + G.telflags |= (UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DO, TELOPT_ECHO); + putiac2(DO, TELOPT_SGA); + iacflush(); +} + +static void do_linemode() +{ + G.charmode = CHM_TRY; + G.telflags &= ~(UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DONT, TELOPT_ECHO); + putiac2(DONT, TELOPT_SGA); + iacflush(); +} + +/* ******************************* */ + +static inline void to_notsup(char c) +{ + if (G.telwish == WILL) putiac2(DONT, c); + else if (G.telwish == DO) putiac2(WONT, c); +} + +static inline void to_echo() +{ + /* if server requests ECHO, don't agree */ + if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; } + else if (G.telwish == DONT) return; + + if (G.telflags & UF_ECHO) + { + if (G.telwish == WILL) + return; } - if (ret == -1 && errno == EWOULDBLOCK) return 1; - return ret; + else + if (G.telwish == WONT) + return; + + if (G.charmode != CHM_OFF) + G.telflags ^= UF_ECHO; + + if (G.telflags & UF_ECHO) + putiac2(DO, TELOPT_ECHO); + else + putiac2(DONT, TELOPT_ECHO); + + setConMode(); + WriteCS(1, "\r\n"); /* sudden modec */ } -/* ******************************************************************* */ -static void telnet_init(void) +static inline void to_sga() { - 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); + /* daemon always sends will/wont, client do/dont */ + + if (G.telflags & UF_SGA) + { + if (G.telwish == WILL) + return; + } + else + if (G.telwish == WONT) + return; + + if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */ + putiac2(DO, TELOPT_SGA); + else + putiac2(DONT, TELOPT_SGA); + + return; } -static void telnet_start(char *hostname, int port) +static void telopt(byte 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); - return; + switch (c) + { + case TELOPT_ECHO: to_echo(c); break; + case TELOPT_SGA: to_sga(c); break; + default: to_notsup(c); break; } - 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)); +/* subnegotiation -- ignore all */ + +static int subneg(byte c) +{ + switch (G.telstate) + { + case TS_SUB1: + if (c == IAC) + G.telstate = TS_SUB2; + break; + case TS_SUB2: + if (c == SE) + return TRUE; + G.telstate = TS_SUB1; + /* break; */ + } + return FALSE; +} + +/* ******************************* */ + +static void fgotsig(int sig) +{ + G.gotsig = sig; +} + + +static void rawmode() +{ + tcsetattr(0, TCSADRAIN, &G.termios_raw); +} + +static void cookmode() +{ + tcsetattr(0, TCSADRAIN, &G.termios_def); +} + +extern int telnet_main(int argc, char** argv) +{ + struct in_addr host; + int port; +#ifdef USE_POLL + struct pollfd ufds[2]; +#else + fd_set readfds; + int maxfd; +#endif + + + memset(&G, 0, sizeof G); + + if (tcgetattr(0, &G.termios_def) < 0) + exit(1); - 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); + G.termios_raw = G.termios_def; + + cfmakeraw(&G.termios_raw); - if (port == TELNETPORT) telnet_setoptions(s); + if (argc < 2) usage(telnet_usage); + port = (argc > 2)? getport(argv[2]): 23; - /* shuttle data back and forth between tty and socket */ - while (1) { - FD_ZERO(&rfds); - FD_ZERO(&wfds); + G.buf = xmalloc(DATABUFSIZE); + G.iacbuf = xmalloc(IACBUFSIZE); - FD_SET(s, &rfds); - /* FD_SET(s, &wfds); */ - FD_SET(STDIN, &rfds); - /* FD_SET(STDOUT, &wfds); */ + host = getserver(argv[1]); + + G.netfd = remote_connect(host, port); + + signal(SIGINT, fgotsig); + +#ifdef USE_POLL + ufds[0].fd = 0; ufds[1].fd = G.netfd; + ufds[0].events = ufds[1].events = POLLIN; +#else + FD_ZERO(&readfds); + FD_SET(0, &readfds); + FD_SET(G.netfd, &readfds); + maxfd = G.netfd + 1; +#endif - 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"); + while (1) + { +#ifndef USE_POLL + fd_set rfds = readfds; + + switch (select(maxfd, &rfds, NULL, NULL, NULL)) +#else + switch (poll(ufds, 2, -1)) +#endif + { + case 0: + /* timeout */ + case -1: + /* error, ignore and/or log something, bay go to loop */ + if (G.gotsig) + conescape(); + else + sleep(1); + break; + default: + +#ifdef USE_POLL + if (ufds[0].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(0, &rfds)) +#endif + { + G.len = read(0, G.buf, DATABUFSIZE); + + if (G.len <= 0) + doexit(0); + + TRACE(0, ("Read con: %d\n", G.len)); + + handlenetoutput(); } - 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"); + +#ifdef USE_POLL + if (ufds[1].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(G.netfd, &rfds)) +#endif + { + G.len = read(G.netfd, G.buf, DATABUFSIZE); + + if (G.len <= 0) + { + WriteCS(1, "Connection closed by foreign host.\r\n"); + doexit(1); + } + TRACE(0, ("Read netfd (%d): %d\n", G.netfd, G.len)); + + handlenetinput(); } } } - - return; } -static void telnet_shutdown(void) +static int getport(char * p) { - printf("\n"); - tcsetattr(STDIN, TCSANOW, &saved_tc); + unsigned int port = atoi(p); + + if ((unsigned)(port - 1 ) > 65534) + { + fatalError("%s: bad port number\n", p); + } + return port; } -#ifdef STANDALONE_TELNET -void usage(const char *msg) +static struct in_addr getserver(char * host) { - printf("%s", msg); - exit(0); + struct in_addr addr; + + struct hostent * he; + if ((he = gethostbyname(host)) == NULL) + { + fatalError("%s: Unkonwn host\n", host); + } + memcpy(&addr, he->h_addr, sizeof addr); + + TRACE(1, ("addr: %s\n", inet_ntoa(addr))); + + return addr; +} + +static int create_socket() +{ + return socket(AF_INET, SOCK_STREAM, 0); } -int main(int argc, char **argv) -#else -int telnet_main(int argc, char **argv) -#endif +static void setup_sockaddr_in(struct sockaddr_in * addr, int port) +{ + memset(addr, 0, sizeof addr); + addr->sin_family = AF_INET; + addr->sin_port = htons(port); +} + +#if 0 +static int local_bind(int port) { - int port = TELNETPORT; + struct sockaddr_in s_addr; + int s = create_socket(); + + setup_sockaddr_in(&s_addr, port); + + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); + + if (bind(s, &s_addr, sizeof s_addr) < 0) + { + char * e = sys_errlist[errno]; + syserrorexit("bind"); + exit(1); + } + listen(s, 1); - 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; + return s; +} +#endif + +static int remote_connect(struct in_addr addr, int port) +{ + struct sockaddr_in s_addr; + int s = create_socket(); + + setup_sockaddr_in(&s_addr, port); + s_addr.sin_addr = addr; + + setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); + + if (connect(s, (struct sockaddr *)&s_addr, sizeof s_addr) < 0) + { + fatalError("Unable to connect to remote host: %s\n", strerror(errno)); + } + return s; } /* - * 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. - */ +Local Variables: +c-file-style: "linux" +c-basic-offset: 4 +tab-width: 4 +End: +*/ + @@ -1,8 +1,12 @@ +/* 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 + * + * Created: Thu Apr 7 13:29:41 1994 too + * Last modified: Fri Jun 9 14:34:24 2000 too * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,490 +22,652 @@ * 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. + * HISTORY + * Revision 3.1 1994/04/17 11:31:54 too + * initial revision + * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen + * <andersen@lineo.com> * - * Original copyright notice is retained at the end of this file. */ + #include "internal.h" -#include <stdio.h> -#include <stdlib.h> +#include <termios.h> #include <unistd.h> -#include <ctype.h> -#include <signal.h> #include <errno.h> -#include <netdb.h> -#include <termios.h> -#include <netinet/in.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <arpa/telnet.h> #include <sys/types.h> #include <sys/socket.h> -#include <sys/ioctl.h> -#define TELOPTS -#include <arpa/telnet.h> -#include <arpa/inet.h> +#include <netinet/in.h> +#include <netdb.h> + +#if 0 +#define DOTRACE 1 +#endif + +#if DOTRACE +#include <arpa/inet.h> /* for inet_ntoa()... */ +#define TRACE(x, y) do { if (x) printf y; } while (0) +#else +#define TRACE(x, y) +#endif + +#if 0 +#define USE_POLL +#include <sys/poll.h> +#else +#include <sys/time.h> +#endif + +#define DATABUFSIZE 128 +#define IACBUFSIZE 128 -static int STDIN = 0; -static int STDOUT = 1; -static const char *telnet_usage = "telnet host [port]\n" +#define CHM_TRY 0 +#define CHM_ON 1 +#define CHM_OFF 2 + +#define UF_ECHO 0x01 +#define UF_SGA 0x02 + +#define TS_0 1 +#define TS_IAC 2 +#define TS_OPT 3 +#define TS_SUB1 4 +#define TS_SUB2 5 + +#define WriteCS(fd, str) write(fd, str, sizeof str -1) + +typedef unsigned char byte; + +/* use globals to reduce size ??? */ /* test this hypothesis later */ +struct Globalvars { + int netfd; /* console fd:s are 0 and 1 (and 2) */ + /* same buffer used both for network and console read/write */ + char * buf; /* allocating so static size is smaller */ + short len; + byte telstate; /* telnet negotiation state from network input */ + byte telwish; /* DO, DONT, WILL, WONT */ + byte charmode; + byte telflags; + byte gotsig; + /* buffer to handle telnet negotiations */ + char * iacbuf; + short iaclen; /* could even use byte */ + struct termios termios_def; + struct termios termios_raw; +} G; + +#define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */ + +#ifdef USE_GLOBALVAR_PTR +struct Globalvars * Gptr; +#define G (*Gptr) +#else +struct Globalvars G; +#endif + +static inline void iacflush() +{ + write(G.netfd, G.iacbuf, G.iaclen); + G.iaclen = 0; +} + +/* Function prototypes */ +static int getport(char * p); +static struct in_addr getserver(char * p); +static int create_socket(); +static void setup_sockaddr_in(struct sockaddr_in * addr, int port); +static int remote_connect(struct in_addr addr, int port); +static void rawmode(); +static void cookmode(); +static void do_linemode(); +static void will_charmode(); +static void telopt(byte c); +static int subneg(byte c); +#if 0 +static int local_bind(int port); +#endif + +/* Some globals */ +static int one = 1; +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" + "\nTelnet is used to establish interactive communication with another\n" + "computer over a network using the TELNET protocol.\n" #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) + + +static void doexit(int ev) +{ + cookmode(); + exit(ev); +} + +static void conescape() +{ + char b; + + if (G.gotsig) /* came from line mode... go raw */ + rawmode(); + + WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n" + " l go to line mode\r\n" + " c go to character mode\r\n" + " z suspend telnet\r\n" + " e exit telnet\r\n"); + + if (read(0, &b, 1) <= 0) + doexit(1); + + switch (b) + { + case 'l': + if (!G.gotsig) + { + do_linemode(); + goto rrturn; + } + break; + case 'c': + if (G.gotsig) + { + will_charmode(); + goto rrturn; + } + break; + case 'z': + cookmode(); + kill(0, SIGTSTP); + rawmode(); + break; + case 'e': + doexit(0); + } + + WriteCS(1, "continuing...\r\n"); + + if (G.gotsig) + cookmode(); + + rrturn: + G.gotsig = 0; + +} +static void handlenetoutput() { - /* - 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); + /* here we could do smart tricks how to handle 0xFF:s in output + * stream like writing twice every sequence of FF:s (thus doing + * many write()s. But I think interactive telnet application does + * not need to be 100% 8-bit clean, so changing every 0xff:s to + * 0x7f:s */ + + int i; + byte * p = G.buf; + + for (i = G.len; i > 0; i--, p++) + { + if (*p == 0x1d) + { + conescape(); + return; + } + if (*p == 0xff) + *p = 0x7f; + } + write(G.netfd, G.buf, G.len); } -static void telnet_suboptions(int net) + +static void handlenetinput() { - 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); - } + int i; + int cstart = 0; + + for (i = 0; i < G.len; i++) + { + byte c = G.buf[i]; + + if (G.telstate == 0) /* most of the time state == 0 */ + { + if (c == IAC) + { + cstart = i; + G.telstate = TS_IAC; } - break; - default: - break; + } + else + switch (G.telstate) + { + case TS_0: + if (c == IAC) + G.telstate = TS_IAC; + else + G.buf[cstart++] = c; + break; + + case TS_IAC: + if (c == IAC) /* IAC IAC -> 0xFF */ + { + G.buf[cstart++] = c; + G.telstate = TS_0; + break; + } + /* else */ + switch (c) + { + case SB: + G.telstate = TS_SUB1; + break; + case DO: + case DONT: + case WILL: + case WONT: + G.telwish = c; + G.telstate = TS_OPT; + break; + default: + G.telstate = TS_0; /* DATA MARK must be added later */ + } + break; + case TS_OPT: /* WILL, WONT, DO, DONT */ + telopt(c); + G.telstate = TS_0; + break; + case TS_SUB1: /* Subnegotiation */ + case TS_SUB2: /* Subnegotiation */ + if (subneg(c) == TRUE) + G.telstate = TS_0; + break; + } } + if (G.telstate) + { + if (G.iaclen) iacflush(); + if (G.telstate == TS_0) G.telstate = 0; + + G.len = cstart; + } + + if (G.len) + write(1, G.buf, G.len); } -static void sighandler(int sig) + +/* ******************************* */ + +static inline void putiac(int c) { - telnet_shutdown(); - exit(0); + G.iacbuf[G.iaclen++] = c; } -static int telnet_send(int tty, int net) + +static void putiac2(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 (G.iaclen + 3 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(wwdd); + putiac(c); +} + +#if 0 +static void putiac1(byte c) +{ + if (G.iaclen + 2 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(c); +} +#endif + +/* void putiacstring (subneg strings) */ + +/* ******************************* */ + +char const escapecharis[] = "\r\nEscape character is "; + +static void setConMode() +{ + if (G.telflags & UF_ECHO) + { + if (G.charmode == CHM_TRY) { + G.charmode = CHM_ON; + fprintf(stdout, "\r\nEntering character mode%s'^]'.\r\n", escapecharis); + rawmode(); + } + } + else + { + if (G.charmode != CHM_OFF) { + G.charmode = CHM_OFF; + fprintf(stdout, "\r\nEntering line mode%s'^C'.\r\n", escapecharis); + cookmode(); } } - if (ret == -1 && errno == EWOULDBLOCK) return 1; - return ret; } -static int telnet_recv(int net, int tty) +/* ******************************* */ + +static void will_charmode() { - /* 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; - } - + G.charmode = CHM_TRY; + G.telflags |= (UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DO, TELOPT_ECHO); + putiac2(DO, TELOPT_SGA); + iacflush(); +} + +static void do_linemode() +{ + G.charmode = CHM_TRY; + G.telflags &= ~(UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DONT, TELOPT_ECHO); + putiac2(DONT, TELOPT_SGA); + iacflush(); +} + +/* ******************************* */ + +static inline void to_notsup(char c) +{ + if (G.telwish == WILL) putiac2(DONT, c); + else if (G.telwish == DO) putiac2(WONT, c); +} + +static inline void to_echo() +{ + /* if server requests ECHO, don't agree */ + if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; } + else if (G.telwish == DONT) return; + + if (G.telflags & UF_ECHO) + { + if (G.telwish == WILL) + return; } - if (ret == -1 && errno == EWOULDBLOCK) return 1; - return ret; + else + if (G.telwish == WONT) + return; + + if (G.charmode != CHM_OFF) + G.telflags ^= UF_ECHO; + + if (G.telflags & UF_ECHO) + putiac2(DO, TELOPT_ECHO); + else + putiac2(DONT, TELOPT_ECHO); + + setConMode(); + WriteCS(1, "\r\n"); /* sudden modec */ } -/* ******************************************************************* */ -static void telnet_init(void) +static inline void to_sga() { - 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); + /* daemon always sends will/wont, client do/dont */ + + if (G.telflags & UF_SGA) + { + if (G.telwish == WILL) + return; + } + else + if (G.telwish == WONT) + return; + + if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */ + putiac2(DO, TELOPT_SGA); + else + putiac2(DONT, TELOPT_SGA); + + return; } -static void telnet_start(char *hostname, int port) +static void telopt(byte 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); - return; + switch (c) + { + case TELOPT_ECHO: to_echo(c); break; + case TELOPT_SGA: to_sga(c); break; + default: to_notsup(c); break; } - 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)); +/* subnegotiation -- ignore all */ + +static int subneg(byte c) +{ + switch (G.telstate) + { + case TS_SUB1: + if (c == IAC) + G.telstate = TS_SUB2; + break; + case TS_SUB2: + if (c == SE) + return TRUE; + G.telstate = TS_SUB1; + /* break; */ + } + return FALSE; +} + +/* ******************************* */ + +static void fgotsig(int sig) +{ + G.gotsig = sig; +} + + +static void rawmode() +{ + tcsetattr(0, TCSADRAIN, &G.termios_raw); +} + +static void cookmode() +{ + tcsetattr(0, TCSADRAIN, &G.termios_def); +} + +extern int telnet_main(int argc, char** argv) +{ + struct in_addr host; + int port; +#ifdef USE_POLL + struct pollfd ufds[2]; +#else + fd_set readfds; + int maxfd; +#endif + + + memset(&G, 0, sizeof G); + + if (tcgetattr(0, &G.termios_def) < 0) + exit(1); - 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); + G.termios_raw = G.termios_def; + + cfmakeraw(&G.termios_raw); - if (port == TELNETPORT) telnet_setoptions(s); + if (argc < 2) usage(telnet_usage); + port = (argc > 2)? getport(argv[2]): 23; - /* shuttle data back and forth between tty and socket */ - while (1) { - FD_ZERO(&rfds); - FD_ZERO(&wfds); + G.buf = xmalloc(DATABUFSIZE); + G.iacbuf = xmalloc(IACBUFSIZE); - FD_SET(s, &rfds); - /* FD_SET(s, &wfds); */ - FD_SET(STDIN, &rfds); - /* FD_SET(STDOUT, &wfds); */ + host = getserver(argv[1]); + + G.netfd = remote_connect(host, port); + + signal(SIGINT, fgotsig); + +#ifdef USE_POLL + ufds[0].fd = 0; ufds[1].fd = G.netfd; + ufds[0].events = ufds[1].events = POLLIN; +#else + FD_ZERO(&readfds); + FD_SET(0, &readfds); + FD_SET(G.netfd, &readfds); + maxfd = G.netfd + 1; +#endif - 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"); + while (1) + { +#ifndef USE_POLL + fd_set rfds = readfds; + + switch (select(maxfd, &rfds, NULL, NULL, NULL)) +#else + switch (poll(ufds, 2, -1)) +#endif + { + case 0: + /* timeout */ + case -1: + /* error, ignore and/or log something, bay go to loop */ + if (G.gotsig) + conescape(); + else + sleep(1); + break; + default: + +#ifdef USE_POLL + if (ufds[0].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(0, &rfds)) +#endif + { + G.len = read(0, G.buf, DATABUFSIZE); + + if (G.len <= 0) + doexit(0); + + TRACE(0, ("Read con: %d\n", G.len)); + + handlenetoutput(); } - 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"); + +#ifdef USE_POLL + if (ufds[1].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(G.netfd, &rfds)) +#endif + { + G.len = read(G.netfd, G.buf, DATABUFSIZE); + + if (G.len <= 0) + { + WriteCS(1, "Connection closed by foreign host.\r\n"); + doexit(1); + } + TRACE(0, ("Read netfd (%d): %d\n", G.netfd, G.len)); + + handlenetinput(); } } } - - return; } -static void telnet_shutdown(void) +static int getport(char * p) { - printf("\n"); - tcsetattr(STDIN, TCSANOW, &saved_tc); + unsigned int port = atoi(p); + + if ((unsigned)(port - 1 ) > 65534) + { + fatalError("%s: bad port number\n", p); + } + return port; } -#ifdef STANDALONE_TELNET -void usage(const char *msg) +static struct in_addr getserver(char * host) { - printf("%s", msg); - exit(0); + struct in_addr addr; + + struct hostent * he; + if ((he = gethostbyname(host)) == NULL) + { + fatalError("%s: Unkonwn host\n", host); + } + memcpy(&addr, he->h_addr, sizeof addr); + + TRACE(1, ("addr: %s\n", inet_ntoa(addr))); + + return addr; +} + +static int create_socket() +{ + return socket(AF_INET, SOCK_STREAM, 0); } -int main(int argc, char **argv) -#else -int telnet_main(int argc, char **argv) -#endif +static void setup_sockaddr_in(struct sockaddr_in * addr, int port) +{ + memset(addr, 0, sizeof addr); + addr->sin_family = AF_INET; + addr->sin_port = htons(port); +} + +#if 0 +static int local_bind(int port) { - int port = TELNETPORT; + struct sockaddr_in s_addr; + int s = create_socket(); + + setup_sockaddr_in(&s_addr, port); + + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); + + if (bind(s, &s_addr, sizeof s_addr) < 0) + { + char * e = sys_errlist[errno]; + syserrorexit("bind"); + exit(1); + } + listen(s, 1); - 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; + return s; +} +#endif + +static int remote_connect(struct in_addr addr, int port) +{ + struct sockaddr_in s_addr; + int s = create_socket(); + + setup_sockaddr_in(&s_addr, port); + s_addr.sin_addr = addr; + + setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); + + if (connect(s, (struct sockaddr *)&s_addr, sizeof s_addr) < 0) + { + fatalError("Unable to connect to remote host: %s\n", strerror(errno)); + } + return s; } /* - * 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. - */ +Local Variables: +c-file-style: "linux" +c-basic-offset: 4 +tab-width: 4 +End: +*/ + diff --git a/tests/cp_tests.mk b/tests/cp_tests.mk index 2082d73..1dc4c5a 100644 --- a/tests/cp_tests.mk +++ b/tests/cp_tests.mk @@ -14,7 +14,13 @@ clean:: cp_clean cp_clean: - rm -rf cp_tests cp_*.{gnu,bb} cp -cp_tests: cp_clean cp +cp_tests: cp_clean cp check_exists check_simple_cp check_cp_symlnk \ + check_cp_symlink_w_a check_cp_files_to_dir check_cp_files_to_dir_w_d \ + check_cp_files_to_dir_w_p check_cp_files_to_dir_w_p_and_d \ + check_cp_dir_to_dir_wo_a check_cp_dir_to_dir_w_a \ + check_cp_dir_to_dir_w_a_take_two + +check_exists: @echo; @echo "No output from diff means busybox cp is functioning properly."; @echo "Some tests might show timestamp differences that are Ok."; @@ -27,6 +33,7 @@ cp_tests: cp_clean cp @echo; mkdir cp_tests; +check_simple_cp: @echo Copy a file to a copy of the file; @echo ------------------------------; cd cp_tests; \ @@ -52,6 +59,7 @@ cp_tests: cp_clean cp @echo; rm -rf cp_tests/*; +check_cp_symlnk: @echo; echo Copy a file pointed to by a symlink; @echo ------------------------------; cd cp_tests; \ @@ -84,6 +92,7 @@ cp_tests: cp_clean cp @echo; rm -rf cp_tests/* +check_cp_symlink_w_a: @echo; echo Copy a symlink, useing the -a switch.; @echo ------------------------------; cd cp_tests; \ @@ -115,38 +124,8 @@ cp_tests: cp_clean cp @echo; rm -rf cp_tests/*; - @echo; echo Copy a directory into another directory with the -a switch; - @echo ------------------------------; - cd cp_tests; \ - mkdir here there; \ - echo A file > here/afile; \ - mkdir here/adir; \ - touch here/adir/afileinadir; \ - ln -s $$(pwd) here/alink; - - @echo; - cd cp_tests; \ - ls -lR . > ../cp_a_dir_dir.gnu; \ - ${GCP} -a here/ there/; \ - ls -lR . >> ../cp_a_dir_dir.gnu; - - @echo; - rm -rf cp_tests/there/*; - - sleep 1; - - @echo; - cd cp_tests; \ - ls -lR . > ../cp_a_dir_dir.bb; \ - ${BCP} -a here/ there/; \ - ls -lR . >> ../cp_a_dir_dir.bb; - - @echo; - diff -u cp_a_dir_dir.gnu cp_a_dir_dir.bb; - - @echo; - rm -rf cp_tests/*; +check_cp_files_to_dir: # Copy a set of files to a directory. @echo; echo Copy a set of files to a directory.; @echo ------------------------------; @@ -174,6 +153,7 @@ cp_tests: cp_clean cp @echo; rm -rf cp_tests/*; +check_cp_files_to_dir_w_d: # Copy a set of files to a directory with the -d switch. @echo; echo Copy a set of files to a directory with the -d switch.; @echo ------------------------------; @@ -203,6 +183,7 @@ cp_tests: cp_clean cp @echo; rm -rf cp_tests/{afile{1,2},symlink1,there1}; +check_cp_files_to_dir_w_p: # Copy a set of files to a directory with the -p switch. @echo; echo Copy a set of files to a directory with the -p switch.; @echo ------------------------------; @@ -234,6 +215,8 @@ cp_tests: cp_clean cp @echo; rm -rf cp_tests/{afile{1,2},symlink1,there1}; + +check_cp_files_to_dir_w_p_and_d: @echo; echo Copy a set of files to a directory with -p and -d switches. @echo ------------------------------; cd cp_tests; \ @@ -264,6 +247,37 @@ cp_tests: cp_clean cp @echo; rm -rf cp_tests/{afile{1,2},symlink1,there1}; +check_cp_dir_to_dir_wo_a: + # Copy a directory to another directory, without the -a switch. + @echo; echo Copy a directory to another directory, without the -a switch. + @echo ------------------------------; + @echo There should be an error message about cannot cp a dir to a subdir of itself. + cd cp_tests; \ + touch a b c; \ + mkdir adir; \ + ls -lR . > ../cp_a_star_adir.gnu; \ + ${GCP} -a * adir; \ + ls -lR . >> ../cp_a_star_adir.gnu; + + @echo + @echo There should be an error message about cannot cp a dir to a subdir of itself. + cd cp_tests; \ + rm -rf adir; \ + mkdir adir; \ + ls -lR . > ../cp_a_star_adir.bb; \ + ${BCP} -a * adir; \ + ls -lR . >> ../cp_a_star_adir.bb; + + @echo; + diff -u cp_a_star_adir.gnu cp_a_star_adir.bb; + + # Done + @echo; + rm -rf cp_tests; + @echo; echo Done. + + +check_cp_dir_to_dir_w_a: @echo; echo Copy a directory into another directory with the -a switch. @echo ------------------------------; cd cp_tests; \ @@ -298,30 +312,43 @@ cp_tests: cp_clean cp @echo; rm -rf cp_tests/dir{a,b}; - # Copy a directory to another directory, without the -a switch. - @echo; echo Copy a directory to another directory, without the -a switch. + +check_cp_dir_to_dir_w_a_take_two: + @echo; echo Copy a directory into another directory with the -a switch; @echo ------------------------------; - @echo There should be an error message about cannot cp a dir to a subdir of itself. - cd cp_tests; \ - touch a b c; \ - mkdir adir; \ - ls -lR . > ../cp_a_star_adir.gnu; \ - ${GCP} -a * adir; \ - ls -lR . >> ../cp_a_star_adir.gnu; + mkdir -p cp_tests/gnu; \ + mkdir -p cp_tests/bb; \ + cd cp_tests; \ + mkdir here there; \ + echo A file > here/afile; \ + mkdir here/adir; \ + touch here/adir/afileinadir; \ + ln -s $$(pwd) here/alink; - @echo - @echo There should be an error message about cannot cp a dir to a subdir of itself. - cd cp_tests; \ - rm -rf adir; \ - mkdir adir; \ - ls -lR . > ../cp_a_star_adir.bb; \ - ${BCP} -a * adir; \ - ls -lR . >> ../cp_a_star_adir.bb; + @echo; + cd cp_tests/gnu; \ + ls -lR . > ../../cp_a_dir_dir.gnu; \ + ${GCP} -a here/ there/; \ + ls -lR . >> ../../cp_a_dir_dir.gnu; @echo; - diff -u cp_a_star_adir.gnu cp_a_star_adir.bb; + rm -rf cp_tests/there/*; + + sleep 1; - # Done @echo; - rm -rf cp_tests; - @echo; echo Done. + cd cp_tests/bb; \ + ls -lR . > ../../cp_a_dir_dir.bb; \ + ${BCP} -a here/ there/; \ + ls -lR . >> ../../cp_a_dir_dir.bb; + + @echo; + echo "Erik 1" + diff -u cp_a_dir_dir.gnu cp_a_dir_dir.bb; + echo "Erik 2" + + @echo; + echo "Erik 3" + rm -rf cp_tests/*; + + |