X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Ftelnet.c;h=c835c7a4525b2450fdc820428e4cdc551614852c;hb=5929edc1fac4340f99ed84e92bf3a2bedd4177c2;hp=8c58521eb00bdd3897c4a7acc07a84b3b8211579;hpb=94f5e0ba7ca7af260f4bf2d8c77b8e6f6f528b18;p=oweals%2Fbusybox.git diff --git a/networking/telnet.c b/networking/telnet.c index 8c58521eb..c835c7a45 100644 --- a/networking/telnet.c +++ b/networking/telnet.c @@ -1,502 +1,729 @@ +/* vi: set sw=4 ts=4: */ /* - * $Id: telnet.c,v 1.2 2000/05/01 19:10:52 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 +#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 "busybox.h" + +#if 0 +enum { DOTRACE = 1 }; +#endif + +#ifdef DOTRACE +#include /* for inet_ntoa()... */ +#define TRACE(x, y) do { if (x) printf y; } while (0) +#else +#define TRACE(x, y) +#endif + +#define DATABUFSIZE 128 +#define IACBUFSIZE 128 + +enum { + 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, +}; + +#define WriteCS(fd, str) write(fd, str, sizeof str -1) + +typedef unsigned char byte; + +/* use globals to reduce size ??? */ /* test this hypothesis later */ +static 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[DATABUFSIZE]; /* allocating so static size is smaller */ + byte telstate; /* telnet negotiation state from network input */ + byte telwish; /* DO, DONT, WILL, WONT */ + byte charmode; + byte telflags; + byte gotsig; + byte do_termios; + /* buffer to handle telnet negotiations */ + char iacbuf[IACBUFSIZE]; + 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) +#endif + +static inline void iacflush(void) { - /* - telnet_sendwill(s, TELOPT_NAWS); TFLAG_SET(TELOPT_NAWS, WILL); - telnet_sendwill(s, TELOPT_TSPEED); TFLAG_SET(TELOPT_TSPEED, WILL); - telnet_sendwill(s, TELOPT_NEW_ENVIRON); TFLAG_SET(TELOPT_NEW_ENVIRON, WILL); - telnet_senddo(s, TELOPT_STATUS); TFLAG_SET(TELOPT_STATUS, DO); - telnet_sendwill(s, TELOPT_TTYPE); TFLAG_SET(TELOPT_TTYPE, WILL); - */ - telnet_senddo(s, TELOPT_SGA); TFLAG_SET(TELOPT_SGA, DO); - telnet_sendwill(s, TELOPT_LFLOW); TFLAG_SET(TELOPT_LFLOW, WILL); - telnet_sendwill(s, TELOPT_LINEMODE); TFLAG_SET(TELOPT_LINEMODE, WILL); - telnet_senddo(s, TELOPT_BINARY); TFLAG_SET(TELOPT_BINARY, DO); - telnet_sendwill(s, TELOPT_BINARY); TFLAG_SET(TELOPT_BINARY, WILL); + write(G.netfd, G.iacbuf, G.iaclen); + G.iaclen = 0; } -static void telnet_suboptions(int net) +/* 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); + +/* Some globals */ +static int one = 1; + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static char *ttype; +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static const char *autologin; +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static int win_width, win_height; +#endif + +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 (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 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 to. + */ + + 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(G.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); - } - 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 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: + 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)) + G.telstate = TS_0; + break; + } + } + if (G.telstate) + { + if (G.iaclen) iacflush(); + if (G.telstate == TS_0) G.telstate = 0; + + len = cstart; } - if (ret == -1 && errno == EWOULDBLOCK) return 1; - return ret; + + if (len) + write(1, G.buf, len); +} + + +/* ******************************* */ + +static inline 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); } -/* ******************************************************************* */ -static void telnet_init(void) +#if 0 +static void putiac1(byte c) { - 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); + if (G.iaclen + 2 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(c); } +#endif -static void telnet_start(char *hostname, int port) +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static void putiac_subopt(byte c, char *str) { - 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; + 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 + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static void putiac_subopt_autologin(void) +{ + int len = strlen(autologin) + 6; // (2 + 1 + 1 + strlen + 2) + 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(*autologin) + putiac(*autologin++); + + putiac(IAC); + putiac(SE); +} +#endif + +#ifdef CONFIG_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 + +/* void putiacstring (subneg strings) */ + +/* ******************************* */ + +static char const escapecharis[] = "\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(); + } } - 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"); - } + else + { + if (G.charmode != CHM_OFF) { + G.charmode = CHM_OFF; + printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); + cookmode(); } } - - return; } -static void telnet_shutdown(void) +/* ******************************* */ + +static void will_charmode(void) { - printf("\n"); - tcsetattr(STDIN, TCSANOW, &saved_tc); + G.charmode = CHM_TRY; + G.telflags |= (UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DO, TELOPT_ECHO); + putiac2(DO, TELOPT_SGA); + iacflush(); } -#ifdef STANDALONE_TELNET -void usage(const char *msg) +static void do_linemode(void) { - printf("%s", msg); - exit(0); + G.charmode = CHM_TRY; + G.telflags &= ~(UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DONT, TELOPT_ECHO); + putiac2(DONT, TELOPT_SGA); + iacflush(); } -int main(int argc, char **argv) -#else -int telnet_main(int argc, char **argv) +/* ******************************* */ + +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(void) +{ + /* 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; + } + 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 inline 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; + + if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */ + putiac2(DO, TELOPT_SGA); + else + putiac2(DONT, TELOPT_SGA); + + return; +} + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static inline void to_ttype(void) +{ + /* Tell server we will (or won't) do TTYPE */ + + if(ttype) + putiac2(WILL, TELOPT_TTYPE); + else + putiac2(WONT, TELOPT_TTYPE); + + return; +} #endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static inline void to_new_environ(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 (or will not) do AUTOLOGIN */ + + if (autologin) + putiac2(WILL, TELOPT_NEW_ENVIRON); + else + putiac2(WONT, TELOPT_NEW_ENVIRON); + + return; } +#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. - */ +#ifdef CONFIG_FEATURE_AUTOWIDTH +static inline void to_naws(void) +{ + /* Tell server we will do NAWS */ + putiac2(WILL, TELOPT_NAWS); + return; +} +#endif + +static void telopt(byte c) +{ + switch (c) + { + case TELOPT_ECHO: to_echo(); break; + case TELOPT_SGA: to_sga(); break; +#ifdef CONFIG_FEATURE_TELNET_TTYPE + case TELOPT_TTYPE: to_ttype();break; +#endif +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + case TELOPT_NEW_ENVIRON: to_new_environ(); break; +#endif +#ifdef CONFIG_FEATURE_AUTOWIDTH + case TELOPT_NAWS: to_naws(); + putiac_naws(c, win_width, 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; +#ifdef CONFIG_FEATURE_TELNET_TTYPE + else + if (c == TELOPT_TTYPE) + putiac_subopt(TELOPT_TTYPE,ttype); +#endif +#ifdef CONFIG_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 fgotsig(int sig) +{ + G.gotsig = sig; +} + + +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); +} + +int telnet_main(int argc, char** argv) +{ + int len; + struct sockaddr_in s_in; +#ifdef USE_POLL + struct pollfd ufds[2]; +#else + fd_set readfds; + int maxfd; +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH + get_terminal_width_height(0, &win_width, &win_height); +#endif + +#ifdef CONFIG_FEATURE_TELNET_TTYPE + ttype = getenv("TERM"); +#endif + + memset(&G, 0, sizeof G); + + if (tcgetattr(0, &G.termios_def) >= 0) { + G.do_termios = 1; + + G.termios_raw = G.termios_def; + cfmakeraw(&G.termios_raw); + } + + if (argc < 2) + bb_show_usage(); + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + if (1 & bb_getopt_ulflags(argc, argv, "al:", &autologin)) + autologin = getenv("USER"); + + if (optind < argc) { + bb_lookup_host(&s_in, argv[optind++]); + s_in.sin_port = bb_lookup_port((optind < argc) ? argv[optind++] : + "telnet", "tcp", 23); + if (optind < argc) + bb_show_usage(); + } else + bb_show_usage(); +#else + bb_lookup_host(&s_in, argv[1]); + s_in.sin_port = bb_lookup_port((argc == 3) ? argv[2] : "telnet", "tcp", 23); +#endif + + G.netfd = xconnect(&s_in); + + setsockopt(G.netfd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); + + 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 + + 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 + { + len = read(0, G.buf, DATABUFSIZE); + + if (len <= 0) + doexit(0); + + 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(G.netfd, &rfds)) +#endif + { + len = read(G.netfd, G.buf, DATABUFSIZE); + + if (len <= 0) + { + WriteCS(1, "Connection closed by foreign host.\r\n"); + doexit(1); + } + TRACE(0, ("Read netfd (%d): %d\n", G.netfd, len)); + + handlenetinput(len); + } + } + } +}