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 
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 // sets terminal mode: LINE or CHARACTER based om internal stat.
52 static char const es[] = "\r\nEscape character is ";
set_mode(void)53 static void set_mode(void)
54 {
55   if (TT.flags & UF_ECHO) {
56     if (TT.term_mode == CM_TRY) {
57       TT.term_mode = CM_ON;
58       printf("\r\nEntering character mode%s'^]'.\r\n", es);
59       if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
60     }
61   } else {
62     if (TT.term_mode != CM_OFF) {
63       TT.term_mode = CM_OFF;
64       printf("\r\nEntering line mode%s'^C'.\r\n", es);
65       if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
66     }
67   }
68 }
69 
70 // flushes all data in IAC buff to server.
flush_iac(void)71 static void flush_iac(void)
72 {
73   int wlen = write(TT.sfd, TT.iac, TT.piac);
74 
75   if(wlen <= 0) error_msg("IAC : send failed.");
76   TT.piac = 0;
77 }
78 
79 // puts DATA in iac buff of length LEN and updates iac buff pointer.
put_iac(int len,...)80 static void put_iac(int len, ...)
81 {
82   va_list va;
83 
84   if(TT.piac + len >= IACBUFSIZE) flush_iac();
85   va_start(va, len);
86   for(;len > 0; TT.iac[TT.piac++] = (uint8_t)va_arg(va, int), len--);
87   va_end(va);
88 }
89 
90 // puts string STR in iac buff and updates iac buff pointer.
str_iac(char * str)91 static void str_iac(char *str)
92 {
93   int len = strlen(str);
94 
95   if(TT.piac + len + 1 >= IACBUFSIZE) flush_iac();
96   strcpy(&TT.iac[TT.piac], str);
97   TT.piac += len+1;
98 }
99 
handle_esc(void)100 static void handle_esc(void)
101 {
102   char input;
103 
104   if(toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
105   xwrite(1,"\r\nConsole escape. Commands are:\r\n\n"
106       " l  go to line mode\r\n"
107       " c  go to character mode\r\n"
108       " z  suspend telnet\r\n"
109       " e  exit telnet\r\n", 114);
110 
111   if (read(STDIN_FILENO, &input, 1) <= 0) {
112     if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
113     exit(0);
114   }
115 
116   switch (input) {
117   case 'l':
118     if (!toys.signal) {
119       TT.term_mode = CM_TRY;
120       TT.flags &= ~(UF_ECHO | UF_SGA);
121       set_mode();
122       put_iac(6, IAC,DONT,TELOPT_ECHO,IAC,DONT, TELOPT_SGA);
123       flush_iac();
124       goto ret;
125     }
126     break;
127   case 'c':
128     if (toys.signal) {
129       TT.term_mode = CM_TRY;
130       TT.flags |= (UF_ECHO | UF_SGA);
131       set_mode();
132       put_iac(6, IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
133       flush_iac();
134       goto ret;
135     }
136     break;
137   case 'z':
138     if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
139     kill(0, SIGTSTP);
140     if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
141     break;
142   case 'e':
143     if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
144     exit(0);
145   default: break;
146   }
147 
148   xwrite(1, "continuing...\r\n", 15);
149   if (toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
150 
151 ret:
152   toys.signal = 0;
153 }
154 
155 /*
156  * handles telnet SUB NEGOTIATIONS
157  * only terminal type is supported.
158  */
handle_negotiations(void)159 static void handle_negotiations(void)
160 {
161   char opt = TT.buff[TT.pbuff++];
162 
163   switch(opt) {
164   case TELOPT_TTYPE:
165     opt =  TT.buff[TT.pbuff++];
166     if(opt == TELQUAL_SEND) {
167       put_iac(4, IAC,SB,TELOPT_TTYPE,TELQUAL_IS);
168       str_iac(TT.ttype);
169       put_iac(2, IAC,SE);
170     }
171     break;
172   default: break;
173   }
174 }
175 
176 /*
177  * handles server's DO DONT WILL WONT requests.
178  * supports ECHO, SGA, TTYPE, NAWS
179  */
handle_ddww(char ddww)180 static void handle_ddww(char ddww)
181 {
182   char opt = TT.buff[TT.pbuff++];
183 
184   switch (opt) {
185   case TELOPT_ECHO: /* ECHO */
186     if (ddww == DO) put_iac(3, IAC,WONT,TELOPT_ECHO);
187     if(ddww == DONT) break;
188     if (TT.flags & UF_ECHO) {
189         if (ddww == WILL) return;
190       } else if (ddww == WONT) return;
191     if (TT.term_mode != CM_OFF) TT.flags ^= UF_ECHO;
192     (TT.flags & UF_ECHO)? put_iac(3, IAC,DO,TELOPT_ECHO) :
193       put_iac(3, IAC,DONT,TELOPT_ECHO);
194     set_mode();
195     printf("\r\n");
196     break;
197 
198   case TELOPT_SGA: /* Supress GO Ahead */
199     if (TT.flags & UF_SGA){ if (ddww == WILL) return;
200     } else if (ddww == WONT) return;
201 
202     TT.flags ^= UF_SGA;
203     (TT.flags & UF_SGA)? put_iac(3, IAC,DO,TELOPT_SGA) :
204       put_iac(3, IAC,DONT,TELOPT_SGA);
205     break;
206 
207   case TELOPT_TTYPE: /* Terminal Type */
208     (TT.ttype)? put_iac(3, IAC,WILL,TELOPT_TTYPE):
209       put_iac(3, IAC,WONT,TELOPT_TTYPE);
210     break;
211 
212   case TELOPT_NAWS: /* Window Size */
213     put_iac(3, IAC,WILL,TELOPT_NAWS);
214     put_iac(9, IAC,SB,TELOPT_NAWS,(TT.win_width >> 8) & 0xff,
215         TT.win_width & 0xff,(TT.win_height >> 8) & 0xff,
216         TT.win_height & 0xff,IAC,SE);
217     break;
218 
219   default: /* Default behaviour is to say NO */
220     if(ddww == WILL) put_iac(3, IAC,DONT,opt);
221     if(ddww == DO) put_iac(3, IAC,WONT,opt);
222     break;
223   }
224 }
225 
226 /*
227  * parses data which is read from server of length LEN.
228  * and passes it to console.
229  */
read_server(int len)230 static int read_server(int len)
231 {
232   int i = 0;
233   char curr;
234   TT.pbuff = 0;
235 
236   do {
237     curr = TT.buff[TT.pbuff++];
238     if (curr == IAC) {
239       curr = TT.buff[TT.pbuff++];
240       switch (curr) {
241       case DO:    /* FALLTHROUGH */
242       case DONT:    /* FALLTHROUGH */
243       case WILL:    /* FALLTHROUGH */
244       case WONT:
245         handle_ddww(curr);
246         break;
247       case SB:
248         handle_negotiations();
249         break;
250       case SE:
251         break;
252       default: break;
253       }
254     } else {
255       toybuf[i++] = curr;
256       if (curr == '\r') { curr = TT.buff[TT.pbuff++];
257         if (curr != '\0') TT.pbuff--;
258       }
259     }
260   } while (TT.pbuff < len);
261 
262   if (i) xwrite(STDIN_FILENO, toybuf, i);
263   return 0;
264 }
265 
266 /*
267  * parses data which is read from console of length LEN
268  * and passes it to server.
269  */
write_server(int len)270 static void write_server(int len)
271 {
272   char *c = (char*)TT.buff;
273   int i = 0;
274 
275   for (; len > 0; len--, c++) {
276     if (*c == 0x1d) {
277       handle_esc();
278       return;
279     }
280     toybuf[i++] = *c;
281     if (*c == IAC) toybuf[i++] = *c; /* IAC -> IAC IAC */
282     else if (*c == '\r') toybuf[i++] = '\0'; /* CR -> CR NUL */
283   }
284   if(i) xwrite(TT.sfd, toybuf, i);
285 }
286 
telnet_main(void)287 void telnet_main(void)
288 {
289   char *port = "23";
290   int set = 1, len;
291   struct pollfd pfds[2];
292 
293   TT.win_width = 80; //columns
294   TT.win_height = 24; //rows
295 
296   if (toys.optc == 2) port = toys.optargs[1];
297 
298   TT.ttype = getenv("TERM");
299   if(!TT.ttype) TT.ttype = "";
300   if(strlen(TT.ttype) > IACBUFSIZE-1) TT.ttype[IACBUFSIZE - 1] = '\0';
301 
302   if (!tcgetattr(0, &TT.def_term)) {
303     TT.term_ok = 1;
304     TT.raw_term = TT.def_term;
305     cfmakeraw(&TT.raw_term);
306   }
307   terminal_size(&TT.win_width, &TT.win_height);
308 
309   TT.sfd = xconnect(*toys.optargs, port, 0, SOCK_STREAM, IPPROTO_TCP, 0);
310   setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
311   setsockopt(TT.sfd, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set));
312 
313   pfds[0].fd = STDIN_FILENO;
314   pfds[0].events = POLLIN;
315   pfds[1].fd = TT.sfd;
316   pfds[1].events = POLLIN;
317 
318   signal(SIGINT, generic_signal);
319   while(1) {
320     if(TT.piac) flush_iac();
321     if(poll(pfds, 2, -1) < 0) {
322       if (toys.signal) handle_esc();
323       else sleep(1);
324 
325       continue;
326     }
327     if(pfds[0].revents) {
328       len = read(STDIN_FILENO, TT.buff, DATABUFSIZE);
329       if(len > 0) write_server(len);
330       else return;
331     }
332     if(pfds[1].revents) {
333       len = read(TT.sfd, TT.buff, DATABUFSIZE);
334       if(len > 0) read_server(len);
335       else {
336         printf("Connection closed by foreign host\r\n");
337         if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
338         exit(1);
339       }
340     }
341   }
342 }
343