aboutsummaryrefslogtreecommitdiff
path: root/toys/pending/telnet.c
diff options
context:
space:
mode:
Diffstat (limited to 'toys/pending/telnet.c')
-rw-r--r--toys/pending/telnet.c386
1 files changed, 386 insertions, 0 deletions
diff --git a/toys/pending/telnet.c b/toys/pending/telnet.c
new file mode 100644
index 00000000..e0f9f6f0
--- /dev/null
+++ b/toys/pending/telnet.c
@@ -0,0 +1,386 @@
+/* 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;
+ unsigned signalno;
+)
+
+#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
+
+/*
+ * creates a socket of family INET/INET6 and protocol TCP and connects
+ * it to HOST at PORT.
+ * if successful then returns SOCK othrwise error
+ */
+static int xconnect_inet_tcp(char *host, int port)
+{
+ int ret;
+ struct addrinfo *info, *rp;
+ char buf[32];
+
+ rp = xzalloc(sizeof(struct addrinfo));
+ rp->ai_family = AF_UNSPEC;
+ rp->ai_socktype = SOCK_STREAM;
+ rp->ai_protocol = IPPROTO_TCP;
+ sprintf(buf, "%d", port);
+
+ ret = getaddrinfo(host, buf, rp, &info);
+ if(ret || !info) perror_exit("BAD ADDRESS: can't find : %s ", host);
+ free(rp);
+
+ for (rp = info; rp; rp = rp->ai_next)
+ if ( (rp->ai_family == AF_INET) || (rp->ai_family == AF_INET6)) break;
+
+ if (!rp) error_exit("Invalid IP %s", host);
+
+ ret = xsocket(rp->ai_family, SOCK_STREAM, IPPROTO_TCP);
+ if(connect(ret, rp->ai_addr, rp->ai_addrlen) == -1) perror_exit("connect");
+
+ freeaddrinfo(info);
+ return ret;
+}
+
+// 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(TT.signalno && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
+ write(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 (!TT.signalno) {
+ 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 (TT.signalno) {
+ 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;
+ }
+
+ write(1, "continuing...\r\n", 15);
+ if (TT.signalno && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
+
+ret:
+ TT.signalno = 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) write(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) write(TT.sfd, toybuf, i);
+}
+
+/*
+ * SIGINT signal handling.
+ * only sets signalno which get handle in main loop.
+ */
+static void handle_sigint(int signo)
+{
+ TT.signalno = signo;
+}
+
+void telnet_main(void)
+{
+ int set = 1, len;
+ struct pollfd pfds[2];
+
+ TT.port = 23; //TELNET_PORT
+ TT.win_width = 80; //columns
+ TT.win_height = 24; //rows
+
+ if(toys.optc == 2) TT.port = atoi(toys.optargs[1]);
+ if(TT.port <= 0 || TT.port > 65535)
+ error_exit("PORT can be non-zero upto 65535.");
+
+ 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_inet_tcp(toys.optargs[0], TT.port);
+ 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;
+
+ TT.piac = TT.pbuff = 0;
+ TT.signalno = 0;
+ signal(SIGINT, handle_sigint);
+ while(1) {
+ if(TT.piac) flush_iac();
+ if(poll(pfds, 2, -1) < 0) {
+ (TT.signalno)?handle_esc():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);
+ }
+ }
+ }
+}