telnet.c 6.7 KB

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