/* * $Id: telnet.c,v 1.3 2000/05/12 19:41:47 erik Exp $ * Mini telnet implementation for busybox * * Copyright (C) 2000 by Randolph Chung * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * This version of telnet is adapted (but very heavily modified) from the * telnet in netkit-telnet 0.16, which is: * * Copyright (c) 1989 The Regents of the University of California. * All rights reserved. * * Original copyright notice is retained at the end of this file. */ #include "internal.h" #include #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" #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) { /* 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); } static void telnet_suboptions(int net) { char buf[256]; switch (SB_GET()) { case TELOPT_TTYPE: if (TFLAG_ISSET(TELOPT_TTYPE, WONT)) return; if (SB_EOF() || SB_GET() != TELQUAL_SEND) { return; } else { const char *name = getenv("TERM"); if (name) { snprintf(buf, sizeof(buf), "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, TELQUAL_IS, name, IAC, SE); write(net, buf, strlen(name)+6); } } break; case TELOPT_TSPEED: if (TFLAG_ISSET(TELOPT_TSPEED, WONT)) return; if (SB_EOF()) return; if (SB_GET() == TELQUAL_SEND) { /* long oospeed, iispeed; netoring.printf("%c%c%c%c%ld,%ld%c%c", IAC, SB, TELOPT_TSPEED, TELQUAL_IS, oospeed, iispeed, IAC, SE); */ } break; /* case TELOPT_LFLOW: if (TFLAG_ISSET(TELOPT_LFLOW, WONT)) return; if (SB_EOF()) return; switch(SB_GET()) { case 1: localflow = 1; break; case 0: localflow = 0; break; default: return; } break; case TELOPT_LINEMODE: if (TFLAG_ISSET(TELOPT_LINEMODE, WONT)) return; if (SB_EOF()) return; switch (SB_GET()) { case WILL: lm_will(subpointer, SB_LEN()); break; case WONT: lm_wont(subpointer, SB_LEN()); break; case DO: lm_do(subpointer, SB_LEN()); break; case DONT: lm_dont(subpointer, SB_LEN()); break; case LM_SLC: slc(subpointer, SB_LEN()); break; case LM_MODE: lm_mode(subpointer, SB_LEN(), 0); break; default: break; } break; case TELOPT_ENVIRON: if (SB_EOF()) return; switch(SB_PEEK()) { case TELQUAL_IS: case TELQUAL_INFO: if (TFLAG_ISSET(TELOPT_ENVIRON, DONT)) return; break; case TELQUAL_SEND: if (TFLAG_ISSET(TELOPT_ENVIRON, WONT)) return; break; default: return; } env_opt(subpointer, SB_LEN()); break; */ case TELOPT_XDISPLOC: if (TFLAG_ISSET(TELOPT_XDISPLOC, WONT)) return; if (SB_EOF()) return; if (SB_GET() == TELQUAL_SEND) { const char *dp = getenv("DISPLAY"); if (dp) { snprintf(buf, sizeof(buf), "%c%c%c%c%s%c%c", IAC, SB, TELOPT_XDISPLOC, TELQUAL_IS, dp, IAC, SE); write(net, buf, strlen(dp)+6); } } break; default: break; } } static void sighandler(int sig) { telnet_shutdown(); exit(0); } static int telnet_send(int tty, int net) { int ret; unsigned char ch; while ((ret = read(tty, &ch, 1)) > 0) { if (ch == 29) { /* 29 -- ctrl-] */ /* do something here? */ exit(0); } else { ret = write(net, &ch, 1); break; } } if (ret == -1 && errno == EWOULDBLOCK) return 1; return ret; } static int telnet_recv(int net, int tty) { /* globals: tr_state - telnet receive state */ int ret, c = 0; unsigned char ch; while ((ret = read(net, &ch, 1)) > 0) { c = ch; /* printf("%02X ", c); fflush(stdout); */ switch (tr_state) { case TS_DATA: if (c == IAC) { tr_state = TS_IAC; break; } else { write(tty, &c, 1); } break; case TS_IAC: switch (c) { case WILL: tr_state = TS_WILL; break; case WONT: tr_state = TS_WONT; break; case DO: tr_state = TS_DO; break; case DONT: tr_state = TS_DONT; break; case SB: SB_CLEAR(); tr_state = TS_SB; break; case IAC: write(tty, &c, 1); /* fallthrough */ default: tr_state = TS_DATA; } /* subnegotiation -- ignored for now */ case TS_SB: if (c == IAC) tr_state = TS_SE; else SB_ACCUM(c); break; case TS_SE: if (c == IAC) { SB_ACCUM(IAC); tr_state = TS_SB; } else if (c == SE) { SB_ACCUM(IAC); SB_ACCUM(SE); subpointer -= 2; SB_TERM(); telnet_suboptions(net); tr_state = TS_DATA; } /* otherwise this is an error, but we ignore it for now */ break; /* handle incoming requests */ case TS_WILL: printf("WILL %s\n", telopts[c]); if (!TFLAG_ISSET(c, DO)) { if (c == TELOPT_BINARY) { TFLAG_SET(c, DO); TFLAG_CLR(c, DONT); telnet_senddo(net, c); } else { TFLAG_SET(c, DONT); telnet_senddont(net, c); } } telnet_senddont(net, c); tr_state = TS_DATA; break; case TS_WONT: printf("WONT %s\n", telopts[c]); if (!TFLAG_ISSET(c, DONT)) { TFLAG_SET(c, DONT); TFLAG_CLR(c, DO); telnet_senddont(net, c); } tr_state = TS_DATA; break; case TS_DO: printf("DO %s\n", telopts[c]); if (!TFLAG_ISSET(c, WILL)) { if (c == TELOPT_BINARY) { TFLAG_SET(c, WILL); TFLAG_CLR(c, WONT); telnet_sendwill(net, c); } else { TFLAG_SET(c, WONT); telnet_sendwont(net, c); } } tr_state = TS_DATA; break; case TS_DONT: printf("DONT %s\n", telopts[c]); if (!TFLAG_ISSET(c, WONT)) { TFLAG_SET(c, WONT); TFLAG_CLR(c, WILL); telnet_sendwont(net, c); } tr_state = TS_DATA; break; } } if (ret == -1 && errno == EWOULDBLOCK) return 1; return ret; } /* ******************************************************************* */ static void telnet_init(void) { struct termios tmp_tc; cc_t esc = (']' & 0x1f); /* ctrl-] */ memset(options, 0, sizeof(options)); SB_CLEAR(); tcgetattr(STDIN, &saved_tc); tmp_tc = saved_tc; tmp_tc.c_lflag &= ~ECHO; /* echo */ tmp_tc.c_oflag |= ONLCR; /* NL->CRLF translation */ tmp_tc.c_iflag |= ICRNL; tmp_tc.c_iflag &= ~(IXANY|IXOFF|IXON); /* no flow control */ tmp_tc.c_lflag |= ISIG; /* trap signals */ tmp_tc.c_lflag &= ~ICANON; /* edit mode */ /* misc settings, compat with default telnet stuff */ tmp_tc.c_oflag &= ~TABDLY; /* 8-bit clean */ tmp_tc.c_iflag &= ~ISTRIP; tmp_tc.c_cflag &= ~(CSIZE|PARENB); tmp_tc.c_cflag |= saved_tc.c_cflag & (CSIZE|PARENB); tmp_tc.c_oflag |= OPOST; /* set escape character */ tmp_tc.c_cc[VEOL] = esc; tcsetattr(STDIN, TCSADRAIN, &tmp_tc); } static void telnet_start(char *hostname, int port) { struct hostent *host = 0; struct sockaddr_in addr; int s, c; fd_set rfds, wfds; memset(&addr, 0, sizeof(addr)); host = gethostbyname(hostname); if (!host) { fprintf(stderr, "Unknown host: %s\n", hostname); return; } addr.sin_family = host->h_addrtype; memcpy(&addr.sin_addr, host->h_addr, sizeof(addr.sin_addr)); addr.sin_port = htons(port); printf("Trying %s...\n", inet_ntoa(addr.sin_addr)); if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) PERROR("socket"); if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) PERROR("connect"); printf("Connected to %s\n", hostname); printf("Escape character is ^]\n"); signal(SIGINT, sighandler); signal(SIGQUIT, sighandler); signal(SIGPIPE, sighandler); signal(SIGWINCH, sighandler); /* make inputs nonblocking */ c = 1; ioctl(s, FIONBIO, &c); ioctl(STDIN, FIONBIO, &c); if (port == TELNETPORT) telnet_setoptions(s); /* shuttle data back and forth between tty and socket */ while (1) { FD_ZERO(&rfds); FD_ZERO(&wfds); FD_SET(s, &rfds); /* FD_SET(s, &wfds); */ FD_SET(STDIN, &rfds); /* FD_SET(STDOUT, &wfds); */ if ((c = select(s+1, &rfds, &wfds, 0, 0))) { if (c == -1) { /* handle errors */ PERROR("select"); } if (FD_ISSET(s, &rfds)) { /* input from network */ FD_CLR(s, &rfds); c = telnet_recv(s, STDOUT); if (c == 0) break; if (c < 0) PERROR("telnet_recv"); } if (FD_ISSET(STDIN, &rfds)) { /* input from tty */ FD_CLR(STDIN, &rfds); c = telnet_send(STDIN, s); if (c == 0) break; if (c < 0) PERROR("telnet_send"); } } } return; } static void telnet_shutdown(void) { printf("\n"); tcsetattr(STDIN, TCSANOW, &saved_tc); } #ifdef STANDALONE_TELNET void usage(const char *msg) { printf("%s", msg); exit(0); } int main(int argc, char **argv) #else int telnet_main(int argc, char **argv) #endif { int port = TELNETPORT; argc--; argv++; if (argc < 1) usage(telnet_usage); if (argc > 1) port = atoi(argv[1]); telnet_init(); atexit(telnet_shutdown); telnet_start(argv[0], port); return 0; } /* * Copyright (c) 1988, 1990 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */