blob: f520fe1dda327f19b95da08fa6e7411fefd816ce
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * telnet implementation for busybox |
4 | * |
5 | * Author: Tomi Ollila <too@iki.fi> |
6 | * Copyright (C) 1994-2000 by Tomi Ollila |
7 | * |
8 | * Created: Thu Apr 7 13:29:41 1994 too |
9 | * Last modified: Fri Jun 9 14:34:24 2000 too |
10 | * |
11 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
12 | * |
13 | * HISTORY |
14 | * Revision 3.1 1994/04/17 11:31:54 too |
15 | * initial revision |
16 | * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen <andersen@codepoet.org> |
17 | * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan |
18 | * <jam@ltsp.org> |
19 | * Modified 2004/02/11 to add ability to pass the USER variable to remote host |
20 | * by Fernando Silveira <swrh@gmx.net> |
21 | * |
22 | */ |
23 | //config:config TELNET |
24 | //config: bool "telnet" |
25 | //config: default y |
26 | //config: help |
27 | //config: Telnet is an interface to the TELNET protocol, but is also commonly |
28 | //config: used to test other simple protocols. |
29 | //config: |
30 | //config:config FEATURE_TELNET_TTYPE |
31 | //config: bool "Pass TERM type to remote host" |
32 | //config: default y |
33 | //config: depends on TELNET |
34 | //config: help |
35 | //config: Setting this option will forward the TERM environment variable to the |
36 | //config: remote host you are connecting to. This is useful to make sure that |
37 | //config: things like ANSI colors and other control sequences behave. |
38 | //config: |
39 | //config:config FEATURE_TELNET_AUTOLOGIN |
40 | //config: bool "Pass USER type to remote host" |
41 | //config: default y |
42 | //config: depends on TELNET |
43 | //config: help |
44 | //config: Setting this option will forward the USER environment variable to the |
45 | //config: remote host you are connecting to. This is useful when you need to |
46 | //config: log into a machine without telling the username (autologin). This |
47 | //config: option enables `-a' and `-l USER' arguments. |
48 | |
49 | //applet:IF_TELNET(APPLET(telnet, BB_DIR_USR_BIN, BB_SUID_DROP)) |
50 | |
51 | //kbuild:lib-$(CONFIG_TELNET) += telnet.o |
52 | |
53 | //usage:#if ENABLE_FEATURE_TELNET_AUTOLOGIN |
54 | //usage:#define telnet_trivial_usage |
55 | //usage: "[-a] [-l USER] HOST [PORT]" |
56 | //usage:#define telnet_full_usage "\n\n" |
57 | //usage: "Connect to telnet server\n" |
58 | //usage: "\n -a Automatic login with $USER variable" |
59 | //usage: "\n -l USER Automatic login as USER" |
60 | //usage: |
61 | //usage:#else |
62 | //usage:#define telnet_trivial_usage |
63 | //usage: "HOST [PORT]" |
64 | //usage:#define telnet_full_usage "\n\n" |
65 | //usage: "Connect to telnet server" |
66 | //usage:#endif |
67 | |
68 | #include <arpa/telnet.h> |
69 | #include <netinet/in.h> |
70 | #include "libbb.h" |
71 | #include "common_bufsiz.h" |
72 | |
73 | #ifdef __BIONIC__ |
74 | /* should be in arpa/telnet.h */ |
75 | # define IAC 255 /* interpret as command: */ |
76 | # define DONT 254 /* you are not to use option */ |
77 | # define DO 253 /* please, you use option */ |
78 | # define WONT 252 /* I won't use option */ |
79 | # define WILL 251 /* I will use option */ |
80 | # define SB 250 /* interpret as subnegotiation */ |
81 | # define SE 240 /* end sub negotiation */ |
82 | # define TELOPT_ECHO 1 /* echo */ |
83 | # define TELOPT_SGA 3 /* suppress go ahead */ |
84 | # define TELOPT_TTYPE 24 /* terminal type */ |
85 | # define TELOPT_NAWS 31 /* window size */ |
86 | #endif |
87 | |
88 | #ifdef DOTRACE |
89 | # define TRACE(x, y) do { if (x) printf y; } while (0) |
90 | #else |
91 | # define TRACE(x, y) |
92 | #endif |
93 | |
94 | enum { |
95 | DATABUFSIZE = 128, |
96 | IACBUFSIZE = 128, |
97 | |
98 | CHM_TRY = 0, |
99 | CHM_ON = 1, |
100 | CHM_OFF = 2, |
101 | |
102 | UF_ECHO = 0x01, |
103 | UF_SGA = 0x02, |
104 | |
105 | TS_NORMAL = 0, |
106 | TS_COPY = 1, |
107 | TS_IAC = 2, |
108 | TS_OPT = 3, |
109 | TS_SUB1 = 4, |
110 | TS_SUB2 = 5, |
111 | TS_CR = 6, |
112 | }; |
113 | |
114 | typedef unsigned char byte; |
115 | |
116 | enum { netfd = 3 }; |
117 | |
118 | struct globals { |
119 | int iaclen; /* could even use byte, but it's a loss on x86 */ |
120 | byte telstate; /* telnet negotiation state from network input */ |
121 | byte telwish; /* DO, DONT, WILL, WONT */ |
122 | byte charmode; |
123 | byte telflags; |
124 | byte do_termios; |
125 | #if ENABLE_FEATURE_TELNET_TTYPE |
126 | char *ttype; |
127 | #endif |
128 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
129 | const char *autologin; |
130 | #endif |
131 | #if ENABLE_FEATURE_AUTOWIDTH |
132 | unsigned win_width, win_height; |
133 | #endif |
134 | /* same buffer used both for network and console read/write */ |
135 | char buf[DATABUFSIZE]; |
136 | /* buffer to handle telnet negotiations */ |
137 | char iacbuf[IACBUFSIZE]; |
138 | struct termios termios_def; |
139 | struct termios termios_raw; |
140 | } FIX_ALIASING; |
141 | #define G (*(struct globals*)bb_common_bufsiz1) |
142 | #define INIT_G() do { \ |
143 | setup_common_bufsiz(); \ |
144 | BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \ |
145 | } while (0) |
146 | |
147 | |
148 | static void rawmode(void); |
149 | static void cookmode(void); |
150 | static void do_linemode(void); |
151 | static void will_charmode(void); |
152 | static void telopt(byte c); |
153 | static void subneg(byte c); |
154 | |
155 | static void iac_flush(void) |
156 | { |
157 | full_write(netfd, G.iacbuf, G.iaclen); |
158 | G.iaclen = 0; |
159 | } |
160 | |
161 | static void doexit(int ev) NORETURN; |
162 | static void doexit(int ev) |
163 | { |
164 | cookmode(); |
165 | exit(ev); |
166 | } |
167 | |
168 | static void con_escape(void) |
169 | { |
170 | char b; |
171 | |
172 | if (bb_got_signal) /* came from line mode... go raw */ |
173 | rawmode(); |
174 | |
175 | full_write1_str("\r\nConsole escape. Commands are:\r\n\n" |
176 | " l go to line mode\r\n" |
177 | " c go to character mode\r\n" |
178 | " z suspend telnet\r\n" |
179 | " e exit telnet\r\n"); |
180 | |
181 | if (read(STDIN_FILENO, &b, 1) <= 0) |
182 | doexit(EXIT_FAILURE); |
183 | |
184 | switch (b) { |
185 | case 'l': |
186 | if (!bb_got_signal) { |
187 | do_linemode(); |
188 | goto ret; |
189 | } |
190 | break; |
191 | case 'c': |
192 | if (bb_got_signal) { |
193 | will_charmode(); |
194 | goto ret; |
195 | } |
196 | break; |
197 | case 'z': |
198 | cookmode(); |
199 | kill(0, SIGTSTP); |
200 | rawmode(); |
201 | break; |
202 | case 'e': |
203 | doexit(EXIT_SUCCESS); |
204 | } |
205 | |
206 | full_write1_str("continuing...\r\n"); |
207 | |
208 | if (bb_got_signal) |
209 | cookmode(); |
210 | ret: |
211 | bb_got_signal = 0; |
212 | } |
213 | |
214 | static void handle_net_output(int len) |
215 | { |
216 | byte outbuf[2 * DATABUFSIZE]; |
217 | byte *dst = outbuf; |
218 | byte *src = (byte*)G.buf; |
219 | byte *end = src + len; |
220 | |
221 | while (src < end) { |
222 | byte c = *src++; |
223 | if (c == 0x1d) { |
224 | con_escape(); |
225 | return; |
226 | } |
227 | *dst = c; |
228 | if (c == IAC) |
229 | *++dst = c; /* IAC -> IAC IAC */ |
230 | else |
231 | if (c == '\r' || c == '\n') { |
232 | /* Enter key sends '\r' in raw mode and '\n' in cooked one. |
233 | * |
234 | * See RFC 1123 3.3.1 Telnet End-of-Line Convention. |
235 | * Using CR LF instead of other allowed possibilities |
236 | * like CR NUL - easier to talk to HTTP/SMTP servers. |
237 | */ |
238 | *dst = '\r'; /* Enter -> CR LF */ |
239 | *++dst = '\n'; |
240 | } |
241 | dst++; |
242 | } |
243 | if (dst - outbuf != 0) |
244 | full_write(netfd, outbuf, dst - outbuf); |
245 | } |
246 | |
247 | static void handle_net_input(int len) |
248 | { |
249 | int i; |
250 | int cstart = 0; |
251 | |
252 | for (i = 0; i < len; i++) { |
253 | byte c = G.buf[i]; |
254 | |
255 | if (G.telstate == TS_NORMAL) { /* most typical state */ |
256 | if (c == IAC) { |
257 | cstart = i; |
258 | G.telstate = TS_IAC; |
259 | } |
260 | else if (c == '\r') { |
261 | cstart = i + 1; |
262 | G.telstate = TS_CR; |
263 | } |
264 | /* No IACs were seen so far, no need to copy |
265 | * bytes within G.buf: */ |
266 | continue; |
267 | } |
268 | |
269 | switch (G.telstate) { |
270 | case TS_CR: |
271 | /* Prev char was CR. If cur one is NUL, ignore it. |
272 | * See RFC 1123 section 3.3.1 for discussion of telnet EOL handling. |
273 | */ |
274 | G.telstate = TS_COPY; |
275 | if (c == '\0') |
276 | break; |
277 | /* else: fall through - need to handle CR IAC ... properly */ |
278 | |
279 | case TS_COPY: /* Prev char was ordinary */ |
280 | /* Similar to NORMAL, but in TS_COPY we need to copy bytes */ |
281 | if (c == IAC) |
282 | G.telstate = TS_IAC; |
283 | else |
284 | G.buf[cstart++] = c; |
285 | if (c == '\r') |
286 | G.telstate = TS_CR; |
287 | break; |
288 | |
289 | case TS_IAC: /* Prev char was IAC */ |
290 | if (c == IAC) { /* IAC IAC -> one IAC */ |
291 | G.buf[cstart++] = c; |
292 | G.telstate = TS_COPY; |
293 | break; |
294 | } |
295 | /* else */ |
296 | switch (c) { |
297 | case SB: |
298 | G.telstate = TS_SUB1; |
299 | break; |
300 | case DO: |
301 | case DONT: |
302 | case WILL: |
303 | case WONT: |
304 | G.telwish = c; |
305 | G.telstate = TS_OPT; |
306 | break; |
307 | /* DATA MARK must be added later */ |
308 | default: |
309 | G.telstate = TS_COPY; |
310 | } |
311 | break; |
312 | |
313 | case TS_OPT: /* Prev chars were IAC WILL/WONT/DO/DONT */ |
314 | telopt(c); |
315 | G.telstate = TS_COPY; |
316 | break; |
317 | |
318 | case TS_SUB1: /* Subnegotiation */ |
319 | case TS_SUB2: /* Subnegotiation */ |
320 | subneg(c); /* can change G.telstate */ |
321 | break; |
322 | } |
323 | } |
324 | |
325 | if (G.telstate != TS_NORMAL) { |
326 | /* We had some IACs, or CR */ |
327 | if (G.iaclen) |
328 | iac_flush(); |
329 | if (G.telstate == TS_COPY) /* we aren't in the middle of IAC */ |
330 | G.telstate = TS_NORMAL; |
331 | len = cstart; |
332 | } |
333 | |
334 | if (len) |
335 | full_write(STDOUT_FILENO, G.buf, len); |
336 | } |
337 | |
338 | static void put_iac(int c) |
339 | { |
340 | G.iacbuf[G.iaclen++] = c; |
341 | } |
342 | |
343 | static void put_iac2_merged(unsigned wwdd_and_c) |
344 | { |
345 | if (G.iaclen + 3 > IACBUFSIZE) |
346 | iac_flush(); |
347 | |
348 | put_iac(IAC); |
349 | put_iac(wwdd_and_c >> 8); |
350 | put_iac(wwdd_and_c & 0xff); |
351 | } |
352 | #define put_iac2(wwdd,c) put_iac2_merged(((wwdd)<<8) + (c)) |
353 | |
354 | #if ENABLE_FEATURE_TELNET_TTYPE |
355 | static void put_iac_subopt(byte c, char *str) |
356 | { |
357 | int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) |
358 | |
359 | if (G.iaclen + len > IACBUFSIZE) |
360 | iac_flush(); |
361 | |
362 | put_iac(IAC); |
363 | put_iac(SB); |
364 | put_iac(c); |
365 | put_iac(0); |
366 | |
367 | while (*str) |
368 | put_iac(*str++); |
369 | |
370 | put_iac(IAC); |
371 | put_iac(SE); |
372 | } |
373 | #endif |
374 | |
375 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
376 | static void put_iac_subopt_autologin(void) |
377 | { |
378 | int len = strlen(G.autologin) + 6; // (2 + 1 + 1 + strlen + 2) |
379 | const char *p = "USER"; |
380 | |
381 | if (G.iaclen + len > IACBUFSIZE) |
382 | iac_flush(); |
383 | |
384 | put_iac(IAC); |
385 | put_iac(SB); |
386 | put_iac(TELOPT_NEW_ENVIRON); |
387 | put_iac(TELQUAL_IS); |
388 | put_iac(NEW_ENV_VAR); |
389 | |
390 | while (*p) |
391 | put_iac(*p++); |
392 | |
393 | put_iac(NEW_ENV_VALUE); |
394 | |
395 | p = G.autologin; |
396 | while (*p) |
397 | put_iac(*p++); |
398 | |
399 | put_iac(IAC); |
400 | put_iac(SE); |
401 | } |
402 | #endif |
403 | |
404 | #if ENABLE_FEATURE_AUTOWIDTH |
405 | static void put_iac_naws(byte c, int x, int y) |
406 | { |
407 | if (G.iaclen + 9 > IACBUFSIZE) |
408 | iac_flush(); |
409 | |
410 | put_iac(IAC); |
411 | put_iac(SB); |
412 | put_iac(c); |
413 | |
414 | /* "... & 0xff" implicitly done below */ |
415 | put_iac(x >> 8); |
416 | put_iac(x); |
417 | put_iac(y >> 8); |
418 | put_iac(y); |
419 | |
420 | put_iac(IAC); |
421 | put_iac(SE); |
422 | } |
423 | #endif |
424 | |
425 | static void setConMode(void) |
426 | { |
427 | if (G.telflags & UF_ECHO) { |
428 | if (G.charmode == CHM_TRY) { |
429 | G.charmode = CHM_ON; |
430 | printf("\r\nEntering %s mode" |
431 | "\r\nEscape character is '^%c'.\r\n", "character", ']'); |
432 | rawmode(); |
433 | } |
434 | } else { |
435 | if (G.charmode != CHM_OFF) { |
436 | G.charmode = CHM_OFF; |
437 | printf("\r\nEntering %s mode" |
438 | "\r\nEscape character is '^%c'.\r\n", "line", 'C'); |
439 | cookmode(); |
440 | } |
441 | } |
442 | } |
443 | |
444 | static void will_charmode(void) |
445 | { |
446 | G.charmode = CHM_TRY; |
447 | G.telflags |= (UF_ECHO | UF_SGA); |
448 | setConMode(); |
449 | |
450 | put_iac2(DO, TELOPT_ECHO); |
451 | put_iac2(DO, TELOPT_SGA); |
452 | iac_flush(); |
453 | } |
454 | |
455 | static void do_linemode(void) |
456 | { |
457 | G.charmode = CHM_TRY; |
458 | G.telflags &= ~(UF_ECHO | UF_SGA); |
459 | setConMode(); |
460 | |
461 | put_iac2(DONT, TELOPT_ECHO); |
462 | put_iac2(DONT, TELOPT_SGA); |
463 | iac_flush(); |
464 | } |
465 | |
466 | static void to_notsup(char c) |
467 | { |
468 | if (G.telwish == WILL) |
469 | put_iac2(DONT, c); |
470 | else if (G.telwish == DO) |
471 | put_iac2(WONT, c); |
472 | } |
473 | |
474 | static void to_echo(void) |
475 | { |
476 | /* if server requests ECHO, don't agree */ |
477 | if (G.telwish == DO) { |
478 | put_iac2(WONT, TELOPT_ECHO); |
479 | return; |
480 | } |
481 | if (G.telwish == DONT) |
482 | return; |
483 | |
484 | if (G.telflags & UF_ECHO) { |
485 | if (G.telwish == WILL) |
486 | return; |
487 | } else if (G.telwish == WONT) |
488 | return; |
489 | |
490 | if (G.charmode != CHM_OFF) |
491 | G.telflags ^= UF_ECHO; |
492 | |
493 | if (G.telflags & UF_ECHO) |
494 | put_iac2(DO, TELOPT_ECHO); |
495 | else |
496 | put_iac2(DONT, TELOPT_ECHO); |
497 | |
498 | setConMode(); |
499 | full_write1_str("\r\n"); /* sudden modec */ |
500 | } |
501 | |
502 | static void to_sga(void) |
503 | { |
504 | /* daemon always sends will/wont, client do/dont */ |
505 | |
506 | if (G.telflags & UF_SGA) { |
507 | if (G.telwish == WILL) |
508 | return; |
509 | } else if (G.telwish == WONT) |
510 | return; |
511 | |
512 | G.telflags ^= UF_SGA; /* toggle */ |
513 | if (G.telflags & UF_SGA) |
514 | put_iac2(DO, TELOPT_SGA); |
515 | else |
516 | put_iac2(DONT, TELOPT_SGA); |
517 | } |
518 | |
519 | #if ENABLE_FEATURE_TELNET_TTYPE |
520 | static void to_ttype(void) |
521 | { |
522 | /* Tell server we will (or won't) do TTYPE */ |
523 | if (G.ttype) |
524 | put_iac2(WILL, TELOPT_TTYPE); |
525 | else |
526 | put_iac2(WONT, TELOPT_TTYPE); |
527 | } |
528 | #endif |
529 | |
530 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
531 | static void to_new_environ(void) |
532 | { |
533 | /* Tell server we will (or will not) do AUTOLOGIN */ |
534 | if (G.autologin) |
535 | put_iac2(WILL, TELOPT_NEW_ENVIRON); |
536 | else |
537 | put_iac2(WONT, TELOPT_NEW_ENVIRON); |
538 | } |
539 | #endif |
540 | |
541 | #if ENABLE_FEATURE_AUTOWIDTH |
542 | static void to_naws(void) |
543 | { |
544 | /* Tell server we will do NAWS */ |
545 | put_iac2(WILL, TELOPT_NAWS); |
546 | } |
547 | #endif |
548 | |
549 | static void telopt(byte c) |
550 | { |
551 | switch (c) { |
552 | case TELOPT_ECHO: |
553 | to_echo(); break; |
554 | case TELOPT_SGA: |
555 | to_sga(); break; |
556 | #if ENABLE_FEATURE_TELNET_TTYPE |
557 | case TELOPT_TTYPE: |
558 | to_ttype(); break; |
559 | #endif |
560 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
561 | case TELOPT_NEW_ENVIRON: |
562 | to_new_environ(); break; |
563 | #endif |
564 | #if ENABLE_FEATURE_AUTOWIDTH |
565 | case TELOPT_NAWS: |
566 | to_naws(); |
567 | put_iac_naws(c, G.win_width, G.win_height); |
568 | break; |
569 | #endif |
570 | default: |
571 | to_notsup(c); |
572 | break; |
573 | } |
574 | } |
575 | |
576 | /* subnegotiation -- ignore all (except TTYPE,NAWS) */ |
577 | static void subneg(byte c) |
578 | { |
579 | switch (G.telstate) { |
580 | case TS_SUB1: |
581 | if (c == IAC) |
582 | G.telstate = TS_SUB2; |
583 | #if ENABLE_FEATURE_TELNET_TTYPE |
584 | else |
585 | if (c == TELOPT_TTYPE && G.ttype) |
586 | put_iac_subopt(TELOPT_TTYPE, G.ttype); |
587 | #endif |
588 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
589 | else |
590 | if (c == TELOPT_NEW_ENVIRON && G.autologin) |
591 | put_iac_subopt_autologin(); |
592 | #endif |
593 | break; |
594 | case TS_SUB2: |
595 | if (c == SE) { |
596 | G.telstate = TS_COPY; |
597 | return; |
598 | } |
599 | G.telstate = TS_SUB1; |
600 | break; |
601 | } |
602 | } |
603 | |
604 | static void rawmode(void) |
605 | { |
606 | if (G.do_termios) |
607 | tcsetattr(0, TCSADRAIN, &G.termios_raw); |
608 | } |
609 | |
610 | static void cookmode(void) |
611 | { |
612 | if (G.do_termios) |
613 | tcsetattr(0, TCSADRAIN, &G.termios_def); |
614 | } |
615 | |
616 | int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
617 | int telnet_main(int argc UNUSED_PARAM, char **argv) |
618 | { |
619 | char *host; |
620 | int port; |
621 | int len; |
622 | struct pollfd ufds[2]; |
623 | |
624 | INIT_G(); |
625 | |
626 | #if ENABLE_FEATURE_AUTOWIDTH |
627 | get_terminal_width_height(0, &G.win_width, &G.win_height); |
628 | #endif |
629 | |
630 | #if ENABLE_FEATURE_TELNET_TTYPE |
631 | G.ttype = getenv("TERM"); |
632 | #endif |
633 | |
634 | if (tcgetattr(0, &G.termios_def) >= 0) { |
635 | G.do_termios = 1; |
636 | G.termios_raw = G.termios_def; |
637 | cfmakeraw(&G.termios_raw); |
638 | } |
639 | |
640 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
641 | if (1 & getopt32(argv, "al:", &G.autologin)) |
642 | G.autologin = getenv("USER"); |
643 | argv += optind; |
644 | #else |
645 | argv++; |
646 | #endif |
647 | if (!*argv) |
648 | bb_show_usage(); |
649 | host = *argv++; |
650 | port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23); |
651 | if (*argv) /* extra params?? */ |
652 | bb_show_usage(); |
653 | |
654 | xmove_fd(create_and_connect_stream_or_die(host, port), netfd); |
655 | |
656 | setsockopt_keepalive(netfd); |
657 | |
658 | signal(SIGINT, record_signo); |
659 | |
660 | ufds[0].fd = STDIN_FILENO; |
661 | ufds[0].events = POLLIN; |
662 | ufds[1].fd = netfd; |
663 | ufds[1].events = POLLIN; |
664 | |
665 | while (1) { |
666 | if (poll(ufds, 2, -1) < 0) { |
667 | /* error, ignore and/or log something, bay go to loop */ |
668 | if (bb_got_signal) |
669 | con_escape(); |
670 | else |
671 | sleep(1); |
672 | continue; |
673 | } |
674 | |
675 | // FIXME: reads can block. Need full bidirectional buffering. |
676 | |
677 | if (ufds[0].revents) { |
678 | len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE); |
679 | if (len <= 0) |
680 | doexit(EXIT_SUCCESS); |
681 | TRACE(0, ("Read con: %d\n", len)); |
682 | handle_net_output(len); |
683 | } |
684 | |
685 | if (ufds[1].revents) { |
686 | len = safe_read(netfd, G.buf, DATABUFSIZE); |
687 | if (len <= 0) { |
688 | full_write1_str("Connection closed by foreign host\r\n"); |
689 | doexit(EXIT_FAILURE); |
690 | } |
691 | TRACE(0, ("Read netfd (%d): %d\n", netfd, len)); |
692 | handle_net_input(len); |
693 | } |
694 | } /* while (1) */ |
695 | } |
696 |