toybox/toys/pending/telnet.c
<<
>>
Prefs
   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
  24GLOBALS(
  25  int sock;
  26  char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
  27  struct termios old_term;
  28  struct termios raw_term;
  29  uint8_t mode;
  30  int echo, sga;
  31  int state, request;
  32)
  33
  34#define NORMAL 0
  35#define SAW_IAC 1
  36#define SAW_WWDD 2
  37#define SAW_SB 3
  38#define SAW_SB_TTYPE 4
  39#define WANT_IAC 5
  40#define WANT_SE 6
  41#define SAW_CR 10
  42
  43#define CM_TRY      0
  44#define CM_ON       1
  45#define CM_OFF      2
  46
  47static void raw(int raw)
  48{
  49  tcsetattr(0, TCSADRAIN, raw ? &TT.raw_term : &TT.old_term);
  50}
  51
  52static void slc(int line)
  53{
  54  TT.mode = line ? CM_OFF : CM_ON;
  55  xprintf("Entering %s mode\r\nEscape character is '^%c'.\r\n",
  56      line ? "line" : "character", line ? 'C' : ']');
  57  raw(!line);
  58}
  59
  60static void set_mode(void)
  61{
  62  if (TT.echo) {
  63    if (TT.mode == CM_TRY) slc(0);
  64  } else if (TT.mode != CM_OFF) slc(1);
  65}
  66
  67static void handle_esc(void)
  68{
  69  char input;
  70
  71  if (toys.signal) raw(1);
  72
  73  // This matches busybox telnet, not BSD telnet.
  74  xputsn("\r\n"
  75      "Console escape. Commands are:\r\n"
  76      "\r\n"
  77      " l  go to line mode\r\n"
  78      " c  go to character mode\r\n"
  79      " z  suspend telnet\r\n"
  80      " e  exit telnet\r\n"
  81      "\r\n"
  82      "telnet> ");
  83  // In particular, the boxes only read a single character, not a line.
  84  if (read(0, &input, 1) <= 0 || input == 4 || input == 'e') {
  85    xprintf("Connection closed.\r\n");
  86    xexit();
  87  }
  88
  89  if (input == 'l') {
  90    if (!toys.signal) {
  91      TT.mode = CM_TRY;
  92      TT.echo = TT.sga = 0;
  93      set_mode();
  94      dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DONT,TELOPT_ECHO,IAC,DONT,TELOPT_SGA);
  95      goto ret;
  96    }
  97  } else if (input == 'c') {
  98    if (toys.signal) {
  99      TT.mode = CM_TRY;
 100      TT.echo = TT.sga = 1;
 101      set_mode();
 102      dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
 103      goto ret;
 104    }
 105  } else if (input == 'z') {
 106    raw(0);
 107    kill(0, SIGTSTP);
 108    raw(1);
 109  }
 110
 111  xprintf("telnet %s %s\r\n", toys.optargs[0], toys.optargs[1] ?: "23");
 112  if (toys.signal) raw(0);
 113
 114ret:
 115  toys.signal = 0;
 116}
 117
 118// Handle WILL WONT DO DONT requests from the server.
 119static void handle_wwdd(char opt)
 120{
 121  if (opt == TELOPT_ECHO) {
 122    if (TT.request == DO) dprintf(TT.sock, "%c%c%c", IAC, WONT, TELOPT_ECHO);
 123    if (TT.request == DONT) return;
 124    if (TT.echo) {
 125        if (TT.request == WILL) return;
 126    } else if (TT.request == WONT) return;
 127    if (TT.mode != CM_OFF) TT.echo ^= 1;
 128    dprintf(TT.sock, "%c%c%c", IAC, TT.echo ? DO : DONT, TELOPT_ECHO);
 129    set_mode();
 130  } else if (opt == TELOPT_SGA) { // Suppress Go Ahead
 131    if (TT.sga) {
 132      if (TT.request == WILL) return;
 133    } else if (TT.request == WONT) return;
 134    TT.sga ^= 1;
 135    dprintf(TT.sock, "%c%c%c", IAC, TT.sga ? DO : DONT, TELOPT_SGA);
 136  } else if (opt == TELOPT_TTYPE) { // Terminal TYPE
 137    dprintf(TT.sock, "%c%c%c", IAC, WILL, TELOPT_TTYPE);
 138  } else if (opt == TELOPT_NAWS) { // Negotiate About Window Size
 139    unsigned cols = 80, rows = 24;
 140
 141    terminal_size(&cols, &rows);
 142    dprintf(TT.sock, "%c%c%c%c%c%c%c%c%c%c%c%c", IAC, WILL, TELOPT_NAWS,
 143            IAC, SB, TELOPT_NAWS, cols>>8, cols, rows>>8, rows,
 144            IAC, SE);
 145  } else {
 146    // Say "no" to anything we don't understand.
 147    dprintf(TT.sock, "%c%c%c", IAC, (TT.request == WILL) ? DONT : WONT, opt);
 148  }
 149}
 150
 151static void handle_server_output(int n)
 152{
 153  char *p = TT.buf, *end = TT.buf + n, ch;
 154  int i = 0;
 155
 156  // Possibilities:
 157  //
 158  // 1. Regular character
 159  // 2. IAC [WILL|WONT|DO|DONT] option
 160  // 3. IAC SB option arg... IAC SE
 161  //
 162  // The only subnegotiation we support is IAC SB TTYPE SEND IAC SE, so we just
 163  // hard-code that into our state machine rather than having a more general
 164  // "collect the subnegotation into a buffer and handle it after we've seen
 165  // the IAC SE at the end". It's 2021, so we're unlikely to need more.
 166
 167  while (p < end) {
 168    ch = *p++;
 169    if (TT.state == SAW_IAC) {
 170      if (ch >= WILL && ch <= DONT) {
 171        TT.state = SAW_WWDD;
 172        TT.request = ch;
 173      } else if (ch == SB) {
 174        TT.state = SAW_SB;
 175      } else {
 176        TT.state = NORMAL;
 177      }
 178    } else if (TT.state == SAW_WWDD) {
 179      handle_wwdd(ch);
 180      TT.state = NORMAL;
 181    } else if (TT.state == SAW_SB) {
 182      if (ch == TELOPT_TTYPE) TT.state = SAW_SB_TTYPE;
 183      else TT.state = WANT_IAC;
 184    } else if (TT.state == SAW_SB_TTYPE) {
 185      if (ch == TELQUAL_SEND) {
 186        dprintf(TT.sock, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, TELQUAL_IS,
 187                getenv("TERM") ?: "NVT", IAC, SE);
 188      }
 189      TT.state = WANT_IAC;
 190    } else if (TT.state == WANT_IAC) {
 191      if (ch == IAC) TT.state = WANT_SE;
 192    } else if (TT.state == WANT_SE) {
 193      if (ch == SE) TT.state = NORMAL;
 194    } else if (ch == IAC) {
 195      TT.state = SAW_IAC;
 196    } else {
 197      if (TT.state == SAW_CR && ch == '\0') {
 198        // CR NUL -> CR
 199      } else toybuf[i++] = ch;
 200      if (ch == '\r') TT.state = SAW_CR;
 201      TT.state = NORMAL;
 202    }
 203  }
 204  if (i) xwrite(0, toybuf, i);
 205}
 206
 207static void handle_user_input(int n)
 208{
 209  char *p = TT.buf, ch;
 210  int i = 0;
 211
 212  while (n--) {
 213    ch = *p++;
 214    if (ch == 0x1d) {
 215      handle_esc();
 216      return;
 217    }
 218    toybuf[i++] = ch;
 219    if (ch == IAC) toybuf[i++] = IAC; // IAC -> IAC IAC
 220    else if (ch == '\r') toybuf[i++] = '\n'; // CR -> CR LF
 221    else if (ch == '\n') { // LF -> CR LF
 222      toybuf[i-1] = '\r';
 223      toybuf[i++] = '\n';
 224    }
 225  }
 226  if (i) xwrite(TT.sock, toybuf, i);
 227}
 228
 229static void reset_terminal(void)
 230{
 231  raw(0);
 232}
 233
 234void telnet_main(void)
 235{
 236  struct pollfd pfds[2];
 237  int n = 1;
 238
 239  tcgetattr(0, &TT.old_term);
 240  TT.raw_term = TT.old_term;
 241  cfmakeraw(&TT.raw_term);
 242
 243  TT.sock = xconnectany(xgetaddrinfo(*toys.optargs, toys.optargs[1] ?: "23", 0,
 244      SOCK_STREAM, IPPROTO_TCP, 0));
 245  xsetsockopt(TT.sock, SOL_SOCKET, SO_KEEPALIVE, &n, sizeof(n));
 246
 247  xprintf("Connected to %s.\r\n", *toys.optargs);
 248
 249  sigatexit(reset_terminal);
 250  signal(SIGINT, generic_signal);
 251
 252  pfds[0].fd = 0;
 253  pfds[0].events = POLLIN;
 254  pfds[1].fd = TT.sock;
 255  pfds[1].events = POLLIN;
 256  for (;;) {
 257    if (poll(pfds, 2, -1) < 0) {
 258      if (toys.signal) handle_esc();
 259      else perror_exit("poll");
 260    }
 261    if (pfds[0].revents) {
 262      if ((n = read(0, TT.buf, sizeof(TT.buf))) <= 0) xexit();
 263      handle_user_input(n);
 264    }
 265    if (pfds[1].revents) {
 266      if ((n = read(TT.sock, TT.buf, sizeof(TT.buf))) <= 0)
 267        error_exit("Connection closed by foreign host\r");
 268      handle_server_output(n);
 269    }
 270  }
 271}
 272