X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Ftelnet.c;h=8b0df7f5c4f0211304b3d87ac5531ac180179883;hb=6a4f2231221c2c4f7ca82f081f442e31a6cfd051;hp=8b6d5f5dc28a1232ea4e52af0b5bd7ea23528f76;hpb=7ab9c7ee52db8759d457819f5480378fa3aa97cc;p=oweals%2Fbusybox.git diff --git a/networking/telnet.c b/networking/telnet.c index 8b6d5f5dc..8b0df7f5c 100644 --- a/networking/telnet.c +++ b/networking/telnet.c @@ -1,507 +1,701 @@ +/* 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 + * Author: Tomi Ollila + * Copyright (C) 1994-2000 by Tomi Ollila * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * Created: Thu Apr 7 13:29:41 1994 too + * Last modified: Fri Jun 9 14:34:24 2000 too * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. + * Licensed under GPLv2 or later, see file LICENSE in this source tree. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * 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. + * HISTORY + * Revision 3.1 1994/04/17 11:31:54 too + * initial revision + * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen + * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan + * + * Modified 2004/02/11 to add ability to pass the USER variable to remote host + * by Fernando Silveira */ +//config:config TELNET +//config: bool "telnet (8.7 kb)" +//config: default y +//config: help +//config: Telnet is an interface to the TELNET protocol, but is also commonly +//config: used to test other simple protocols. +//config: +//config:config FEATURE_TELNET_TTYPE +//config: bool "Pass TERM type to remote host" +//config: default y +//config: depends on TELNET +//config: help +//config: Setting this option will forward the TERM environment variable to the +//config: remote host you are connecting to. This is useful to make sure that +//config: things like ANSI colors and other control sequences behave. +//config: +//config:config FEATURE_TELNET_AUTOLOGIN +//config: bool "Pass USER type to remote host" +//config: default y +//config: depends on TELNET +//config: help +//config: Setting this option will forward the USER environment variable to the +//config: remote host you are connecting to. This is useful when you need to +//config: log into a machine without telling the username (autologin). This +//config: option enables '-a' and '-l USER' options. +//config: +//config:config FEATURE_TELNET_WIDTH +//config: bool "Enable window size autodetection" +//config: default y +//config: depends on TELNET + +//applet:IF_TELNET(APPLET(telnet, BB_DIR_USR_BIN, BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_TELNET) += telnet.o + +//usage:#if ENABLE_FEATURE_TELNET_AUTOLOGIN +//usage:#define telnet_trivial_usage +//usage: "[-a] [-l USER] HOST [PORT]" +//usage:#define telnet_full_usage "\n\n" +//usage: "Connect to telnet server\n" +//usage: "\n -a Automatic login with $USER variable" +//usage: "\n -l USER Automatic login as USER" +//usage: +//usage:#else +//usage:#define telnet_trivial_usage +//usage: "HOST [PORT]" +//usage:#define telnet_full_usage "\n\n" +//usage: "Connect to telnet server" +//usage:#endif -#include "internal.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define TELOPTS #include -#include - -static int STDIN = 0; -static int STDOUT = 1; -static const char *telnet_usage = "telnet host [port]\n" -#ifndef BB_FEATURE_TRIVIAL_HELP - "\nProvides interactive communication with another\n" - "networked host using the TELNET protocol\n" +#include +#include "libbb.h" +#include "common_bufsiz.h" + +#ifdef __BIONIC__ +/* should be in arpa/telnet.h */ +# define IAC 255 /* interpret as command: */ +# define DONT 254 /* you are not to use option */ +# define DO 253 /* please, you use option */ +# define WONT 252 /* I won't use option */ +# define WILL 251 /* I will use option */ +# define SB 250 /* interpret as subnegotiation */ +# define SE 240 /* end sub negotiation */ +# define TELOPT_ECHO 1 /* echo */ +# define TELOPT_SGA 3 /* suppress go ahead */ +# define TELOPT_TTYPE 24 /* terminal type */ +# define TELOPT_NAWS 31 /* window size */ +#endif + +#ifdef DOTRACE +# define TRACE(x, y) do { if (x) printf y; } while (0) +#else +# define TRACE(x, y) #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) + +enum { + DATABUFSIZE = 128, + IACBUFSIZE = 128, + + CHM_TRY = 0, + CHM_ON = 1, + CHM_OFF = 2, + + UF_ECHO = 0x01, + UF_SGA = 0x02, + + TS_NORMAL = 0, + TS_COPY = 1, + TS_IAC = 2, + TS_OPT = 3, + TS_SUB1 = 4, + TS_SUB2 = 5, + TS_CR = 6, +}; + +typedef unsigned char byte; + +enum { netfd = 3 }; + +struct globals { + int iaclen; /* could even use byte, but it's a loss on x86 */ + byte telstate; /* telnet negotiation state from network input */ + byte telwish; /* DO, DONT, WILL, WONT */ + byte charmode; + byte telflags; + byte do_termios; +#if ENABLE_FEATURE_TELNET_TTYPE + char *ttype; +#endif +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + const char *autologin; +#endif +#if ENABLE_FEATURE_TELNET_WIDTH + unsigned win_width, win_height; +#endif + /* same buffer used both for network and console read/write */ + char buf[DATABUFSIZE]; + /* buffer to handle telnet negotiations */ + char iacbuf[IACBUFSIZE]; + struct termios termios_def; + struct termios termios_raw; +} FIX_ALIASING; +#define G (*(struct globals*)bb_common_bufsiz1) +#define INIT_G() do { \ + setup_common_bufsiz(); \ + BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \ +} while (0) + + +static void rawmode(void); +static void cookmode(void); +static void do_linemode(void); +static void will_charmode(void); +static void telopt(byte c); +static void subneg(byte c); + +static void iac_flush(void) { - /* - telnet_sendwill(s, TELOPT_NAWS); TFLAG_SET(TELOPT_NAWS, WILL); - telnet_sendwill(s, TELOPT_TSPEED); TFLAG_SET(TELOPT_TSPEED, WILL); - telnet_sendwill(s, TELOPT_NEW_ENVIRON); TFLAG_SET(TELOPT_NEW_ENVIRON, WILL); - telnet_senddo(s, TELOPT_STATUS); TFLAG_SET(TELOPT_STATUS, DO); - telnet_sendwill(s, TELOPT_TTYPE); TFLAG_SET(TELOPT_TTYPE, WILL); - */ - telnet_senddo(s, TELOPT_SGA); TFLAG_SET(TELOPT_SGA, DO); - telnet_sendwill(s, TELOPT_LFLOW); TFLAG_SET(TELOPT_LFLOW, WILL); - telnet_sendwill(s, TELOPT_LINEMODE); TFLAG_SET(TELOPT_LINEMODE, WILL); - telnet_senddo(s, TELOPT_BINARY); TFLAG_SET(TELOPT_BINARY, DO); - telnet_sendwill(s, TELOPT_BINARY); TFLAG_SET(TELOPT_BINARY, WILL); + full_write(netfd, G.iacbuf, G.iaclen); + G.iaclen = 0; } -static void telnet_suboptions(int net) +static void doexit(int ev) NORETURN; +static void doexit(int ev) { - char buf[256]; - switch (SB_GET()) { - case TELOPT_TTYPE: - if (TFLAG_ISSET(TELOPT_TTYPE, WONT)) return; - if (SB_EOF() || SB_GET() != TELQUAL_SEND) { - return; - } else { - const char *name = getenv("TERM"); - if (name) { - snprintf(buf, sizeof(buf), "%c%c%c%c%s%c%c", IAC, SB, - TELOPT_TTYPE, TELQUAL_IS, name, IAC, SE); - write(net, buf, strlen(name)+6); - } - } - break; - case TELOPT_TSPEED: - if (TFLAG_ISSET(TELOPT_TSPEED, WONT)) return; - if (SB_EOF()) return; - if (SB_GET() == TELQUAL_SEND) { - /* - long oospeed, iispeed; - netoring.printf("%c%c%c%c%ld,%ld%c%c", IAC, SB, TELOPT_TSPEED, - TELQUAL_IS, oospeed, iispeed, IAC, SE); - */ + cookmode(); + exit(ev); +} + +static void con_escape(void) +{ + char b; + + if (bb_got_signal) /* came from line mode... go raw */ + rawmode(); + + full_write1_str("\r\nConsole escape. Commands are:\r\n\n" + " l go to line mode\r\n" + " c go to character mode\r\n" + " z suspend telnet\r\n" + " e exit telnet\r\n"); + + if (read(STDIN_FILENO, &b, 1) <= 0) + doexit(EXIT_FAILURE); + + switch (b) { + case 'l': + if (!bb_got_signal) { + do_linemode(); + goto ret; + } + break; + case 'c': + if (bb_got_signal) { + will_charmode(); + goto ret; + } + break; + case 'z': + cookmode(); + kill(0, SIGTSTP); + rawmode(); + break; + case 'e': + doexit(EXIT_SUCCESS); + } + + full_write1_str("continuing...\r\n"); + + if (bb_got_signal) + cookmode(); + ret: + bb_got_signal = 0; +} + +static void handle_net_output(int len) +{ + byte outbuf[2 * DATABUFSIZE]; + byte *dst = outbuf; + byte *src = (byte*)G.buf; + byte *end = src + len; + + while (src < end) { + byte c = *src++; + if (c == 0x1d) { + con_escape(); + return; + } + *dst = c; + if (c == IAC) + *++dst = c; /* IAC -> IAC IAC */ + else + if (c == '\r' || c == '\n') { + /* Enter key sends '\r' in raw mode and '\n' in cooked one. + * + * See RFC 1123 3.3.1 Telnet End-of-Line Convention. + * Using CR LF instead of other allowed possibilities + * like CR NUL - easier to talk to HTTP/SMTP servers. + */ + *dst = '\r'; /* Enter -> CR LF */ + *++dst = '\n'; + } + dst++; + } + if (dst - outbuf != 0) + full_write(netfd, outbuf, dst - outbuf); +} + +static void handle_net_input(int len) +{ + int i; + int cstart = 0; + + for (i = 0; i < len; i++) { + byte c = G.buf[i]; + + if (G.telstate == TS_NORMAL) { /* most typical state */ + if (c == IAC) { + cstart = i; + G.telstate = TS_IAC; } - break; - /* - case TELOPT_LFLOW: - if (TFLAG_ISSET(TELOPT_LFLOW, WONT)) return; - if (SB_EOF()) return; - switch(SB_GET()) { - case 1: localflow = 1; break; - case 0: localflow = 0; break; - default: return; + else if (c == '\r') { + cstart = i + 1; + G.telstate = TS_CR; } - break; - case TELOPT_LINEMODE: - if (TFLAG_ISSET(TELOPT_LINEMODE, WONT)) return; - if (SB_EOF()) return; - switch (SB_GET()) { - case WILL: lm_will(subpointer, SB_LEN()); break; - case WONT: lm_wont(subpointer, SB_LEN()); break; - case DO: lm_do(subpointer, SB_LEN()); break; - case DONT: lm_dont(subpointer, SB_LEN()); break; - case LM_SLC: slc(subpointer, SB_LEN()); break; - case LM_MODE: lm_mode(subpointer, SB_LEN(), 0); break; - default: break; + /* No IACs were seen so far, no need to copy + * bytes within G.buf: */ + continue; + } + + switch (G.telstate) { + case TS_CR: + /* Prev char was CR. If cur one is NUL, ignore it. + * See RFC 1123 section 3.3.1 for discussion of telnet EOL handling. + */ + G.telstate = TS_COPY; + if (c == '\0') + break; + /* else: fall through - need to handle CR IAC ... properly */ + + case TS_COPY: /* Prev char was ordinary */ + /* Similar to NORMAL, but in TS_COPY we need to copy bytes */ + if (c == IAC) + G.telstate = TS_IAC; + else + G.buf[cstart++] = c; + if (c == '\r') + G.telstate = TS_CR; + break; + + case TS_IAC: /* Prev char was IAC */ + if (c == IAC) { /* IAC IAC -> one IAC */ + G.buf[cstart++] = c; + G.telstate = TS_COPY; + break; } - break; - case TELOPT_ENVIRON: - if (SB_EOF()) return; - switch(SB_PEEK()) { - case TELQUAL_IS: - case TELQUAL_INFO: - if (TFLAG_ISSET(TELOPT_ENVIRON, DONT)) return; - break; - case TELQUAL_SEND: - if (TFLAG_ISSET(TELOPT_ENVIRON, WONT)) return; - break; - default: - return; + /* else */ + switch (c) { + case SB: + G.telstate = TS_SUB1; + break; + case DO: + case DONT: + case WILL: + case WONT: + G.telwish = c; + G.telstate = TS_OPT; + break; + /* DATA MARK must be added later */ + default: + G.telstate = TS_COPY; } - env_opt(subpointer, SB_LEN()); break; - */ - case TELOPT_XDISPLOC: - if (TFLAG_ISSET(TELOPT_XDISPLOC, WONT)) return; - if (SB_EOF()) return; - if (SB_GET() == TELQUAL_SEND) { - const char *dp = getenv("DISPLAY"); - if (dp) { - snprintf(buf, sizeof(buf), "%c%c%c%c%s%c%c", IAC, SB, - TELOPT_XDISPLOC, TELQUAL_IS, dp, IAC, SE); - write(net, buf, strlen(dp)+6); - } - } - break; - default: + + case TS_OPT: /* Prev chars were IAC WILL/WONT/DO/DONT */ + telopt(c); + G.telstate = TS_COPY; break; + + case TS_SUB1: /* Subnegotiation */ + case TS_SUB2: /* Subnegotiation */ + subneg(c); /* can change G.telstate */ + break; + } } + + if (G.telstate != TS_NORMAL) { + /* We had some IACs, or CR */ + if (G.iaclen) + iac_flush(); + if (G.telstate == TS_COPY) /* we aren't in the middle of IAC */ + G.telstate = TS_NORMAL; + len = cstart; + } + + if (len) + full_write(STDOUT_FILENO, G.buf, len); } -static void sighandler(int sig) +static void put_iac(int c) { - telnet_shutdown(); - exit(0); + G.iacbuf[G.iaclen++] = c; } -static int telnet_send(int tty, int net) +static void put_iac2_merged(unsigned wwdd_and_c) { - int ret; - unsigned char ch; - - while ((ret = read(tty, &ch, 1)) > 0) { - if (ch == 29) { /* 29 -- ctrl-] */ - /* do something here? */ - exit(0); - } else { - ret = write(net, &ch, 1); - break; - } - } - if (ret == -1 && errno == EWOULDBLOCK) return 1; - return ret; + if (G.iaclen + 3 > IACBUFSIZE) + iac_flush(); + + put_iac(IAC); + put_iac(wwdd_and_c >> 8); + put_iac(wwdd_and_c & 0xff); } +#define put_iac2(wwdd,c) put_iac2_merged(((wwdd)<<8) + (c)) -static int telnet_recv(int net, int tty) +#if ENABLE_FEATURE_TELNET_TTYPE +static void put_iac_subopt(byte c, char *str) { - /* globals: tr_state - telnet receive state */ - int ret, c = 0; - unsigned char ch; - - while ((ret = read(net, &ch, 1)) > 0) { - c = ch; - /* printf("%02X ", c); fflush(stdout); */ - switch (tr_state) { - case TS_DATA: - if (c == IAC) { - tr_state = TS_IAC; - break; - } else { - write(tty, &c, 1); - } - break; - case TS_IAC: - switch (c) { - case WILL: - tr_state = TS_WILL; break; - case WONT: - tr_state = TS_WONT; break; - case DO: - tr_state = TS_DO; break; - case DONT: - tr_state = TS_DONT; break; - case SB: - SB_CLEAR(); - tr_state = TS_SB; break; - case IAC: - write(tty, &c, 1); /* fallthrough */ - default: - tr_state = TS_DATA; - } - - /* subnegotiation -- ignored for now */ - case TS_SB: - if (c == IAC) tr_state = TS_SE; - else SB_ACCUM(c); - break; - case TS_SE: - if (c == IAC) { - SB_ACCUM(IAC); - tr_state = TS_SB; - } else if (c == SE) { - SB_ACCUM(IAC); - SB_ACCUM(SE); - subpointer -= 2; - SB_TERM(); - telnet_suboptions(net); - tr_state = TS_DATA; - } - /* otherwise this is an error, but we ignore it for now */ - break; - /* handle incoming requests */ - case TS_WILL: - printf("WILL %s\n", telopts[c]); - if (!TFLAG_ISSET(c, DO)) { - if (c == TELOPT_BINARY) { - TFLAG_SET(c, DO); - TFLAG_CLR(c, DONT); - telnet_senddo(net, c); - } else { - TFLAG_SET(c, DONT); - telnet_senddont(net, c); - } - } - telnet_senddont(net, c); - tr_state = TS_DATA; - break; - case TS_WONT: - printf("WONT %s\n", telopts[c]); - if (!TFLAG_ISSET(c, DONT)) { - TFLAG_SET(c, DONT); - TFLAG_CLR(c, DO); - telnet_senddont(net, c); - } - tr_state = TS_DATA; - break; - case TS_DO: - printf("DO %s\n", telopts[c]); - if (!TFLAG_ISSET(c, WILL)) { - if (c == TELOPT_BINARY) { - TFLAG_SET(c, WILL); - TFLAG_CLR(c, WONT); - telnet_sendwill(net, c); - } else { - TFLAG_SET(c, WONT); - telnet_sendwont(net, c); - } - } - tr_state = TS_DATA; - break; - case TS_DONT: - printf("DONT %s\n", telopts[c]); - if (!TFLAG_ISSET(c, WONT)) { - TFLAG_SET(c, WONT); - TFLAG_CLR(c, WILL); - telnet_sendwont(net, c); - } - tr_state = TS_DATA; - break; + int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) + + if (G.iaclen + len > IACBUFSIZE) + iac_flush(); + + put_iac(IAC); + put_iac(SB); + put_iac(c); + put_iac(0); + + while (*str) + put_iac(*str++); + + put_iac(IAC); + put_iac(SE); +} +#endif + +#if ENABLE_FEATURE_TELNET_AUTOLOGIN +static void put_iac_subopt_autologin(void) +{ + int len = strlen(G.autologin) + 6; // (2 + 1 + 1 + strlen + 2) + const char *p = "USER"; + + if (G.iaclen + len > IACBUFSIZE) + iac_flush(); + + put_iac(IAC); + put_iac(SB); + put_iac(TELOPT_NEW_ENVIRON); + put_iac(TELQUAL_IS); + put_iac(NEW_ENV_VAR); + + while (*p) + put_iac(*p++); + + put_iac(NEW_ENV_VALUE); + + p = G.autologin; + while (*p) + put_iac(*p++); + + put_iac(IAC); + put_iac(SE); +} +#endif + +#if ENABLE_FEATURE_TELNET_WIDTH +static void put_iac_naws(byte c, int x, int y) +{ + if (G.iaclen + 9 > IACBUFSIZE) + iac_flush(); + + put_iac(IAC); + put_iac(SB); + put_iac(c); + + /* "... & 0xff" implicitly done below */ + put_iac(x >> 8); + put_iac(x); + put_iac(y >> 8); + put_iac(y); + + put_iac(IAC); + put_iac(SE); +} +#endif + +static void setConMode(void) +{ + if (G.telflags & UF_ECHO) { + if (G.charmode == CHM_TRY) { + G.charmode = CHM_ON; + printf("\r\nEntering %s mode" + "\r\nEscape character is '^%c'.\r\n", "character", ']'); + rawmode(); + } + } else { + if (G.charmode != CHM_OFF) { + G.charmode = CHM_OFF; + printf("\r\nEntering %s mode" + "\r\nEscape character is '^%c'.\r\n", "line", 'C'); + cookmode(); } - } - if (ret == -1 && errno == EWOULDBLOCK) return 1; - return ret; } -/* ******************************************************************* */ -static void telnet_init(void) +static void will_charmode(void) +{ + G.charmode = CHM_TRY; + G.telflags |= (UF_ECHO | UF_SGA); + setConMode(); + + put_iac2(DO, TELOPT_ECHO); + put_iac2(DO, TELOPT_SGA); + iac_flush(); +} + +static void do_linemode(void) { - struct termios tmp_tc; - cc_t esc = (']' & 0x1f); /* ctrl-] */ - - memset(options, 0, sizeof(options)); - SB_CLEAR(); - - tcgetattr(STDIN, &saved_tc); - - tmp_tc = saved_tc; - tmp_tc.c_lflag &= ~ECHO; /* echo */ - tmp_tc.c_oflag |= ONLCR; /* NL->CRLF translation */ - tmp_tc.c_iflag |= ICRNL; - tmp_tc.c_iflag &= ~(IXANY|IXOFF|IXON); /* no flow control */ - tmp_tc.c_lflag |= ISIG; /* trap signals */ - tmp_tc.c_lflag &= ~ICANON; /* edit mode */ - - /* misc settings, compat with default telnet stuff */ - tmp_tc.c_oflag &= ~TABDLY; - - /* 8-bit clean */ - tmp_tc.c_iflag &= ~ISTRIP; - tmp_tc.c_cflag &= ~(CSIZE|PARENB); - tmp_tc.c_cflag |= saved_tc.c_cflag & (CSIZE|PARENB); - tmp_tc.c_oflag |= OPOST; - - /* set escape character */ - tmp_tc.c_cc[VEOL] = esc; - tcsetattr(STDIN, TCSADRAIN, &tmp_tc); + G.charmode = CHM_TRY; + G.telflags &= ~(UF_ECHO | UF_SGA); + setConMode(); + + put_iac2(DONT, TELOPT_ECHO); + put_iac2(DONT, TELOPT_SGA); + iac_flush(); } -static void telnet_start(char *hostname, int port) +static void to_notsup(char c) { - struct hostent *host = 0; - struct sockaddr_in addr; - int s, c; - fd_set rfds, wfds; - - memset(&addr, 0, sizeof(addr)); - host = gethostbyname(hostname); - if (!host) { - fprintf(stderr, "Unknown host: %s\n", hostname); + if (G.telwish == WILL) + put_iac2(DONT, c); + else if (G.telwish == DO) + put_iac2(WONT, c); +} + +static void to_echo(void) +{ + /* if server requests ECHO, don't agree */ + if (G.telwish == DO) { + put_iac2(WONT, TELOPT_ECHO); return; } - addr.sin_family = host->h_addrtype; - memcpy(&addr.sin_addr, host->h_addr, sizeof(addr.sin_addr)); - addr.sin_port = htons(port); - - printf("Trying %s...\n", inet_ntoa(addr.sin_addr)); - - if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) PERROR("socket"); - if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) - PERROR("connect"); - printf("Connected to %s\n", hostname); - printf("Escape character is ^]\n"); - - signal(SIGINT, sighandler); - signal(SIGQUIT, sighandler); - signal(SIGPIPE, sighandler); - signal(SIGWINCH, sighandler); - - /* make inputs nonblocking */ - c = 1; - ioctl(s, FIONBIO, &c); - ioctl(STDIN, FIONBIO, &c); - - if (port == TELNETPORT) telnet_setoptions(s); - - /* shuttle data back and forth between tty and socket */ - while (1) { - FD_ZERO(&rfds); - FD_ZERO(&wfds); - - FD_SET(s, &rfds); - /* FD_SET(s, &wfds); */ - FD_SET(STDIN, &rfds); - /* FD_SET(STDOUT, &wfds); */ - - if ((c = select(s+1, &rfds, &wfds, 0, 0))) { - if (c == -1) { - /* handle errors */ - PERROR("select"); - } - if (FD_ISSET(s, &rfds)) { - /* input from network */ - FD_CLR(s, &rfds); - c = telnet_recv(s, STDOUT); - if (c == 0) break; - if (c < 0) PERROR("telnet_recv"); - } - if (FD_ISSET(STDIN, &rfds)) { - /* input from tty */ - FD_CLR(STDIN, &rfds); - c = telnet_send(STDIN, s); - if (c == 0) break; - if (c < 0) PERROR("telnet_send"); - } + if (G.telwish == DONT) + return; + + if (G.telflags & UF_ECHO) { + if (G.telwish == WILL) + return; + } else if (G.telwish == WONT) + return; + + if (G.charmode != CHM_OFF) + G.telflags ^= UF_ECHO; + + if (G.telflags & UF_ECHO) + put_iac2(DO, TELOPT_ECHO); + else + put_iac2(DONT, TELOPT_ECHO); + + setConMode(); + full_write1_str("\r\n"); /* sudden modec */ +} + +static void to_sga(void) +{ + /* daemon always sends will/wont, client do/dont */ + + if (G.telflags & UF_SGA) { + if (G.telwish == WILL) + return; + } else if (G.telwish == WONT) + return; + + G.telflags ^= UF_SGA; /* toggle */ + if (G.telflags & UF_SGA) + put_iac2(DO, TELOPT_SGA); + else + put_iac2(DONT, TELOPT_SGA); +} + +#if ENABLE_FEATURE_TELNET_TTYPE +static void to_ttype(void) +{ + /* Tell server we will (or won't) do TTYPE */ + if (G.ttype) + put_iac2(WILL, TELOPT_TTYPE); + else + put_iac2(WONT, TELOPT_TTYPE); +} +#endif + +#if ENABLE_FEATURE_TELNET_AUTOLOGIN +static void to_new_environ(void) +{ + /* Tell server we will (or will not) do AUTOLOGIN */ + if (G.autologin) + put_iac2(WILL, TELOPT_NEW_ENVIRON); + else + put_iac2(WONT, TELOPT_NEW_ENVIRON); +} +#endif + +#if ENABLE_FEATURE_TELNET_WIDTH +static void to_naws(void) +{ + /* Tell server we will do NAWS */ + put_iac2(WILL, TELOPT_NAWS); +} +#endif + +static void telopt(byte c) +{ + switch (c) { + case TELOPT_ECHO: + to_echo(); break; + case TELOPT_SGA: + to_sga(); break; +#if ENABLE_FEATURE_TELNET_TTYPE + case TELOPT_TTYPE: + to_ttype(); break; +#endif +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + case TELOPT_NEW_ENVIRON: + to_new_environ(); break; +#endif +#if ENABLE_FEATURE_TELNET_WIDTH + case TELOPT_NAWS: + to_naws(); + put_iac_naws(c, G.win_width, G.win_height); + break; +#endif + default: + to_notsup(c); + break; + } +} + +/* subnegotiation -- ignore all (except TTYPE,NAWS) */ +static void subneg(byte c) +{ + switch (G.telstate) { + case TS_SUB1: + if (c == IAC) + G.telstate = TS_SUB2; +#if ENABLE_FEATURE_TELNET_TTYPE + else + if (c == TELOPT_TTYPE && G.ttype) + put_iac_subopt(TELOPT_TTYPE, G.ttype); +#endif +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + else + if (c == TELOPT_NEW_ENVIRON && G.autologin) + put_iac_subopt_autologin(); +#endif + break; + case TS_SUB2: + if (c == SE) { + G.telstate = TS_COPY; + return; } + G.telstate = TS_SUB1; + break; } - - return; } -static void telnet_shutdown(void) +static void rawmode(void) { - printf("\n"); - tcsetattr(STDIN, TCSANOW, &saved_tc); + if (G.do_termios) + tcsetattr(0, TCSADRAIN, &G.termios_raw); } -#ifdef STANDALONE_TELNET -void usage(const char *msg) +static void cookmode(void) { - printf("%s", msg); - exit(0); + if (G.do_termios) + tcsetattr(0, TCSADRAIN, &G.termios_def); } -int main(int argc, char **argv) +int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int telnet_main(int argc UNUSED_PARAM, char **argv) +{ + char *host; + int port; + int len; + struct pollfd ufds[2]; + + INIT_G(); + +#if ENABLE_FEATURE_TELNET_WIDTH + get_terminal_width_height(0, &G.win_width, &G.win_height); +#endif + +#if ENABLE_FEATURE_TELNET_TTYPE + G.ttype = getenv("TERM"); +#endif + + if (tcgetattr(0, &G.termios_def) >= 0) { + G.do_termios = 1; + G.termios_raw = G.termios_def; + cfmakeraw(&G.termios_raw); + } + +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + if (1 == getopt32(argv, "al:", &G.autologin)) { + /* Only -a without -l USER picks $USER from envvar */ + G.autologin = getenv("USER"); + } + argv += optind; #else -int telnet_main(int argc, char **argv) + argv++; #endif -{ - int port = TELNETPORT; - - argc--; argv++; - if (argc < 1) usage(telnet_usage); - if (argc > 1) port = atoi(argv[1]); - telnet_init(); - atexit(telnet_shutdown); - - telnet_start(argv[0], port); - return 0; -} + if (!*argv) + bb_show_usage(); + host = *argv++; + port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23); + if (*argv) /* extra params?? */ + bb_show_usage(); -/* - * Copyright (c) 1988, 1990 Regents of the University of California. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ + xmove_fd(create_and_connect_stream_or_die(host, port), netfd); + + setsockopt_keepalive(netfd); + + signal(SIGINT, record_signo); + + ufds[0].fd = STDIN_FILENO; + ufds[0].events = POLLIN; + ufds[1].fd = netfd; + ufds[1].events = POLLIN; + + while (1) { + if (poll(ufds, 2, -1) < 0) { + /* error, ignore and/or log something, bay go to loop */ + if (bb_got_signal) + con_escape(); + else + sleep(1); + continue; + } + +// FIXME: reads can block. Need full bidirectional buffering. + + if (ufds[0].revents) { + len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE); + if (len <= 0) + doexit(EXIT_SUCCESS); + TRACE(0, ("Read con: %d\n", len)); + handle_net_output(len); + } + + if (ufds[1].revents) { + len = safe_read(netfd, G.buf, DATABUFSIZE); + if (len <= 0) { + full_write1_str("Connection closed by foreign host\r\n"); + doexit(EXIT_FAILURE); + } + TRACE(0, ("Read netfd (%d): %d\n", netfd, len)); + handle_net_input(len); + } + } /* while (1) */ +}