1 /* telnet.c - Telnet client.
2  *
3  * Copyright 2012 Madhur Verma <mad.flexi@gmail.com>
4  * Copyright 2013 Kyungwan Han <asura321@gmail.com>
5  * Modified by Ashwini Kumar <ak.ashwini1981@gmail.com>
6  *
7  * Not in SUSv4.
8 
9 USE_TELNET(NEWTOY(telnet, "<1>2", TOYFLAG_BIN))
10 
11 config TELNET
12   bool "telnet"
13   default n
14   help
15     usage: telnet HOST [PORT]
16 
17     Connect to telnet server
18 */
19 
20 #define FOR_telnet
21 #include "toys.h"
22 #include <arpa/telnet.h>
23 #include <netinet/in.h>
24 #include  <sys/poll.h>
25 
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;)26 GLOBALS(
27   int port;
28   int sfd;
29   char buff[128];
30   int pbuff;
31   char iac[256];
32   int piac;
33   char *ttype;
34   struct termios def_term;
35   struct termios raw_term;
36   uint8_t term_ok;
37   uint8_t term_mode;
38   uint8_t flags;
39   unsigned win_width;
40   unsigned win_height;
41 )
42 
43 #define DATABUFSIZE 128
44 #define IACBUFSIZE  256
45 #define CM_TRY      0
46 #define CM_ON       1
47 #define CM_OFF      2
48 #define UF_ECHO     0x01
49 #define UF_SGA      0x02
50 
51 /*
52  * creates a socket of family INET/INET6 and protocol TCP and connects
53  * it to HOST at PORT.
54  * if successful then returns SOCK othrwise error
55  */
56 static int xconnect_inet_tcp(char *host, int port)
57 {
58   int ret;
59   struct addrinfo *info, *rp;
60   char buf[32];
61 
62   rp = xzalloc(sizeof(struct addrinfo));
63   rp->ai_family = AF_UNSPEC;
64   rp->ai_socktype = SOCK_STREAM;
65   rp->ai_protocol = IPPROTO_TCP;
66   sprintf(buf, "%d", port);
67 
68   ret = getaddrinfo(host, buf, rp, &info);
69   if(ret || !info) perror_exit("BAD ADDRESS: can't find : %s ", host);
70   free(rp);
71 
72   for (rp = info; rp; rp = rp->ai_next)
73     if ( (rp->ai_family == AF_INET) || (rp->ai_family == AF_INET6)) break;
74 
75   if (!rp) error_exit("Invalid IP %s", host);
76 
77   ret = xsocket(rp->ai_family, SOCK_STREAM, IPPROTO_TCP);
78   if(connect(ret, rp->ai_addr, rp->ai_addrlen) == -1) perror_exit("connect");
79 
80   freeaddrinfo(info);
81   return ret;
82 }
83 
84 // sets terminal mode: LINE or CHARACTER based om internal stat.
85 static char const es[] = "\r\nEscape character is ";
set_mode(void)86 static void set_mode(void)
87 {
88   if (TT.flags & UF_ECHO) {
89     if (TT.term_mode == CM_TRY) {
90       TT.term_mode = CM_ON;
91       printf("\r\nEntering character mode%s'^]'.\r\n", es);
92       if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
93     }
94   } else {
95     if (TT.term_mode != CM_OFF) {
96       TT.term_mode = CM_OFF;
97       printf("\r\nEntering line mode%s'^C'.\r\n", es);
98       if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
99     }
100   }
101 }
102 
103 // flushes all data in IAC buff to server.
flush_iac(void)104 static void flush_iac(void)
105 {
106   int wlen = write(TT.sfd, TT.iac, TT.piac);
107 
108   if(wlen <= 0) error_msg("IAC : send failed.");
109   TT.piac = 0;
110 }
111 
112 // puts DATA in iac buff of length LEN and updates iac buff pointer.
put_iac(int len,...)113 static void put_iac(int len, ...)
114 {
115   va_list va;
116 
117   if(TT.piac + len >= IACBUFSIZE) flush_iac();
118   va_start(va, len);
119   for(;len > 0; TT.iac[TT.piac++] = (uint8_t)va_arg(va, int), len--);
120   va_end(va);
121 }
122 
123 // puts string STR in iac buff and updates iac buff pointer.
str_iac(char * str)124 static void str_iac(char *str)
125 {
126   int len = strlen(str);
127 
128   if(TT.piac + len + 1 >= IACBUFSIZE) flush_iac();
129   strcpy(&TT.iac[TT.piac], str);
130   TT.piac += len+1;
131 }
132 
handle_esc(void)133 static void handle_esc(void)
134 {
135   char input;
136 
137   if(toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
138   write(1,"\r\nConsole escape. Commands are:\r\n\n"
139       " l  go to line mode\r\n"
140       " c  go to character mode\r\n"
141       " z  suspend telnet\r\n"
142       " e  exit telnet\r\n", 114);
143 
144   if (read(STDIN_FILENO, &input, 1) <= 0) {
145     if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
146     exit(0);
147   }
148 
149   switch (input) {
150   case 'l':
151     if (!toys.signal) {
152       TT.term_mode = CM_TRY;
153       TT.flags &= ~(UF_ECHO | UF_SGA);
154       set_mode();
155       put_iac(6, IAC,DONT,TELOPT_ECHO,IAC,DONT, TELOPT_SGA);
156       flush_iac();
157       goto ret;
158     }
159     break;
160   case 'c':
161     if (toys.signal) {
162       TT.term_mode = CM_TRY;
163       TT.flags |= (UF_ECHO | UF_SGA);
164       set_mode();
165       put_iac(6, IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
166       flush_iac();
167       goto ret;
168     }
169     break;
170   case 'z':
171     if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
172     kill(0, SIGTSTP);
173     if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
174     break;
175   case 'e':
176     if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
177     exit(0);
178   default: break;
179   }
180 
181   write(1, "continuing...\r\n", 15);
182   if (toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
183 
184 ret:
185   toys.signal = 0;
186 }
187 
188 /*
189  * handles telnet SUB NEGOTIATIONS
190  * only terminal type is supported.
191  */
handle_negotiations(void)192 static void handle_negotiations(void)
193 {
194   char opt = TT.buff[TT.pbuff++];
195 
196   switch(opt) {
197   case TELOPT_TTYPE:
198     opt =  TT.buff[TT.pbuff++];
199     if(opt == TELQUAL_SEND) {
200       put_iac(4, IAC,SB,TELOPT_TTYPE,TELQUAL_IS);
201       str_iac(TT.ttype);
202       put_iac(2, IAC,SE);
203     }
204     break;
205   default: break;
206   }
207 }
208 
209 /*
210  * handles server's DO DONT WILL WONT requests.
211  * supports ECHO, SGA, TTYPE, NAWS
212  */
handle_ddww(char ddww)213 static void handle_ddww(char ddww)
214 {
215   char opt = TT.buff[TT.pbuff++];
216 
217   switch (opt) {
218   case TELOPT_ECHO: /* ECHO */
219     if (ddww == DO) put_iac(3, IAC,WONT,TELOPT_ECHO);
220     if(ddww == DONT) break;
221     if (TT.flags & UF_ECHO) {
222         if (ddww == WILL) return;
223       } else if (ddww == WONT) return;
224     if (TT.term_mode != CM_OFF) TT.flags ^= UF_ECHO;
225     (TT.flags & UF_ECHO)? put_iac(3, IAC,DO,TELOPT_ECHO) :
226       put_iac(3, IAC,DONT,TELOPT_ECHO);
227     set_mode();
228     printf("\r\n");
229     break;
230 
231   case TELOPT_SGA: /* Supress GO Ahead */
232     if (TT.flags & UF_SGA){ if (ddww == WILL) return;
233     } else if (ddww == WONT) return;
234 
235     TT.flags ^= UF_SGA;
236     (TT.flags & UF_SGA)? put_iac(3, IAC,DO,TELOPT_SGA) :
237       put_iac(3, IAC,DONT,TELOPT_SGA);
238     break;
239 
240   case TELOPT_TTYPE: /* Terminal Type */
241     (TT.ttype)? put_iac(3, IAC,WILL,TELOPT_TTYPE):
242       put_iac(3, IAC,WONT,TELOPT_TTYPE);
243     break;
244 
245   case TELOPT_NAWS: /* Window Size */
246     put_iac(3, IAC,WILL,TELOPT_NAWS);
247     put_iac(9, IAC,SB,TELOPT_NAWS,(TT.win_width >> 8) & 0xff,
248         TT.win_width & 0xff,(TT.win_height >> 8) & 0xff,
249         TT.win_height & 0xff,IAC,SE);
250     break;
251 
252   default: /* Default behaviour is to say NO */
253     if(ddww == WILL) put_iac(3, IAC,DONT,opt);
254     if(ddww == DO) put_iac(3, IAC,WONT,opt);
255     break;
256   }
257 }
258 
259 /*
260  * parses data which is read from server of length LEN.
261  * and passes it to console.
262  */
read_server(int len)263 static int read_server(int len)
264 {
265   int i = 0;
266   char curr;
267   TT.pbuff = 0;
268 
269   do {
270     curr = TT.buff[TT.pbuff++];
271     if (curr == IAC) {
272       curr = TT.buff[TT.pbuff++];
273       switch (curr) {
274       case DO:    /* FALLTHROUGH */
275       case DONT:    /* FALLTHROUGH */
276       case WILL:    /* FALLTHROUGH */
277       case WONT:
278         handle_ddww(curr);
279         break;
280       case SB:
281         handle_negotiations();
282         break;
283       case SE:
284         break;
285       default: break;
286       }
287     } else {
288       toybuf[i++] = curr;
289       if (curr == '\r') { curr = TT.buff[TT.pbuff++];
290         if (curr != '\0') TT.pbuff--;
291       }
292     }
293   } while (TT.pbuff < len);
294 
295   if (i) write(STDIN_FILENO, toybuf, i);
296   return 0;
297 }
298 
299 /*
300  * parses data which is read from console of length LEN
301  * and passes it to server.
302  */
write_server(int len)303 static void write_server(int len)
304 {
305   char *c = (char*)TT.buff;
306   int i = 0;
307 
308   for (; len > 0; len--, c++) {
309     if (*c == 0x1d) {
310       handle_esc();
311       return;
312     }
313     toybuf[i++] = *c;
314     if (*c == IAC) toybuf[i++] = *c; /* IAC -> IAC IAC */
315     else if (*c == '\r') toybuf[i++] = '\0'; /* CR -> CR NUL */
316   }
317   if(i) write(TT.sfd, toybuf, i);
318 }
319 
telnet_main(void)320 void telnet_main(void)
321 {
322   int set = 1, len;
323   struct pollfd pfds[2];
324 
325   TT.port = 23; //TELNET_PORT
326   TT.win_width = 80; //columns
327   TT.win_height = 24; //rows
328 
329   if(toys.optc == 2) TT.port = atoi(toys.optargs[1]);
330   if(TT.port <= 0 || TT.port > 65535) error_exit("bad PORT (1-65535)");
331 
332   TT.ttype = getenv("TERM");
333   if(!TT.ttype) TT.ttype = "";
334   if(strlen(TT.ttype) > IACBUFSIZE-1) TT.ttype[IACBUFSIZE - 1] = '\0';
335 
336   if (!tcgetattr(0, &TT.def_term)) {
337     TT.term_ok = 1;
338     TT.raw_term = TT.def_term;
339     cfmakeraw(&TT.raw_term);
340   }
341   terminal_size(&TT.win_width, &TT.win_height);
342 
343   TT.sfd = xconnect_inet_tcp(toys.optargs[0], TT.port);
344   setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
345   setsockopt(TT.sfd, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set));
346 
347   pfds[0].fd = STDIN_FILENO;
348   pfds[0].events = POLLIN;
349   pfds[1].fd = TT.sfd;
350   pfds[1].events = POLLIN;
351 
352   signal(SIGINT, generic_signal);
353   while(1) {
354     if(TT.piac) flush_iac();
355     if(poll(pfds, 2, -1) < 0) {
356       if (toys.signal) handle_esc();
357       else sleep(1);
358 
359       continue;
360     }
361     if(pfds[0].revents) {
362       len = read(STDIN_FILENO, TT.buff, DATABUFSIZE);
363       if(len > 0) write_server(len);
364       else return;
365     }
366     if(pfds[1].revents) {
367       len = read(TT.sfd, TT.buff, DATABUFSIZE);
368       if(len > 0) read_server(len);
369       else {
370         printf("Connection closed by foreign host\r\n");
371         if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
372         exit(1);
373       }
374     }
375   }
376 }
377