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
9USE_TELNET(NEWTOY(telnet, "<1>2", TOYFLAG_BIN))
10
11config 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
26GLOBALS(
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.
52static char const es[] = "\r\nEscape character is ";
53static 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.
71static 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.
80static 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.
91static 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
100static 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
151ret:
152  toys.signal = 0;
153}
154
155/*
156 * handles telnet SUB NEGOTIATIONS
157 * only terminal type is supported.
158 */
159static 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 */
180static 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 */
230static 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 */
270static 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
287void 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(xgetaddrinfo(*toys.optargs, port, 0, SOCK_STREAM,
310    IPPROTO_TCP, 0));
311  setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
312  setsockopt(TT.sfd, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set));
313
314  pfds[0].fd = STDIN_FILENO;
315  pfds[0].events = POLLIN;
316  pfds[1].fd = TT.sfd;
317  pfds[1].events = POLLIN;
318
319  signal(SIGINT, generic_signal);
320  while(1) {
321    if(TT.piac) flush_iac();
322    if(poll(pfds, 2, -1) < 0) {
323      if (toys.signal) handle_esc();
324      else sleep(1);
325
326      continue;
327    }
328    if(pfds[0].revents) {
329      len = read(STDIN_FILENO, TT.buff, DATABUFSIZE);
330      if(len > 0) write_server(len);
331      else return;
332    }
333    if(pfds[1].revents) {
334      len = read(TT.sfd, TT.buff, DATABUFSIZE);
335      if(len > 0) read_server(len);
336      else {
337        printf("Connection closed by foreign host\r\n");
338        if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
339        exit(1);
340      }
341    }
342  }
343}
344