diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2026-02-24 23:47:52 +0100 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2026-02-24 23:47:52 +0100 |
| commit | c920ecd73df764ade3d02444d24222d6244dc826 (patch) | |
| tree | cdb7a66d760c256f8ad872ba6964c8c34df95ec8 | |
| parent | 1ecee617873163665e9f644b236ef1bad3e8a775 (diff) | |
| download | busybox-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.c | 62 |
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 | ||
| 669 | static int write_to_net(void *this) | 672 | static 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(); |
