busybox/networking/telnet.c
Eric Andersen cd8c436d81 Latest patch from vodz:
-- reverse resolve network name and cache in route and ifconfig
	applets, fix print nslookup server name if compile without
	uClibc, fix route crashe 'route add', fix warnings compile
	networking and pwd_grp applets
2001-11-10 11:22:46 +00:00

710 lines
13 KiB
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 into BusyBox by Erik Andersen
* <andersen@lineo.com>
* 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"
#if 0
static 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 128
static 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_PTR
struct Globalvars * Gptr;
#define G (*Gptr)
#else
static struct Globalvars G;
#endif
static inline void iacflush(void)
{
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 void setup_sockaddr_in(struct sockaddr_in * addr, int port);
static int remote_connect(struct in_addr addr, int port);
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);
#if 0
static int local_bind(int port);
#endif
/* Some globals */
static int one = 1;
#ifdef CONFIG_FEATURE_TELNET_TTYPE
static char *ttype;
#endif
static void doexit(int ev)
{
cookmode();
exit(ev);
}
static void conescape(void)
{
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 */
int i;
byte * p = G.buf;
for (i = len; i > 0; i--, p++)
{
if (*p == 0x1d)
{
conescape();
return;
}
if (*p == 0xff)
*p = 0x7f;
}
write(G.netfd, G.buf, len);
}
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 0
static void putiac1(byte c)
{
if (G.iaclen + 2 > IACBUFSIZE)
iacflush();
putiac(IAC);
putiac(c);
}
#endif
#ifdef CONFIG_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
/* 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();
}
}
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(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)
{
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 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
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
default: to_notsup(c); break;
}
}
/* ******************************* */
/* subnegotiation -- ignore all (except TTYPE) */
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
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)
{
tcsetattr(0, TCSADRAIN, &G.termios_raw);
}
static void cookmode(void)
{
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 CONFIG_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(void)
{
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);
}
#if 0
static int local_bind(int port)
{
struct sockaddr_in s_addr;
int s = create_socket();
setup_sockaddr_in(&s_addr, port);
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
if (bind(s, &s_addr, sizeof s_addr) < 0)
{
char * e = sys_errlist[errno];
syserrorexit("bind");
exit(1);
}
listen(s, 1);
return s;
}
#endif
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: 4
tab-width: 4
End:
*/