Home | History | Annotate | Download | only in pending
      1 /* telnet.c - Telnet client.
      2  *
      3  * Copyright 2012 Madhur Verma <mad.flexi (at) gmail.com>
      4  * Copyright 2013 Kyungwan Han <asura321 (at) gmail.com>
      5  * Modified by Ashwini Kumar <ak.ashwini1981 (at) 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 ";
     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.
     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.
     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.
     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 
    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  */
    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  */
    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  */
    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  */
    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 
    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(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