You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
6.7 KiB
272 lines
6.7 KiB
/* 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>
|
|
|
|
GLOBALS(
|
|
int sock;
|
|
char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
|
|
struct termios old_term;
|
|
struct termios raw_term;
|
|
uint8_t mode;
|
|
int echo, sga;
|
|
int state, request;
|
|
)
|
|
|
|
#define NORMAL 0
|
|
#define SAW_IAC 1
|
|
#define SAW_WWDD 2
|
|
#define SAW_SB 3
|
|
#define SAW_SB_TTYPE 4
|
|
#define WANT_IAC 5
|
|
#define WANT_SE 6
|
|
#define SAW_CR 10
|
|
|
|
#define CM_TRY 0
|
|
#define CM_ON 1
|
|
#define CM_OFF 2
|
|
|
|
static void raw(int raw)
|
|
{
|
|
tcsetattr(0, TCSADRAIN, raw ? &TT.raw_term : &TT.old_term);
|
|
}
|
|
|
|
static void slc(int line)
|
|
{
|
|
TT.mode = line ? CM_OFF : CM_ON;
|
|
xprintf("Entering %s mode\r\nEscape character is '^%c'.\r\n",
|
|
line ? "line" : "character", line ? 'C' : ']');
|
|
raw(!line);
|
|
}
|
|
|
|
static void set_mode(void)
|
|
{
|
|
if (TT.echo) {
|
|
if (TT.mode == CM_TRY) slc(0);
|
|
} else if (TT.mode != CM_OFF) slc(1);
|
|
}
|
|
|
|
static void handle_esc(void)
|
|
{
|
|
char input;
|
|
|
|
if (toys.signal) raw(1);
|
|
|
|
// This matches busybox telnet, not BSD telnet.
|
|
xputsn("\r\n"
|
|
"Console escape. Commands are:\r\n"
|
|
"\r\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"
|
|
"\r\n"
|
|
"telnet> ");
|
|
// In particular, the boxes only read a single character, not a line.
|
|
if (read(0, &input, 1) <= 0 || input == 4 || input == 'e') {
|
|
xprintf("Connection closed.\r\n");
|
|
xexit();
|
|
}
|
|
|
|
if (input == 'l') {
|
|
if (!toys.signal) {
|
|
TT.mode = CM_TRY;
|
|
TT.echo = TT.sga = 0;
|
|
set_mode();
|
|
dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DONT,TELOPT_ECHO,IAC,DONT,TELOPT_SGA);
|
|
goto ret;
|
|
}
|
|
} else if (input == 'c') {
|
|
if (toys.signal) {
|
|
TT.mode = CM_TRY;
|
|
TT.echo = TT.sga = 1;
|
|
set_mode();
|
|
dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
|
|
goto ret;
|
|
}
|
|
} else if (input == 'z') {
|
|
raw(0);
|
|
kill(0, SIGTSTP);
|
|
raw(1);
|
|
}
|
|
|
|
xprintf("telnet %s %s\r\n", toys.optargs[0], toys.optargs[1] ?: "23");
|
|
if (toys.signal) raw(0);
|
|
|
|
ret:
|
|
toys.signal = 0;
|
|
}
|
|
|
|
// Handle WILL WONT DO DONT requests from the server.
|
|
static void handle_wwdd(char opt)
|
|
{
|
|
if (opt == TELOPT_ECHO) {
|
|
if (TT.request == DO) dprintf(TT.sock, "%c%c%c", IAC, WONT, TELOPT_ECHO);
|
|
if (TT.request == DONT) return;
|
|
if (TT.echo) {
|
|
if (TT.request == WILL) return;
|
|
} else if (TT.request == WONT) return;
|
|
if (TT.mode != CM_OFF) TT.echo ^= 1;
|
|
dprintf(TT.sock, "%c%c%c", IAC, TT.echo ? DO : DONT, TELOPT_ECHO);
|
|
set_mode();
|
|
} else if (opt == TELOPT_SGA) { // Suppress Go Ahead
|
|
if (TT.sga) {
|
|
if (TT.request == WILL) return;
|
|
} else if (TT.request == WONT) return;
|
|
TT.sga ^= 1;
|
|
dprintf(TT.sock, "%c%c%c", IAC, TT.sga ? DO : DONT, TELOPT_SGA);
|
|
} else if (opt == TELOPT_TTYPE) { // Terminal TYPE
|
|
dprintf(TT.sock, "%c%c%c", IAC, WILL, TELOPT_TTYPE);
|
|
} else if (opt == TELOPT_NAWS) { // Negotiate About Window Size
|
|
unsigned cols = 80, rows = 24;
|
|
|
|
terminal_size(&cols, &rows);
|
|
dprintf(TT.sock, "%c%c%c%c%c%c%c%c%c%c%c%c", IAC, WILL, TELOPT_NAWS,
|
|
IAC, SB, TELOPT_NAWS, cols>>8, cols, rows>>8, rows,
|
|
IAC, SE);
|
|
} else {
|
|
// Say "no" to anything we don't understand.
|
|
dprintf(TT.sock, "%c%c%c", IAC, (TT.request == WILL) ? DONT : WONT, opt);
|
|
}
|
|
}
|
|
|
|
static void handle_server_output(int n)
|
|
{
|
|
char *p = TT.buf, *end = TT.buf + n, ch;
|
|
int i = 0;
|
|
|
|
// Possibilities:
|
|
//
|
|
// 1. Regular character
|
|
// 2. IAC [WILL|WONT|DO|DONT] option
|
|
// 3. IAC SB option arg... IAC SE
|
|
//
|
|
// The only subnegotiation we support is IAC SB TTYPE SEND IAC SE, so we just
|
|
// hard-code that into our state machine rather than having a more general
|
|
// "collect the subnegotation into a buffer and handle it after we've seen
|
|
// the IAC SE at the end". It's 2021, so we're unlikely to need more.
|
|
|
|
while (p < end) {
|
|
ch = *p++;
|
|
if (TT.state == SAW_IAC) {
|
|
if (ch >= WILL && ch <= DONT) {
|
|
TT.state = SAW_WWDD;
|
|
TT.request = ch;
|
|
} else if (ch == SB) {
|
|
TT.state = SAW_SB;
|
|
} else {
|
|
TT.state = NORMAL;
|
|
}
|
|
} else if (TT.state == SAW_WWDD) {
|
|
handle_wwdd(ch);
|
|
TT.state = NORMAL;
|
|
} else if (TT.state == SAW_SB) {
|
|
if (ch == TELOPT_TTYPE) TT.state = SAW_SB_TTYPE;
|
|
else TT.state = WANT_IAC;
|
|
} else if (TT.state == SAW_SB_TTYPE) {
|
|
if (ch == TELQUAL_SEND) {
|
|
dprintf(TT.sock, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, TELQUAL_IS,
|
|
getenv("TERM") ?: "NVT", IAC, SE);
|
|
}
|
|
TT.state = WANT_IAC;
|
|
} else if (TT.state == WANT_IAC) {
|
|
if (ch == IAC) TT.state = WANT_SE;
|
|
} else if (TT.state == WANT_SE) {
|
|
if (ch == SE) TT.state = NORMAL;
|
|
} else if (ch == IAC) {
|
|
TT.state = SAW_IAC;
|
|
} else {
|
|
if (TT.state == SAW_CR && ch == '\0') {
|
|
// CR NUL -> CR
|
|
} else toybuf[i++] = ch;
|
|
if (ch == '\r') TT.state = SAW_CR;
|
|
TT.state = NORMAL;
|
|
}
|
|
}
|
|
if (i) xwrite(0, toybuf, i);
|
|
}
|
|
|
|
static void handle_user_input(int n)
|
|
{
|
|
char *p = TT.buf, ch;
|
|
int i = 0;
|
|
|
|
while (n--) {
|
|
ch = *p++;
|
|
if (ch == 0x1d) {
|
|
handle_esc();
|
|
return;
|
|
}
|
|
toybuf[i++] = ch;
|
|
if (ch == IAC) toybuf[i++] = IAC; // IAC -> IAC IAC
|
|
else if (ch == '\r') toybuf[i++] = '\n'; // CR -> CR LF
|
|
else if (ch == '\n') { // LF -> CR LF
|
|
toybuf[i-1] = '\r';
|
|
toybuf[i++] = '\n';
|
|
}
|
|
}
|
|
if (i) xwrite(TT.sock, toybuf, i);
|
|
}
|
|
|
|
static void reset_terminal(void)
|
|
{
|
|
raw(0);
|
|
}
|
|
|
|
void telnet_main(void)
|
|
{
|
|
struct pollfd pfds[2];
|
|
int n = 1;
|
|
|
|
tcgetattr(0, &TT.old_term);
|
|
TT.raw_term = TT.old_term;
|
|
cfmakeraw(&TT.raw_term);
|
|
|
|
TT.sock = xconnectany(xgetaddrinfo(*toys.optargs, toys.optargs[1] ?: "23", 0,
|
|
SOCK_STREAM, IPPROTO_TCP, 0));
|
|
xsetsockopt(TT.sock, SOL_SOCKET, SO_KEEPALIVE, &n, sizeof(n));
|
|
|
|
xprintf("Connected to %s.\r\n", *toys.optargs);
|
|
|
|
sigatexit(reset_terminal);
|
|
signal(SIGINT, generic_signal);
|
|
|
|
pfds[0].fd = 0;
|
|
pfds[0].events = POLLIN;
|
|
pfds[1].fd = TT.sock;
|
|
pfds[1].events = POLLIN;
|
|
for (;;) {
|
|
if (poll(pfds, 2, -1) < 0) {
|
|
if (toys.signal) handle_esc();
|
|
else perror_exit("poll");
|
|
}
|
|
if (pfds[0].revents) {
|
|
if ((n = read(0, TT.buf, sizeof(TT.buf))) <= 0) xexit();
|
|
handle_user_input(n);
|
|
}
|
|
if (pfds[1].revents) {
|
|
if ((n = read(TT.sock, TT.buf, sizeof(TT.buf))) <= 0)
|
|
error_exit("Connection closed by foreign host\r");
|
|
handle_server_output(n);
|
|
}
|
|
}
|
|
}
|