342 lines
7.8 KiB
C
342 lines
7.8 KiB
C
/* telnet.c - Telnet client.
|
|
*
|
|
* Copyright 2012 Madhur Verma <mad.flexi@gmail.com>
|
|
* Copyright 2013 Kyungwan Han <asura321@gmail.com>
|
|
* Modified by Ashwini Kumar <ak.ashwini1981@gmail.com>
|
|
*
|
|
* Not in SUSv4.
|
|
|
|
USE_TELNET(NEWTOY(telnet, "<1>2", TOYFLAG_BIN))
|
|
|
|
config TELNET
|
|
bool "telnet"
|
|
default n
|
|
help
|
|
usage: telnet HOST [PORT]
|
|
|
|
Connect to telnet server
|
|
*/
|
|
|
|
#define FOR_telnet
|
|
#include "toys.h"
|
|
#include <arpa/telnet.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/poll.h>
|
|
|
|
GLOBALS(
|
|
int port;
|
|
int sfd;
|
|
char buff[128];
|
|
int pbuff;
|
|
char iac[256];
|
|
int piac;
|
|
char *ttype;
|
|
struct termios def_term;
|
|
struct termios raw_term;
|
|
uint8_t term_ok;
|
|
uint8_t term_mode;
|
|
uint8_t flags;
|
|
unsigned win_width;
|
|
unsigned win_height;
|
|
)
|
|
|
|
#define DATABUFSIZE 128
|
|
#define IACBUFSIZE 256
|
|
#define CM_TRY 0
|
|
#define CM_ON 1
|
|
#define CM_OFF 2
|
|
#define UF_ECHO 0x01
|
|
#define UF_SGA 0x02
|
|
|
|
// sets terminal mode: LINE or CHARACTER based om internal stat.
|
|
static char const es[] = "\r\nEscape character is ";
|
|
static void set_mode(void)
|
|
{
|
|
if (TT.flags & UF_ECHO) {
|
|
if (TT.term_mode == CM_TRY) {
|
|
TT.term_mode = CM_ON;
|
|
printf("\r\nEntering character mode%s'^]'.\r\n", es);
|
|
if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
|
|
}
|
|
} else {
|
|
if (TT.term_mode != CM_OFF) {
|
|
TT.term_mode = CM_OFF;
|
|
printf("\r\nEntering line mode%s'^C'.\r\n", es);
|
|
if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
|
|
}
|
|
}
|
|
}
|
|
|
|
// flushes all data in IAC buff to server.
|
|
static void flush_iac(void)
|
|
{
|
|
int wlen = write(TT.sfd, TT.iac, TT.piac);
|
|
|
|
if(wlen <= 0) error_msg("IAC : send failed.");
|
|
TT.piac = 0;
|
|
}
|
|
|
|
// puts DATA in iac buff of length LEN and updates iac buff pointer.
|
|
static void put_iac(int len, ...)
|
|
{
|
|
va_list va;
|
|
|
|
if(TT.piac + len >= IACBUFSIZE) flush_iac();
|
|
va_start(va, len);
|
|
for(;len > 0; TT.iac[TT.piac++] = (uint8_t)va_arg(va, int), len--);
|
|
va_end(va);
|
|
}
|
|
|
|
// puts string STR in iac buff and updates iac buff pointer.
|
|
static void str_iac(char *str)
|
|
{
|
|
int len = strlen(str);
|
|
|
|
if(TT.piac + len + 1 >= IACBUFSIZE) flush_iac();
|
|
strcpy(&TT.iac[TT.piac], str);
|
|
TT.piac += len+1;
|
|
}
|
|
|
|
static void handle_esc(void)
|
|
{
|
|
char input;
|
|
|
|
if(toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
|
|
xwrite(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", 114);
|
|
|
|
if (read(STDIN_FILENO, &input, 1) <= 0) {
|
|
if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
|
|
exit(0);
|
|
}
|
|
|
|
switch (input) {
|
|
case 'l':
|
|
if (!toys.signal) {
|
|
TT.term_mode = CM_TRY;
|
|
TT.flags &= ~(UF_ECHO | UF_SGA);
|
|
set_mode();
|
|
put_iac(6, IAC,DONT,TELOPT_ECHO,IAC,DONT, TELOPT_SGA);
|
|
flush_iac();
|
|
goto ret;
|
|
}
|
|
break;
|
|
case 'c':
|
|
if (toys.signal) {
|
|
TT.term_mode = CM_TRY;
|
|
TT.flags |= (UF_ECHO | UF_SGA);
|
|
set_mode();
|
|
put_iac(6, IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
|
|
flush_iac();
|
|
goto ret;
|
|
}
|
|
break;
|
|
case 'z':
|
|
if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
|
|
kill(0, SIGTSTP);
|
|
if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
|
|
break;
|
|
case 'e':
|
|
if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
|
|
exit(0);
|
|
default: break;
|
|
}
|
|
|
|
xwrite(1, "continuing...\r\n", 15);
|
|
if (toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
|
|
|
|
ret:
|
|
toys.signal = 0;
|
|
}
|
|
|
|
/*
|
|
* handles telnet SUB NEGOTIATIONS
|
|
* only terminal type is supported.
|
|
*/
|
|
static void handle_negotiations(void)
|
|
{
|
|
char opt = TT.buff[TT.pbuff++];
|
|
|
|
switch(opt) {
|
|
case TELOPT_TTYPE:
|
|
opt = TT.buff[TT.pbuff++];
|
|
if(opt == TELQUAL_SEND) {
|
|
put_iac(4, IAC,SB,TELOPT_TTYPE,TELQUAL_IS);
|
|
str_iac(TT.ttype);
|
|
put_iac(2, IAC,SE);
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* handles server's DO DONT WILL WONT requests.
|
|
* supports ECHO, SGA, TTYPE, NAWS
|
|
*/
|
|
static void handle_ddww(char ddww)
|
|
{
|
|
char opt = TT.buff[TT.pbuff++];
|
|
|
|
switch (opt) {
|
|
case TELOPT_ECHO: /* ECHO */
|
|
if (ddww == DO) put_iac(3, IAC,WONT,TELOPT_ECHO);
|
|
if(ddww == DONT) break;
|
|
if (TT.flags & UF_ECHO) {
|
|
if (ddww == WILL) return;
|
|
} else if (ddww == WONT) return;
|
|
if (TT.term_mode != CM_OFF) TT.flags ^= UF_ECHO;
|
|
(TT.flags & UF_ECHO)? put_iac(3, IAC,DO,TELOPT_ECHO) :
|
|
put_iac(3, IAC,DONT,TELOPT_ECHO);
|
|
set_mode();
|
|
printf("\r\n");
|
|
break;
|
|
|
|
case TELOPT_SGA: /* Supress GO Ahead */
|
|
if (TT.flags & UF_SGA){ if (ddww == WILL) return;
|
|
} else if (ddww == WONT) return;
|
|
|
|
TT.flags ^= UF_SGA;
|
|
(TT.flags & UF_SGA)? put_iac(3, IAC,DO,TELOPT_SGA) :
|
|
put_iac(3, IAC,DONT,TELOPT_SGA);
|
|
break;
|
|
|
|
case TELOPT_TTYPE: /* Terminal Type */
|
|
(TT.ttype)? put_iac(3, IAC,WILL,TELOPT_TTYPE):
|
|
put_iac(3, IAC,WONT,TELOPT_TTYPE);
|
|
break;
|
|
|
|
case TELOPT_NAWS: /* Window Size */
|
|
put_iac(3, IAC,WILL,TELOPT_NAWS);
|
|
put_iac(9, IAC,SB,TELOPT_NAWS,(TT.win_width >> 8) & 0xff,
|
|
TT.win_width & 0xff,(TT.win_height >> 8) & 0xff,
|
|
TT.win_height & 0xff,IAC,SE);
|
|
break;
|
|
|
|
default: /* Default behaviour is to say NO */
|
|
if(ddww == WILL) put_iac(3, IAC,DONT,opt);
|
|
if(ddww == DO) put_iac(3, IAC,WONT,opt);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* parses data which is read from server of length LEN.
|
|
* and passes it to console.
|
|
*/
|
|
static int read_server(int len)
|
|
{
|
|
int i = 0;
|
|
char curr;
|
|
TT.pbuff = 0;
|
|
|
|
do {
|
|
curr = TT.buff[TT.pbuff++];
|
|
if (curr == IAC) {
|
|
curr = TT.buff[TT.pbuff++];
|
|
switch (curr) {
|
|
case DO: /* FALLTHROUGH */
|
|
case DONT: /* FALLTHROUGH */
|
|
case WILL: /* FALLTHROUGH */
|
|
case WONT:
|
|
handle_ddww(curr);
|
|
break;
|
|
case SB:
|
|
handle_negotiations();
|
|
break;
|
|
case SE:
|
|
break;
|
|
default: break;
|
|
}
|
|
} else {
|
|
toybuf[i++] = curr;
|
|
if (curr == '\r') { curr = TT.buff[TT.pbuff++];
|
|
if (curr != '\0') TT.pbuff--;
|
|
}
|
|
}
|
|
} while (TT.pbuff < len);
|
|
|
|
if (i) xwrite(STDIN_FILENO, toybuf, i);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* parses data which is read from console of length LEN
|
|
* and passes it to server.
|
|
*/
|
|
static void write_server(int len)
|
|
{
|
|
char *c = (char*)TT.buff;
|
|
int i = 0;
|
|
|
|
for (; len > 0; len--, c++) {
|
|
if (*c == 0x1d) {
|
|
handle_esc();
|
|
return;
|
|
}
|
|
toybuf[i++] = *c;
|
|
if (*c == IAC) toybuf[i++] = *c; /* IAC -> IAC IAC */
|
|
else if (*c == '\r') toybuf[i++] = '\0'; /* CR -> CR NUL */
|
|
}
|
|
if(i) xwrite(TT.sfd, toybuf, i);
|
|
}
|
|
|
|
void telnet_main(void)
|
|
{
|
|
char *port = "23";
|
|
int set = 1, len;
|
|
struct pollfd pfds[2];
|
|
|
|
TT.win_width = 80; //columns
|
|
TT.win_height = 24; //rows
|
|
|
|
if (toys.optc == 2) port = toys.optargs[1];
|
|
|
|
TT.ttype = getenv("TERM");
|
|
if(!TT.ttype) TT.ttype = "";
|
|
if(strlen(TT.ttype) > IACBUFSIZE-1) TT.ttype[IACBUFSIZE - 1] = '\0';
|
|
|
|
if (!tcgetattr(0, &TT.def_term)) {
|
|
TT.term_ok = 1;
|
|
TT.raw_term = TT.def_term;
|
|
cfmakeraw(&TT.raw_term);
|
|
}
|
|
terminal_size(&TT.win_width, &TT.win_height);
|
|
|
|
TT.sfd = xconnect(*toys.optargs, port, 0, SOCK_STREAM, IPPROTO_TCP, 0);
|
|
setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
|
|
setsockopt(TT.sfd, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set));
|
|
|
|
pfds[0].fd = STDIN_FILENO;
|
|
pfds[0].events = POLLIN;
|
|
pfds[1].fd = TT.sfd;
|
|
pfds[1].events = POLLIN;
|
|
|
|
signal(SIGINT, generic_signal);
|
|
while(1) {
|
|
if(TT.piac) flush_iac();
|
|
if(poll(pfds, 2, -1) < 0) {
|
|
if (toys.signal) handle_esc();
|
|
else sleep(1);
|
|
|
|
continue;
|
|
}
|
|
if(pfds[0].revents) {
|
|
len = read(STDIN_FILENO, TT.buff, DATABUFSIZE);
|
|
if(len > 0) write_server(len);
|
|
else return;
|
|
}
|
|
if(pfds[1].revents) {
|
|
len = read(TT.sfd, TT.buff, DATABUFSIZE);
|
|
if(len > 0) read_server(len);
|
|
else {
|
|
printf("Connection closed by foreign host\r\n");
|
|
if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|