📄 telnet.c
字号:
/* vi: set sw=4 ts=4: *//* * telnet implementation for busybox * * Author: Tomi Ollila <too@iki.fi> * Copyright (C) 1994-2000 by Tomi Ollila * * Created: Thu Apr 7 13:29:41 1994 too * Last modified: Fri Jun 9 14:34:24 2000 too * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * 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 * * HISTORY * Revision 3.1 1994/04/17 11:31:54 too * initial revision * Modified 2000/06/13 for inclusion in BusyBox by Erik Andersen <andersee@debian.org> * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan * <jam@ltsp.org> * */#include <termios.h>#include <unistd.h>#include <errno.h>#include <stdlib.h>#include <stdarg.h>#include <string.h>#include <signal.h>#include <arpa/telnet.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netdb.h>#include "busybox.h"#ifdef BB_FEATURE_AUTOWIDTH# include <sys/ioctl.h>#endif#if 0static const int DOTRACE = 1;#endif#ifdef DOTRACE#include <arpa/inet.h> /* for inet_ntoa()... */#define TRACE(x, y) do { if (x) printf y; } while (0)#else#define TRACE(x, y) #endif#if 0#define USE_POLL#include <sys/poll.h>#else#include <sys/time.h>#endif#define DATABUFSIZE 128#define IACBUFSIZE 128static const int CHM_TRY = 0;static const int CHM_ON = 1;static const int CHM_OFF = 2;static const int UF_ECHO = 0x01;static const int UF_SGA = 0x02;enum { 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; /* 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_PTRstruct Globalvars * Gptr;#define G (*Gptr)#elsestatic struct Globalvars G;#endifstatic inline void iacflush(){ write(G.netfd, G.iacbuf, G.iaclen); G.iaclen = 0;}/* Function prototypes */static int getport(char * p);static struct in_addr getserver(char * p);static int create_socket();static void setup_sockaddr_in(struct sockaddr_in * addr, int port);static int remote_connect(struct in_addr addr, int port);static void rawmode();static void cookmode();static void do_linemode();static void will_charmode();static void telopt(byte c);static int subneg(byte c);#if 0static int local_bind(int port);#endif/* Some globals */static int one = 1;#ifdef BB_FEATURE_TELNET_TTYPEstatic char *ttype;#endif#ifdef BB_FEATURE_AUTOWIDTHstatic int win_width, win_height;#endifstatic void doexit(int ev){ cookmode(); exit(ev);} static void conescape(){ char b; if (G.gotsig) /* came from line mode... go raw */ rawmode(); WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n" " l go to line mode\r\n" " c go to character mode\r\n" " z suspend telnet\r\n" " e exit telnet\r\n"); if (read(0, &b, 1) <= 0) doexit(1); switch (b) { case 'l': if (!G.gotsig) { do_linemode(); goto rrturn; } break; case 'c': if (G.gotsig) { will_charmode(); goto rrturn; } break; case 'z': cookmode(); kill(0, SIGTSTP); rawmode(); break; case 'e': doexit(0); } WriteCS(1, "continuing...\r\n"); if (G.gotsig) cookmode(); rrturn: G.gotsig = 0; }static void handlenetoutput(int len){ /* 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 = 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 (j > 0 ) write(G.netfd, outbuf, j);}static void handlenetinput(int len){ 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) == TRUE) 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(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);}#if 0static void putiac1(byte c){ if (G.iaclen + 2 > IACBUFSIZE) iacflush(); putiac(IAC); putiac(c);}#endif#ifdef BB_FEATURE_TELNET_TTYPEstatic 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#ifdef BB_FEATURE_AUTOWIDTHstatic 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(){ 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(); } }}/* ******************************* */static void will_charmode(){ G.charmode = CHM_TRY; G.telflags |= (UF_ECHO | UF_SGA); setConMode(); putiac2(DO, TELOPT_ECHO); putiac2(DO, TELOPT_SGA); iacflush();}static void do_linemode(){ G.charmode = CHM_TRY; G.telflags &= ~(UF_ECHO | UF_SGA); setConMode(); putiac2(DONT, TELOPT_ECHO); putiac2(DONT, TELOPT_SGA); iacflush();}/* ******************************* */static inline void to_notsup(char c){ if (G.telwish == WILL) putiac2(DONT, c); else if (G.telwish == DO) putiac2(WONT, c);}static inline void to_echo(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 BB_FEATURE_TELNET_TTYPEstatic 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 BB_FEATURE_AUTOWIDTHstatic inline void to_naws(void){ /* Tell server we will do NAWS */ putiac2(WILL, TELOPT_NAWS); return;}#endifstatic void telopt(byte c){ switch (c) { case TELOPT_ECHO: to_echo(); break; case TELOPT_SGA: to_sga(); break;#ifdef BB_FEATURE_TELNET_TTYPE case TELOPT_TTYPE: to_ttype(); break;#endif#ifdef BB_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 BB_FEATURE_TELNET_TTYPE else if (c == TELOPT_TTYPE) putiac_subopt(TELOPT_TTYPE,ttype);#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(){ tcsetattr(0, TCSADRAIN, &G.termios_raw);} static void cookmode(){ tcsetattr(0, TCSADRAIN, &G.termios_def);}extern int telnet_main(int argc, char** argv){ struct in_addr host; int port; int len;#ifdef USE_POLL struct pollfd ufds[2];#else fd_set readfds; int maxfd;#endif #ifdef BB_FEATURE_AUTOWIDTH struct winsize winp; if( ioctl(0, TIOCGWINSZ, &winp) == 0 ) { win_width = winp.ws_col; win_height = winp.ws_row; }#endif#ifdef BB_FEATURE_TELNET_TTYPE ttype = getenv("TERM");#endif memset(&G, 0, sizeof G); if (tcgetattr(0, &G.termios_def) < 0) exit(1); G.termios_raw = G.termios_def; cfmakeraw(&G.termios_raw); if (argc < 2) show_usage(); port = (argc > 2)? getport(argv[2]): 23; host = getserver(argv[1]); G.netfd = remote_connect(host, port); signal(SIGINT, fgotsig);#ifdef USE_POLL ufds[0].fd = 0; ufds[1].fd = G.netfd; ufds[0].events = ufds[1].events = POLLIN;#else FD_ZERO(&readfds); FD_SET(0, &readfds); FD_SET(G.netfd, &readfds); maxfd = G.netfd + 1;#endif 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); } } }}static int getport(char * p){ unsigned int port = atoi(p); if ((unsigned)(port - 1 ) > 65534) { error_msg_and_die("%s: bad port number", p); } return port;}static struct in_addr getserver(char * host){ struct in_addr addr; struct hostent * he; he = xgethostbyname(host); memcpy(&addr, he->h_addr, sizeof addr); TRACE(1, ("addr: %s\n", inet_ntoa(addr))); return addr;}static int create_socket(){ return socket(AF_INET, SOCK_STREAM, 0);}static void setup_sockaddr_in(struct sockaddr_in * addr, int port){ memset(addr, 0, sizeof(struct sockaddr_in)); addr->sin_family = AF_INET; addr->sin_port = htons(port);} static int remote_connect(struct in_addr addr, int port){ struct sockaddr_in s_addr; int s = create_socket(); setup_sockaddr_in(&s_addr, port); s_addr.sin_addr = addr; setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); if (connect(s, (struct sockaddr *)&s_addr, sizeof s_addr) < 0) { perror_msg_and_die("Unable to connect to remote host"); } return s;}/*Local Variables:c-file-style: "linux"c-basic-offset: 4tab-width: 4End:*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -