X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Ftelnet.c;h=5d7ecef3bf40f3b563caccf32bc2367d64e16ba9;hb=08ea11ab0749a7977e6d47cd0fa7b3c9cc10af32;hp=076728fb02e10bbd9d1b4b86f038e31dc71b3ca9;hpb=f7c49ef2d106eb38f648432e2cff2a81bb982437;p=oweals%2Fbusybox.git diff --git a/networking/telnet.c b/networking/telnet.c index 076728fb0..5d7ecef3b 100644 --- a/networking/telnet.c +++ b/networking/telnet.c @@ -1,503 +1,649 @@ /* vi: set sw=4 ts=4: */ /* - * $Id: telnet.c,v 1.1 2000/02/22 17:17:45 erik Exp $ - * Mini telnet implementation for busybox + * telnet implementation for busybox * - * Copyright (C) 2000 by Randolph Chung + * 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 the GPL v2 or later, see the file LICENSE in this tarball. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * HISTORY + * Revision 3.1 1994/04/17 11:31:54 too + * initial revision + * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen + * 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 * - * 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 -#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\n"; -static struct termios saved_tc; -static unsigned char options[NTELOPTS]; -static char tr_state = 0; /* telnet send and receive state */ -static unsigned char subbuffer[256]; -static unsigned char *subpointer, *subend; -#define SB_CLEAR() subpointer = subbuffer; -#define SB_TERM() { subend = subpointer; SB_CLEAR(); } -#define SB_ACCUM(c) if (subpointer < (subbuffer+sizeof subbuffer)) { *subpointer++ = (c); } -#define SB_GET() (*subpointer++) -#define SB_PEEK() (*subpointer) -#define SB_EOF() (subpointer >= subend) -#define SB_LEN() (subend - subpointer) - -#define TELNETPORT 23 -#define MASK_WILL 0x01 -#define MASK_WONT 0x04 -#define MASK_DO 0x10 -#define MASK_DONT 0x40 - -#define TFLAG_ISSET(opt, flag) (options[opt] & MASK_##flag) -#define TFLAG_SET(opt, flag) (options[opt] |= MASK_##flag) -#define TFLAG_CLR(opt, flag) (options[opt] &= ~MASK_##flag) - -#define PERROR(ctx) do { fprintf(stderr, "%s: %s\n", ctx, strerror(errno)); \ - return; } while (0) - -#define TS_DATA 0 -#define TS_IAC 1 -#define TS_WILL 2 -#define TS_WONT 3 -#define TS_DO 4 -#define TS_DONT 5 -#define TS_CR 6 -#define TS_SB 7 /* sub-option collection */ -#define TS_SE 8 /* looking for sub-option end */ - -/* prototypes */ -static void telnet_init(void); -static void telnet_start(char *host, int port); -static void telnet_shutdown(void); -/* ******************************************************************* */ -#define SENDCOMMAND(c,o) \ - char buf[3]; \ - buf[0] = IAC; buf[1] = c; buf[2] = o; \ - write(s, buf, sizeof(buf)); - -static inline void telnet_sendwill(int s, int c) { SENDCOMMAND(WILL, c); } -static inline void telnet_sendwont(int s, int c) { SENDCOMMAND(WONT, c); } -static inline void telnet_senddo(int s, int c) { SENDCOMMAND(DO, c); } -static inline void telnet_senddont(int s, int c) { SENDCOMMAND(DONT, c); } - -static void telnet_setoptions(int s) +#include +#include "libbb.h" + +#ifdef DOTRACE +#define TRACE(x, y) do { if (x) printf y; } while (0) +#else +#define TRACE(x, y) +#endif + +enum { + DATABUFSIZE = 128, + IACBUFSIZE = 128, + + CHM_TRY = 0, + CHM_ON = 1, + CHM_OFF = 2, + + UF_ECHO = 0x01, + UF_SGA = 0x02, + + TS_0 = 1, + TS_IAC = 2, + TS_OPT = 3, + TS_SUB1 = 4, + TS_SUB2 = 5, +}; + +typedef unsigned char byte; + +enum { netfd = 3 }; + +struct globals { + int iaclen; /* could even use byte, but it's a loss on x86 */ + byte telstate; /* telnet negotiation state from network input */ + byte telwish; /* DO, DONT, WILL, WONT */ + byte charmode; + byte telflags; + byte do_termios; +#if ENABLE_FEATURE_TELNET_TTYPE + char *ttype; +#endif +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + const char *autologin; +#endif +#if ENABLE_FEATURE_AUTOWIDTH + unsigned win_width, win_height; +#endif + /* same buffer used both for network and console read/write */ + char buf[DATABUFSIZE]; + /* buffer to handle telnet negotiations */ + char iacbuf[IACBUFSIZE]; + struct termios termios_def; + struct termios termios_raw; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +void BUG_telnet_globals_too_big(void); +#define INIT_G() do { \ + if (sizeof(G) > COMMON_BUFSIZE) \ + BUG_telnet_globals_too_big(); \ + /* memset(&G, 0, sizeof G); - already is */ \ +} while (0) + +/* Function prototypes */ +static void rawmode(void); +static void cookmode(void); +static void do_linemode(void); +static void will_charmode(void); +static void telopt(byte c); +static int subneg(byte c); + +static void iacflush(void) { - /* - telnet_senddo(s, TELOPT_SGA); TFLAG_SET(TELOPT_SGA, DO); - telnet_sendwill(s, TELOPT_NAWS); TFLAG_SET(TELOPT_NAWS, WILL); - telnet_sendwill(s, TELOPT_TSPEED); TFLAG_SET(TELOPT_TSPEED, WILL); - telnet_sendwill(s, TELOPT_LFLOW); TFLAG_SET(TELOPT_LFLOW, WILL); - telnet_sendwill(s, TELOPT_LINEMODE); TFLAG_SET(TELOPT_LINEMODE, WILL); - telnet_sendwill(s, TELOPT_NEW_ENVIRON); TFLAG_SET(TELOPT_NEW_ENVIRON, WILL); - telnet_senddo(s, TELOPT_STATUS); TFLAG_SET(TELOPT_STATUS, DO); - telnet_sendwill(s, TELOPT_TTYPE); TFLAG_SET(TELOPT_TTYPE, WILL); - */ - telnet_senddo(s, TELOPT_BINARY); TFLAG_SET(TELOPT_BINARY, DO); - telnet_sendwill(s, TELOPT_BINARY); TFLAG_SET(TELOPT_BINARY, WILL); + write(netfd, G.iacbuf, G.iaclen); + G.iaclen = 0; } -static void telnet_suboptions(int net) +#define write_str(fd, str) write(fd, str, sizeof(str) - 1) + +static void doexit(int ev) NORETURN; +static void doexit(int ev) { - char buf[256]; - switch (SB_GET()) { - case TELOPT_TTYPE: - if (TFLAG_ISSET(TELOPT_TTYPE, WONT)) return; - if (SB_EOF() || SB_GET() != TELQUAL_SEND) { - return; - } else { - const char *name = getenv("TERM"); - if (name) { - snprintf(buf, sizeof(buf), "%c%c%c%c%s%c%c", IAC, SB, - TELOPT_TTYPE, TELQUAL_IS, name, IAC, SE); - write(net, buf, strlen(name)+6); - } - } - break; - case TELOPT_TSPEED: - if (TFLAG_ISSET(TELOPT_TSPEED, WONT)) return; - if (SB_EOF()) return; - if (SB_GET() == TELQUAL_SEND) { - /* - long oospeed, iispeed; - netoring.printf("%c%c%c%c%ld,%ld%c%c", IAC, SB, TELOPT_TSPEED, - TELQUAL_IS, oospeed, iispeed, IAC, SE); - */ - } - break; - /* - case TELOPT_LFLOW: - if (TFLAG_ISSET(TELOPT_LFLOW, WONT)) return; - if (SB_EOF()) return; - switch(SB_GET()) { - case 1: localflow = 1; break; - case 0: localflow = 0; break; - default: return; - } - 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; - } + cookmode(); + exit(ev); } -static void sighandler(int sig) +static void conescape(void) { - telnet_shutdown(); - exit(0); + char b; + + if (bb_got_signal) /* came from line mode... go raw */ + rawmode(); + + write_str(1, "\r\nConsole escape. Commands are:\r\n\n" + " l go to line mode\r\n" + " c go to character mode\r\n" + " z suspend telnet\r\n" + " e exit telnet\r\n"); + + if (read(STDIN_FILENO, &b, 1) <= 0) + doexit(EXIT_FAILURE); + + switch (b) { + case 'l': + if (!bb_got_signal) { + do_linemode(); + goto rrturn; + } + break; + case 'c': + if (bb_got_signal) { + will_charmode(); + goto rrturn; + } + break; + case 'z': + cookmode(); + kill(0, SIGTSTP); + rawmode(); + break; + case 'e': + doexit(EXIT_SUCCESS); + } + + write_str(1, "continuing...\r\n"); + + if (bb_got_signal) + cookmode(); + + rrturn: + bb_got_signal = 0; + } -static int telnet_send(int tty, int net) +static void handlenetoutput(int len) { - 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; + /* here we could do smart tricks how to handle 0xFF:s in output + * stream like writing twice every sequence of FF:s (thus doing + * many write()s. But I think interactive telnet application does + * not need to be 100% 8-bit clean, so changing every 0xff:s to + * 0x7f:s + * + * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com) + * I don't agree. + * first - I cannot use programs like sz/rz + * second - the 0x0D is sent as one character and if the next + * char is 0x0A then it's eaten by a server side. + * third - whay doy you have to make 'many write()s'? + * I don't understand. + * So I implemented it. It's realy useful for me. I hope that + * others people will find it interesting too. + */ + + int i, j; + byte * p = (byte*)G.buf; + byte outbuf[4*DATABUFSIZE]; + + for (i = len, j = 0; i > 0; i--, p++) { + if (*p == 0x1d) { + conescape(); + return; } + outbuf[j++] = *p; + if (*p == 0xff) + outbuf[j++] = 0xff; + else if (*p == 0x0d) + outbuf[j++] = 0x00; } - if (ret == -1 && errno == EWOULDBLOCK) return 1; - return ret; + if (j > 0) + write(netfd, outbuf, j); } -static int telnet_recv(int net, int tty) +static void handlenetinput(int len) { - /* 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); - } + int i; + int cstart = 0; + + for (i = 0; i < len; i++) { + byte c = G.buf[i]; + + if (G.telstate == 0) { /* most of the time state == 0 */ + if (c == IAC) { + cstart = i; + G.telstate = TS_IAC; + } + } else + switch (G.telstate) { + case TS_0: + if (c == IAC) + G.telstate = TS_IAC; + else + G.buf[cstart++] = c; 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); - } + if (c == IAC) { /* IAC IAC -> 0xFF */ + G.buf[cstart++] = c; + G.telstate = TS_0; + break; } - 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); + /* 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 */ } - 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; + case TS_OPT: /* WILL, WONT, DO, DONT */ + telopt(c); + G.telstate = TS_0; 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; + case TS_SUB1: /* Subnegotiation */ + case TS_SUB2: /* Subnegotiation */ + if (subneg(c)) + G.telstate = TS_0; break; + } + } + if (G.telstate) { + if (G.iaclen) iacflush(); + if (G.telstate == TS_0) G.telstate = 0; + len = cstart; + } + + if (len) + write(STDOUT_FILENO, G.buf, len); +} + +static void putiac(int c) +{ + G.iacbuf[G.iaclen++] = c; +} + +static void putiac2(byte wwdd, byte c) +{ + if (G.iaclen + 3 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(wwdd); + putiac(c); +} + +#if ENABLE_FEATURE_TELNET_TTYPE +static void putiac_subopt(byte c, char *str) +{ + int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) + + if (G.iaclen + len > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(c); + putiac(0); + + while (*str) + putiac(*str++); + + putiac(IAC); + putiac(SE); +} +#endif + +#if ENABLE_FEATURE_TELNET_AUTOLOGIN +static void putiac_subopt_autologin(void) +{ + int len = strlen(G.autologin) + 6; // (2 + 1 + 1 + strlen + 2) + const char *user = "USER"; + + if (G.iaclen + len > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(TELOPT_NEW_ENVIRON); + putiac(TELQUAL_IS); + putiac(NEW_ENV_VAR); + + while (*user) + putiac(*user++); + + putiac(NEW_ENV_VALUE); + + while (*G.autologin) + putiac(*G.autologin++); + + putiac(IAC); + putiac(SE); +} +#endif + +#if ENABLE_FEATURE_AUTOWIDTH +static void putiac_naws(byte c, int x, int y) +{ + if (G.iaclen + 9 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(c); + + putiac((x >> 8) & 0xff); + putiac(x & 0xff); + putiac((y >> 8) & 0xff); + putiac(y & 0xff); + + putiac(IAC); + putiac(SE); +} +#endif + +static char const escapecharis[] ALIGN1 = "\r\nEscape character is "; + +static void setConMode(void) +{ + if (G.telflags & UF_ECHO) { + if (G.charmode == CHM_TRY) { + G.charmode = CHM_ON; + printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis); + rawmode(); + } + } else { + if (G.charmode != CHM_OFF) { + G.charmode = CHM_OFF; + printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); + cookmode(); } - } - if (ret == -1 && errno == EWOULDBLOCK) return 1; - return ret; } -/* ******************************************************************* */ -static void telnet_init(void) +static void will_charmode(void) +{ + G.charmode = CHM_TRY; + G.telflags |= (UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DO, TELOPT_ECHO); + putiac2(DO, TELOPT_SGA); + iacflush(); +} + +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(); + + putiac2(DONT, TELOPT_ECHO); + putiac2(DONT, TELOPT_SGA); + iacflush(); } -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) + putiac2(DONT, c); + else if (G.telwish == DO) + putiac2(WONT, c); +} + +static void to_echo(void) +{ + /* if server requests ECHO, don't agree */ + if (G.telwish == DO) { + putiac2(WONT, TELOPT_ECHO); return; } - addr.sin_family = host->h_addrtype; - memcpy(&addr.sin_addr, host->h_addr, sizeof(addr.sin_addr)); - addr.sin_port = htons(port); - - printf("Trying %s...\n", inet_ntoa(addr.sin_addr)); - - if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) PERROR("socket"); - if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) - PERROR("connect"); - printf("Connected to %s\n", hostname); - printf("Escape character is ^]\n"); - - signal(SIGINT, sighandler); - signal(SIGQUIT, sighandler); - signal(SIGPIPE, sighandler); - signal(SIGWINCH, sighandler); - - /* make inputs nonblocking */ - c = 1; - ioctl(s, FIONBIO, &c); - ioctl(STDIN, FIONBIO, &c); - - if (port == TELNETPORT) telnet_setoptions(s); - - /* shuttle data back and forth between tty and socket */ - while (1) { - FD_ZERO(&rfds); - FD_ZERO(&wfds); - - FD_SET(s, &rfds); - /* FD_SET(s, &wfds); */ - FD_SET(STDIN, &rfds); - /* FD_SET(STDOUT, &wfds); */ - - if ((c = select(s+1, &rfds, &wfds, 0, 0))) { - if (c == -1) { - /* handle errors */ - PERROR("select"); - } - if (FD_ISSET(s, &rfds)) { - /* input from network */ - FD_CLR(s, &rfds); - c = telnet_recv(s, STDOUT); - if (c == 0) break; - if (c < 0) PERROR("telnet_recv"); - } - if (FD_ISSET(STDIN, &rfds)) { - /* input from tty */ - FD_CLR(STDIN, &rfds); - c = telnet_send(STDIN, s); - if (c == 0) break; - if (c < 0) PERROR("telnet_send"); - } - } - } - - return; + if (G.telwish == DONT) + return; + + if (G.telflags & UF_ECHO) { + if (G.telwish == WILL) + return; + } else if (G.telwish == WONT) + return; + + if (G.charmode != CHM_OFF) + G.telflags ^= UF_ECHO; + + if (G.telflags & UF_ECHO) + putiac2(DO, TELOPT_ECHO); + else + putiac2(DONT, TELOPT_ECHO); + + setConMode(); + write_str(1, "\r\n"); /* sudden modec */ } -static void telnet_shutdown(void) +static void to_sga(void) { - printf("\n"); - tcsetattr(STDIN, TCSANOW, &saved_tc); + /* daemon always sends will/wont, client do/dont */ + + if (G.telflags & UF_SGA) { + if (G.telwish == WILL) + return; + } else if (G.telwish == WONT) + return; + + G.telflags ^= UF_SGA; /* toggle */ + if (G.telflags & UF_SGA) + putiac2(DO, TELOPT_SGA); + else + putiac2(DONT, TELOPT_SGA); } -#ifdef STANDALONE_TELNET -void usage(const char *msg) +#if ENABLE_FEATURE_TELNET_TTYPE +static void to_ttype(void) { - printf("%s", msg); - exit(0); + /* Tell server we will (or won't) do TTYPE */ + + if (G.ttype) + putiac2(WILL, TELOPT_TTYPE); + else + putiac2(WONT, TELOPT_TTYPE); } +#endif -int main(int argc, char **argv) -#else -int telnet_main(int argc, char **argv) +#if ENABLE_FEATURE_TELNET_AUTOLOGIN +static void to_new_environ(void) +{ + /* Tell server we will (or will not) do AUTOLOGIN */ + + if (G.autologin) + putiac2(WILL, TELOPT_NEW_ENVIRON); + else + putiac2(WONT, TELOPT_NEW_ENVIRON); +} #endif + +#if ENABLE_FEATURE_AUTOWIDTH +static void to_naws(void) { - 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; + /* Tell server we will do NAWS */ + putiac2(WILL, TELOPT_NAWS); } +#endif -/* - * 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. - */ +static void telopt(byte c) +{ + switch (c) { + case TELOPT_ECHO: + to_echo(); break; + case TELOPT_SGA: + to_sga(); break; +#if ENABLE_FEATURE_TELNET_TTYPE + case TELOPT_TTYPE: + to_ttype(); break; +#endif +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + case TELOPT_NEW_ENVIRON: + to_new_environ(); break; +#endif +#if ENABLE_FEATURE_AUTOWIDTH + case TELOPT_NAWS: + to_naws(); + putiac_naws(c, G.win_width, G.win_height); + break; +#endif + default: + to_notsup(c); + break; + } +} + +/* subnegotiation -- ignore all (except TTYPE,NAWS) */ +static int subneg(byte c) +{ + switch (G.telstate) { + case TS_SUB1: + if (c == IAC) + G.telstate = TS_SUB2; +#if ENABLE_FEATURE_TELNET_TTYPE + else + if (c == TELOPT_TTYPE) + putiac_subopt(TELOPT_TTYPE, G.ttype); +#endif +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + else + if (c == TELOPT_NEW_ENVIRON) + putiac_subopt_autologin(); +#endif + break; + case TS_SUB2: + if (c == SE) + return TRUE; + G.telstate = TS_SUB1; + /* break; */ + } + return FALSE; +} + +static void rawmode(void) +{ + if (G.do_termios) + tcsetattr(0, TCSADRAIN, &G.termios_raw); +} + +static void cookmode(void) +{ + if (G.do_termios) + tcsetattr(0, TCSADRAIN, &G.termios_def); +} + +/* poll gives smaller (-70 bytes) code */ +#define USE_POLL 1 + +int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int telnet_main(int argc UNUSED_PARAM, char **argv) +{ + char *host; + int port; + int len; +#ifdef USE_POLL + struct pollfd ufds[2]; +#else + fd_set readfds; + int maxfd; +#endif + + INIT_G(); + +#if ENABLE_FEATURE_AUTOWIDTH + get_terminal_width_height(0, &G.win_width, &G.win_height); +#endif + +#if ENABLE_FEATURE_TELNET_TTYPE + G.ttype = getenv("TERM"); +#endif + + if (tcgetattr(0, &G.termios_def) >= 0) { + G.do_termios = 1; + G.termios_raw = G.termios_def; + cfmakeraw(&G.termios_raw); + } + +#if ENABLE_FEATURE_TELNET_AUTOLOGIN + if (1 & getopt32(argv, "al:", &G.autologin)) + G.autologin = getenv("USER"); + argv += optind; +#else + argv++; +#endif + if (!*argv) + bb_show_usage(); + host = *argv++; + port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23); + if (*argv) /* extra params?? */ + bb_show_usage(); + + xmove_fd(create_and_connect_stream_or_die(host, port), netfd); + + setsockopt(netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); + + signal(SIGINT, record_signo); + +#ifdef USE_POLL + ufds[0].fd = 0; ufds[1].fd = netfd; + ufds[0].events = ufds[1].events = POLLIN; +#else + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + FD_SET(netfd, &readfds); + maxfd = netfd + 1; +#endif + + while (1) { +#ifndef USE_POLL + fd_set rfds = readfds; + + switch (select(maxfd, &rfds, NULL, NULL, NULL)) +#else + switch (poll(ufds, 2, -1)) +#endif + { + case 0: + /* timeout */ + case -1: + /* error, ignore and/or log something, bay go to loop */ + if (bb_got_signal) + conescape(); + else + sleep(1); + break; + default: + +#ifdef USE_POLL + if (ufds[0].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(STDIN_FILENO, &rfds)) +#endif + { + len = read(STDIN_FILENO, G.buf, DATABUFSIZE); + if (len <= 0) + doexit(EXIT_SUCCESS); + TRACE(0, ("Read con: %d\n", len)); + handlenetoutput(len); + } + +#ifdef USE_POLL + if (ufds[1].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(netfd, &rfds)) +#endif + { + len = read(netfd, G.buf, DATABUFSIZE); + if (len <= 0) { + write_str(1, "Connection closed by foreign host\r\n"); + doexit(EXIT_FAILURE); + } + TRACE(0, ("Read netfd (%d): %d\n", netfd, len)); + handlenetinput(len); + } + } + } /* while (1) */ +}