aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2026-02-24 23:47:52 +0100
committerDenys Vlasenko <vda.linux@googlemail.com>2026-02-24 23:47:52 +0100
commitc920ecd73df764ade3d02444d24222d6244dc826 (patch)
treecdb7a66d760c256f8ad872ba6964c8c34df95ec8
parent1ecee617873163665e9f644b236ef1bad3e8a775 (diff)
downloadbusybox-w32-c920ecd73df764ade3d02444d24222d6244dc826.tar.gz
busybox-w32-c920ecd73df764ade3d02444d24222d6244dc826.tar.bz2
busybox-w32-c920ecd73df764ade3d02444d24222d6244dc826.zip
telnet: fix false positive "Error writing to foreign host"
function old new delta write_to_net 620 625 +5 Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--networking/telnet.c62
1 files changed, 34 insertions, 28 deletions
diff --git a/networking/telnet.c b/networking/telnet.c
index 66fc0bdf1..a0eadc91b 100644
--- a/networking/telnet.c
+++ b/networking/telnet.c
@@ -164,8 +164,8 @@ struct globals {
164#define INITIAL_SENT (1 << 2) 164#define INITIAL_SENT (1 << 2)
165#define DO_TERMIOS (1 << 3) 165#define DO_TERMIOS (1 << 3)
166 166
167 byte word_aligned_bytes[2]; 167 byte word_aligned_bytes[2];
168#define changed word_aligned_bytes[0] 168#define changes_seen word_aligned_bytes[0]
169#define CHANGED_ECHO (1 << 0) 169#define CHANGED_ECHO (1 << 0)
170#define CHANGED_NAWS (1 << 1) 170#define CHANGED_NAWS (1 << 1)
171// These happen only once: 171// These happen only once:
@@ -173,14 +173,17 @@ struct globals {
173#define CHANGED_TTYPE (1 << 3) 173#define CHANGED_TTYPE (1 << 3)
174#define CHANGED_NEW_ENVIRON (1 << 4) 174#define CHANGED_NEW_ENVIRON (1 << 4)
175// The second byte is changed async, by signal handler: 175// The second byte is changed async, by signal handler:
176#define got_SIGWINCH word_aligned_bytes[1] 176#define got_SIGWINCH word_aligned_bytes[1]
177#define G_changed_word (*(uint16_t*)G.word_aligned_bytes) 177#define G_changes_or_WINCH (*(uint16_t*)G.word_aligned_bytes)
178// The "shared word" trick is unsafe on only-word-store arches such as DEC Alpha or SHARC,
179// but Alpha retired in 2004 and SHARC does not run linux (it's a DSP).
178 180
179 byte optstate_ECHO; 181 byte optstate_ECHO;
180#define OPT_ECHO_ON 1 182// On program start:
181// We operate tty in rawmode only when echo ON (IOW: we saw server say that) 183#define OPT_ECHO_UNKNOWN 0xff
182#define OPT_ECHO_OFF 0 184#define OPT_ECHO_OFF 0
183#define OPT_ECHO_UNKNOWN 0xff /* on program start */ 185// We operate tty in rawmode only when echo ON (IOW: we saw server say that)
186#define OPT_ECHO_ON 1
184 187
185 byte echo_sga_response_size; 188 byte echo_sga_response_size;
186 189
@@ -405,7 +408,7 @@ static void handle_changes_in_options(stdin_to_net_t *conn)
405{ 408{
406 int count; 409 int count;
407 410
408 log1("changed:%x flags:%x", G.changed, G.flags); 411 log1("changed:%x flags:%x", G.changes_seen, G.flags);
409 412
410 count = remaining_free_bytes(conn->size); 413 count = remaining_free_bytes(conn->size);
411 // As soon as we see any DO/DONT/WILL/WONT known to us, 414 // As soon as we see any DO/DONT/WILL/WONT known to us,
@@ -417,26 +420,26 @@ static void handle_changes_in_options(stdin_to_net_t *conn)
417 // only if that option was requested. 420 // only if that option was requested.
418 // We repeatedly send only NAWS (when our window changes). 421 // We repeatedly send only NAWS (when our window changes).
419 // Repeated DO requests are ignored. 422 // Repeated DO requests are ignored.
420 if (G.changed 423 if (G.changes_seen
421 && (count >= G.echo_sga_response_size) 424 && (count >= G.echo_sga_response_size)
422 ) { 425 ) {
423 if (G.changed & CHANGED_ECHO) { 426 if (G.changes_seen & CHANGED_ECHO) {
424 // Server said WILL/WONT ECHO - confirm every time 427 // Server said WILL/WONT ECHO - confirm every time
425 log1("C:%s ECHO", G.optstate_ECHO ? "DO" : "DONT"); 428 log1("C:%s ECHO", G.optstate_ECHO ? "DO" : "DONT");
426 put_iac3_IAC_x_y(G.optstate_ECHO ? DO : DONT, TELOPT_ECHO); 429 put_iac3_IAC_x_y(G.optstate_ECHO ? DO : DONT, TELOPT_ECHO);
427 } 430 }
428 if (G.changed & CHANGED_SGA) { 431 if (G.changes_seen & CHANGED_SGA) {
429 // Server said WILL SGA - confirm once 432 // Server said WILL SGA - confirm once
430 log1("C:DO SGA"); 433 log1("C:DO SGA");
431 put_iac3_IAC_x_y(DO, TELOPT_SGA); 434 put_iac3_IAC_x_y(DO, TELOPT_SGA);
432 G.flags |= FLAGS_SGA_SEEN; // remember we did it 435 G.flags |= FLAGS_SGA_SEEN; // remember we did it
433 G.changed -= CHANGED_SGA; 436 G.changes_seen -= CHANGED_SGA;
434 } 437 }
435 G.changed &= ~(CHANGED_ECHO|CHANGED_SGA); 438 G.changes_seen &= ~(CHANGED_ECHO|CHANGED_SGA);
436 439
437 if (!(G.flags & INITIAL_SENT)) { 440 if (!(G.flags & INITIAL_SENT)) {
438 // From now on, we'll only do DO/DONT ECHO and maybe DO SGA 441 // From now on, we'll only do DO/DONT ECHO and maybe DO SGA
439 // in the "if (G.changed)" block. 442 // in the "if (G.changes_seen)" block.
440 G.flags |= INITIAL_SENT; 443 G.flags |= INITIAL_SENT;
441 G.echo_sga_response_size = (G.flags & FLAGS_SGA_SEEN) ? 3 : 6; 444 G.echo_sga_response_size = (G.flags & FLAGS_SGA_SEEN) ? 3 : 6;
442 445
@@ -466,9 +469,9 @@ static void handle_changes_in_options(stdin_to_net_t *conn)
466 // Therefore we always send WILL X before SB X... 469 // Therefore we always send WILL X before SB X...
467#if ENABLE_FEATURE_TELNET_WIDTH 470#if ENABLE_FEATURE_TELNET_WIDTH
468 if (remaining_free_bytes(conn->size) > MAX_NAWS_SIZE) { 471 if (remaining_free_bytes(conn->size) > MAX_NAWS_SIZE) {
469 if (G.changed & CHANGED_NAWS) { 472 if (G.changes_seen & CHANGED_NAWS) {
470 G.flags |= FLAGS_NAWS_ON; // remember we did it 473 G.flags |= FLAGS_NAWS_ON; // remember we did it
471 G.changed -= CHANGED_NAWS; 474 G.changes_seen -= CHANGED_NAWS;
472 goto generate_naws; 475 goto generate_naws;
473 } 476 }
474 // Handle window resize: send updated NAWS 477 // Handle window resize: send updated NAWS
@@ -484,23 +487,23 @@ static void handle_changes_in_options(stdin_to_net_t *conn)
484 } 487 }
485#endif 488#endif
486#if ENABLE_FEATURE_TELNET_TTYPE 489#if ENABLE_FEATURE_TELNET_TTYPE
487 if ((G.changed & CHANGED_TTYPE) 490 if ((G.changes_seen & CHANGED_TTYPE)
488 && remaining_free_bytes(conn->size) > 6 + 2 * strlen(G.ttype) 491 && remaining_free_bytes(conn->size) > 6 + 2 * strlen(G.ttype)
489 ) { 492 ) {
490 log1("C:SB %s '%s'", "TTYPE", G.ttype); 493 log1("C:SB %s '%s'", "TTYPE", G.ttype);
491 put_iac_subopt(TELOPT_TTYPE, G.ttype); 494 put_iac_subopt(TELOPT_TTYPE, G.ttype);
492 G.ttype = NULL; // remember we did it 495 G.ttype = NULL; // remember we did it
493 G.changed -= CHANGED_TTYPE; 496 G.changes_seen -= CHANGED_TTYPE;
494 } 497 }
495#endif 498#endif
496#if ENABLE_FEATURE_TELNET_AUTOLOGIN 499#if ENABLE_FEATURE_TELNET_AUTOLOGIN
497 if ((G.changed & CHANGED_NEW_ENVIRON) 500 if ((G.changes_seen & CHANGED_NEW_ENVIRON)
498 && remaining_free_bytes(conn->size) > 12 + 2 * strlen(G.autologin) 501 && remaining_free_bytes(conn->size) > 12 + 2 * strlen(G.autologin)
499 ) { 502 ) {
500 log1("C:SB %s '%s'", "NEW_ENVIRON", G.autologin); 503 log1("C:SB %s '%s'", "NEW_ENVIRON", G.autologin);
501 put_iac_subopt_autologin(G.autologin); 504 put_iac_subopt_autologin(G.autologin);
502 G.autologin = NULL; // remember we did it 505 G.autologin = NULL; // remember we did it
503 G.changed -= CHANGED_NEW_ENVIRON; 506 G.changes_seen -= CHANGED_NEW_ENVIRON;
504 } 507 }
505#endif 508#endif
506} 509}
@@ -556,7 +559,7 @@ static void show_menu(void)
556 announce_rawmode(); /* no "_and_switch_": we are already in rawmode */ 559 announce_rawmode(); /* no "_and_switch_": we are already in rawmode */
557 echo_changed: 560 echo_changed:
558 if (G.flags & INITIAL_SENT) 561 if (G.flags & INITIAL_SENT)
559 G.changed |= CHANGED_ECHO; /* inform the server at next send */ 562 G.changes_seen |= CHANGED_ECHO; /* inform the server at next send */
560 return; 563 return;
561 } 564 }
562 break; 565 break;
@@ -663,7 +666,7 @@ static int have_data_to_write_to_net(void *this)
663 ioloop_remove_conn(conn->io, (connection_t*)conn); 666 ioloop_remove_conn(conn->io, (connection_t*)conn);
664 return -1; 667 return -1;
665 } 668 }
666 return conn->size > 0 || G_changed_word != 0; 669 return conn->size > 0 || G_changes_or_WINCH != 0;
667} 670}
668 671
669static int write_to_net(void *this) 672static int write_to_net(void *this)
@@ -672,10 +675,13 @@ static int write_to_net(void *this)
672 int count; 675 int count;
673 676
674 /* Do we have option or NAWS changes to handle? */ 677 /* Do we have option or NAWS changes to handle? */
675 if (G_changed_word) 678 if (G_changes_or_WINCH)
676 handle_changes_in_options(conn); /* yes */ 679 handle_changes_in_options(conn); /* yes */
677 680
678 count = MIN(BUFSIZE - conn->wridx, conn->size); 681 count = MIN(BUFSIZE - conn->wridx, conn->size);
682 // can be zero due to handle_changes_in_options()
683 if (count == 0)
684 return 0;
679 count = safe_write(conn->write_fd, BUF_TTY2NET + conn->wridx, count); 685 count = safe_write(conn->write_fd, BUF_TTY2NET + conn->wridx, count);
680 if (count <= 0) { 686 if (count <= 0) {
681 if (count < 0 && errno == EAGAIN) 687 if (count < 0 && errno == EAGAIN)
@@ -814,7 +820,7 @@ static int read_from_net(void *this)
814 break; 820 break;
815 case TELOPT_SGA: /* Remote option: "suppress go ahead" */ 821 case TELOPT_SGA: /* Remote option: "suppress go ahead" */
816 if (will && !(G.flags & FLAGS_SGA_SEEN)) 822 if (will && !(G.flags & FLAGS_SGA_SEEN))
817 G.changed |= CHANGED_SGA; 823 G.changes_seen |= CHANGED_SGA;
818 break; 824 break;
819 } 825 }
820 } else if (conn->negotiation_verb == DO) { 826 } else if (conn->negotiation_verb == DO) {
@@ -823,19 +829,19 @@ static int read_from_net(void *this)
823 case TELOPT_TTYPE: /* Local option: we send terminal type */ 829 case TELOPT_TTYPE: /* Local option: we send terminal type */
824 log1("TTYPE:'%s' %ssetting CHANGED_TTYPE", G.ttype, G.ttype ? "" : "not "); 830 log1("TTYPE:'%s' %ssetting CHANGED_TTYPE", G.ttype, G.ttype ? "" : "not ");
825 if (G.ttype) 831 if (G.ttype)
826 G.changed |= CHANGED_TTYPE; 832 G.changes_seen |= CHANGED_TTYPE;
827 break; 833 break;
828#endif 834#endif
829#if ENABLE_FEATURE_TELNET_AUTOLOGIN 835#if ENABLE_FEATURE_TELNET_AUTOLOGIN
830 case TELOPT_NEW_ENVIRON: /* Local option: we send username */ 836 case TELOPT_NEW_ENVIRON: /* Local option: we send username */
831 if (G.autologin) 837 if (G.autologin)
832 G.changed |= CHANGED_NEW_ENVIRON; 838 G.changes_seen |= CHANGED_NEW_ENVIRON;
833 break; 839 break;
834#endif 840#endif
835#if ENABLE_FEATURE_TELNET_WIDTH 841#if ENABLE_FEATURE_TELNET_WIDTH
836 case TELOPT_NAWS: /* Local option: we send window size */ 842 case TELOPT_NAWS: /* Local option: we send window size */
837 if (!(G.flags & FLAGS_NAWS_ON)) 843 if (!(G.flags & FLAGS_NAWS_ON))
838 G.changed |= CHANGED_NAWS; 844 G.changes_seen |= CHANGED_NAWS;
839 break; 845 break;
840#endif 846#endif
841 } 847 }
@@ -873,7 +879,7 @@ static int read_from_net(void *this)
873 879
874 if (oldstate_ECHO != G.optstate_ECHO) { 880 if (oldstate_ECHO != G.optstate_ECHO) {
875 /* Tell net writer to generate a confirmation */ 881 /* Tell net writer to generate a confirmation */
876 G.changed |= CHANGED_ECHO; 882 G.changes_seen |= CHANGED_ECHO;
877 /* Print the banner and set termios */ 883 /* Print the banner and set termios */
878 if (G.optstate_ECHO == OPT_ECHO_ON) 884 if (G.optstate_ECHO == OPT_ECHO_ON)
879 announce_and_switch_to_rawmode(); 885 announce_and_switch_to_rawmode();