[git commit] telnet: fixes after testing against some MUD servers

Denys Vlasenko vda.linux at googlemail.com
Tue Feb 24 06:16:16 UTC 2026


commit: https://git.busybox.net/busybox/commit/?id=b86f0198d6879cb63485b14c050925e34b6549d0
branch: https://git.busybox.net/busybox/log/?h=master

A few size tests were not tight enough. More importantly,
the logic "is this a telnet server?" made more robust.

TTYPE seems to be understood by the MUD server I tried,
for some reason NAWS is not?

function                                             old     new   delta
packed_usage                                       36040   36078     +38
write_to_net                                         598     620     +22
telnet_main                                          455     462      +7
handle_SIGWINCH                                       15      21      +6
read_from_net                                        534     539      +5
show_menu                                            212     203      -9
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 5/1 up/down: 78/-9)              Total: 69 bytes

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 networking/telnet.c | 305 +++++++++++++++++++++++++++++++++-------------------
 1 file changed, 193 insertions(+), 112 deletions(-)

diff --git a/networking/telnet.c b/networking/telnet.c
index 8a7803637..66fc0bdf1 100644
--- a/networking/telnet.c
+++ b/networking/telnet.c
@@ -52,20 +52,14 @@
 
 //kbuild:lib-$(CONFIG_TELNET) += telnet.o
 
-//usage:#if ENABLE_FEATURE_TELNET_AUTOLOGIN
 //usage:#define telnet_trivial_usage
-//usage:       "[-a] [-l USER] HOST [PORT]"
-//usage:#define telnet_full_usage "\n\n"
-//usage:       "Connect to telnet server\n"
-//usage:     "\n	-a	Automatic login with $USER variable"
-//usage:     "\n	-l USER	Automatic login as USER"
-//usage:
-//usage:#else
-//usage:#define telnet_trivial_usage
-//usage:       "HOST [PORT]"
+//usage:	IF_FEATURE_TELNET_AUTOLOGIN("[-a] [-l USER] ")"HOST [PORT]"
 //usage:#define telnet_full_usage "\n\n"
 //usage:       "Connect to telnet server"
-//usage:#endif
+//usage:	IF_FEATURE_TELNET_AUTOLOGIN("\n"
+//usage:     "\n	-a	Automatic login with $USER envvar"
+//usage:     "\n	-l USER	Automatic login as USER"
+//usage:	)
 
 #include <arpa/telnet.h>
 #include <netinet/in.h>
@@ -79,15 +73,24 @@
 # define DO          253  /* please, you use option */
 # define WONT        252  /* I won't use option */
 # define WILL        251  /* I will use option (if I see your "IAC DO <opt>" confirmation */
-# define SB          250  /* interpret as subnegotiation */
-# define SE          240  /* end sub negotiation */
+# define SB          250  /* begin subnegotiation */
+# define SE          240  /* end subnegotiation */
 # define TELOPT_ECHO   1  /* echo */
 # define TELOPT_SGA    3  /* suppress go ahead */
 # define TELOPT_TTYPE 24  /* terminal type */
 # define TELOPT_NAWS  31  /* window size */
 #endif
 
-#define DEBUG 0
+#define SUPPORT_FOR_LOCAL_OPTS (0 \
+	|| ENABLE_FEATURE_TELNET_WIDTH \
+	|| ENABLE_FEATURE_TELNET_TTYPE \
+	|| ENABLE_FEATURE_TELNET_AUTOLOGIN \
+	)
+
+// -v option
+#define VERBOSE 0
+// lots of unconditional debug
+#define DEBUG   0
 
 #if DEBUG
 # define dbg(...) bb_error_msg(__VA_ARGS__)
@@ -102,6 +105,14 @@ static char *bin_to_hex(const void *hash_value, unsigned hash_length)
 # define dbg(...) ((void)0)
 #endif
 
+#if VERBOSE
+# define log1(...) do { if (G.verbose) bb_error_msg(__VA_ARGS__); } while (0)
+# define IF_VERBOSE(...) __VA_ARGS__
+#else
+# define log1(...) ((void)0)
+# define IF_VERBOSE(...) /* nothing */
+#endif
+
 enum {
 	TS_NORMAL = 0,
 	TS_IAC  = 1,
@@ -110,7 +121,7 @@ enum {
 	TS_SUB2 = 4,
 	TS_CR   = 5,
 
-	MAX_NAWS_SIZE = 13, /* pathological 65535x65535 case needs full escaping */
+	MAX_NAWS_SIZE = 13, // pathological 65535x65535 case needs full escaping
 
 	netfd   = 3,
 };
@@ -144,45 +155,51 @@ static void set_input_state(net_to_stdout_t *conn, int new_state, int c)
 #endif
 
 struct globals {
-	unsigned flags;
-/* Set when server agreed to use NAWS: */
-#define FLAGS_NAWS_ON  (1 << 0)
-/* SGA option seen and responded to, no longer look for it: */
-#define FLAGS_SGA_SEEN (1 << 1)
-/* Seen telnet protocol from server and sent our wishes: */
-#define INITIAL_SENT   (1 << 2)
-#define DO_TERMIOS     (1 << 3)
-
-	byte                word_aligned_bytes[2];
-#define changed             word_aligned_bytes[0]
+	unsigned        flags;
+// Set when server agreed to use NAWS:
+#define FLAGS_NAWS_ON   (1 << 0)
+// SGA option seen and responded to, no longer look for it:
+#define FLAGS_SGA_SEEN  (1 << 1)
+// Seen telnet protocol from server and sent our WILLs:
+#define INITIAL_SENT    (1 << 2)
+#define DO_TERMIOS      (1 << 3)
+
+	byte            word_aligned_bytes[2];
+#define changed         word_aligned_bytes[0]
 #define CHANGED_ECHO        (1 << 0)
 #define CHANGED_NAWS        (1 << 1)
-/* These happen only once: */
+// These happen only once:
 #define CHANGED_SGA         (1 << 2)
 #define CHANGED_TTYPE       (1 << 3)
 #define CHANGED_NEW_ENVIRON (1 << 4)
-/* The second byte is changed async, by signal handler: */
-#define got_SIGWINCH        word_aligned_bytes[1]
-#define G_changed_word      (*(uint16_t*)G.word_aligned_bytes)
+// The second byte is changed async, by signal handler:
+#define got_SIGWINCH    word_aligned_bytes[1]
+#define G_changed_word  (*(uint16_t*)G.word_aligned_bytes)
 
 	byte            optstate_ECHO;
+#define OPT_ECHO_ON      1
+// We operate tty in rawmode only when echo ON (IOW: we saw server say that)
+#define OPT_ECHO_OFF     0
+#define OPT_ECHO_UNKNOWN 0xff /* on program start */
+
 	byte            echo_sga_response_size;
 
+	IF_VERBOSE(                 unsigned verbose;)
 	IF_FEATURE_TELNET_TTYPE(    const char *ttype;)
 	IF_FEATURE_TELNET_AUTOLOGIN(const char *autologin;)
-	ioloop_state_t io;
-	stdin_to_net_t conn_stdin2net;
+	ioloop_state_t  io;
+	stdin_to_net_t  conn_stdin2net;
 	net_to_stdout_t conn_net2stdout;
-	struct termios termios_def;
-	struct termios termios_raw;
+	struct termios  termios_def;
+	struct termios  termios_raw;
 // buf[] arrays in conn structs are conceptually cleaner, but they
 // make G.member offsets larger -> larger code
-#define BUF_TTY2NET ((byte*)bb_common_bufsiz1)
-#define BUF_NET2TTY G.buf2
-#define BUFSIZE     1024
-/* Note: can't just increase BUFSIZE arbitrarily: common bufsiz1 is not guaranteed to be >1k! */
-#define BUFMASK     (BUFSIZE - 1)
-	byte        buf2[BUFSIZE];
+#define BUF_TTY2NET     ((byte*)bb_common_bufsiz1)
+#define BUF_NET2TTY     G.buf2
+#define BUFSIZE         1024
+// Note: can't just increase BUFSIZE arbitrarily: common bufsiz1 is not guaranteed to be >1k!
+#define BUFMASK         (BUFSIZE - 1)
+	byte            buf2[BUFSIZE];
 } FIX_ALIASING;
 #define G (*ptr_to_globals)
 #define INIT_G() do { \
@@ -197,7 +214,8 @@ static int remaining_free_bytes(int n)
 #if ENABLE_FEATURE_TELNET_WIDTH
 static void handle_SIGWINCH(int sig UNUSED_PARAM)
 {
-	if (G.flags & FLAGS_NAWS_ON) /* Only if server okayed NAWS */
+	// Only if server said DO NAWS and seen our WILL NAWS:
+	if ((G.flags & (FLAGS_NAWS_ON|INITIAL_SENT)) == (FLAGS_NAWS_ON|INITIAL_SENT))
 		G.got_SIGWINCH = 1;
 }
 #endif
@@ -237,9 +255,7 @@ static void put_iac2_msb_lsb(unsigned x_y)
 }
 #define put_iac2_x_y(x,y) put_iac2_msb_lsb(((x)<<8) + (y))
 
-#if ENABLE_FEATURE_TELNET_WIDTH \
- || ENABLE_FEATURE_TELNET_TTYPE \
- || ENABLE_FEATURE_TELNET_AUTOLOGIN
+#if SUPPORT_FOR_LOCAL_OPTS
 static void put_iac4_msb_lsb(unsigned x_y_z_t)
 {
 	put_iac2_msb_lsb(x_y_z_t >> 16);
@@ -303,6 +319,7 @@ static void put_iac_naws(void)
 
 	/* Send width and height as 16-bit big-endian, escaping IAC bytes */
 	get_terminal_width_height(0, &width, &height);
+	log1("C:SB NAWS %dx%d", width, height);
 	put_iac_byte_escaped(width >> 8);
 	put_iac_byte_escaped(width);
 	put_iac_byte_escaped(height >> 8);
@@ -314,125 +331,175 @@ static void put_iac_naws(void)
 
 // Telnet option handling strategy:
 //
-// We send nothing on startup by our own (think "telnet www.kernel.org 80").
-// As soon as we see ECHO or SGA message, we respond to them and then
-// send "IAC WILL NAWS", "IAC WILL TTYPE", "IAC WILL NEW_ENVIRON"
+// We send nothing on startup on our own (think "telnet www.kernel.org 80").
+// As soon as we see any DO X or WILL X message known to us, we respond
+// to them: send DO/DONT ECHO, DO SGA if server talked about them, then send
+// WILL NAWS, WILL TTYPE, WILL NEW_ENVIRON
 // without waiting for server to advertise those options.
-// (Why? servers may decide to not advertise all 123 options they know).
+// (Why? Some servers may decide to not advertise all 123 options they know).
 //
 // TELOPT_ECHO (1) - remote: server echoes our keystrokes back to us
-// - Server says: "IAC WILL ECHO" or "IAC WONT ECHO"
+// - Server says: WILL ECHO or WONT ECHO
 // - If we don't see it, or see WONT: line mode (local terminal echoes), aka cooked mode
 // - When active: We're in character mode (server echoes), aka raw mode
 // - Response logic:
-//   - Server says WILL ECHO: we send "IAC DO ECHO", enter raw mode
-//     (this is the most usual case for non-ancient servers)
-//   - Server says WONT ECHO: we send "IAC DONT ECHO", enter cooked mode
-//   - We mirror server's changes, we do allow it to change during session
-//     unlike one-shot options which are negotiated once during startup.
-//   - If user changes mode from ^] menu, we send corresponding "IAC DO/DONT ECHO".
+// - Server says WILL ECHO: we send DO ECHO, enter raw mode
+//   (this is the most usual case for non-ancient servers)
+// - Server says WONT ECHO: we send DONT ECHO, enter cooked mode
+// - We mirror server's changes, we do allow it to change during session
+//   unlike one-shot options which are negotiated once during startup.
+// - If user changes mode from ^] menu, we send corresponding DO/DONT ECHO.
 //
 // TELOPT_SGA (3) - remote: "Suppress Go Ahead", full-duplex
-// - Server says: "IAC WILL SGA" or "IAC WONT SGA"
+// - Server says: WILL SGA or WONT SGA
 // - Response logic: one-shot.
-//   - We expect all sane servers to say "IAC WILL SGA" at startup.
-//   - If that happens, we send "IAC DO SGA". After that, we never react to any
-//     further SGA messages (we expect them to not happen in practice)
+// - We expect all sane servers to choose WILL SGA at startup.
+// - If that happens, we send DO SGA. After that, we never react to any
+//   further SGA messages (we expect them to not repeat in practice)
 //
 // TELOPT_NAWS (31) - Window Size - local: we send our window size
-// - If server says: "IAC DO NAWS", it understands NAWS. We check this.
+// - If server says: DO NAWS, it understands NAWS. We require this before
+// - sending SB NAWS ...
 // - We send window size during initial handshake and on window resize
 //
 // TELOPT_TTYPE (24) - Terminal Type - local: we send our terminal type
 // - Response logic: one-shot.
-//   - If server says DO TTYPE: send "IAC SB TTYPE 0 <$TERM> IAC SE"
+// - If server says DO TTYPE: send "IAC SB TTYPE 0 '$TERM' IAC SE"
 //
 // TELOPT_NEW_ENVIRON (39) - Environment (Autologin) - local: we send our username
 // - Response logic: one-shot.
-//   - If server says DO NEW_ENVIRON: send "IAC SB NEW_ENVIRON IS VAR USER VALUE <name> IAC SE"
+// - If server says DO NEW_ENVIRON: send "IAC SB NEW_ENVIRON IS VAR 'USER' VALUE 'name' IAC SE"
 //
 // Unknown options:
 // Response: NONE - just ignore them
 // By protocol definition, an option is not in effect until BOTH sides agree.
 // We don't respond to unknown options - they simply won't be in effect.
+//
+// Testing at discworld.atuin.net:23 reveal servers often do NOT negotiate ECHO/SGA
+// in initial handshake while supporting NAWS and TTYPE:
+//telnet: S:DO 24 <--TTYPE
+//telnet: TTYPE:'xterm-256color' setting CHANGED_TTYPE
+//LPmud version : DW OS v1.02 on port 4242.
+//telnet: changed:8 flags:8
+//telnet: C:SB TTYPE 'xterm-256color'
+//telnet: S:DO 31 <--NAWS
+//telnet: S:WILL 86
+//telnet: S:DO 91
+//telnet: S:WILL 70
+//telnet: S:WILL 93
+//telnet: S:DO 39
+//telnet: S:WILL 201
+//......text.....
+//telnet: changed:2 flags:8
+//telnet: C:SB NAWS 226x53
+//...text....
+//telnet: S:SB 24 <--TTYPE
+//...text....
+//Setting your network terminal type to "xterm-256color".
+//> cols
+//Columns currently set to 79.
+//> rows
+//Rows currently set to 24.
+//^^^ server understood TTYPE but not NAWS. ?!
 static void handle_changes_in_options(stdin_to_net_t *conn)
 {
 	int count;
 
-	//bb_error_msg("changed:%x flags:%x", G.changed, G.flags);
+	log1("changed:%x flags:%x", G.changed, G.flags);
 
 	count = remaining_free_bytes(conn->size);
-	/* As soon as we see either ECHO or SGA from server,
-	 * we assume it *is* a telnet server
-	 * (not in "telnet www.kernel.org 80" scenario),
-	 * and we respond to them, also expressing our own
-	 * wishes: "WILL NAWS" etc.
-	 */
-	if ((G.changed & (CHANGED_ECHO|CHANGED_SGA))
+	// As soon as we see any DO/DONT/WILL/WONT known to us,
+	// we assume it *is* a telnet server
+	// (we are not in "telnet www.kernel.org 80" scenario),
+	// and we respond to them, also expressing our own
+	// wishes: WILL NAWS etc. We send our WILLs once, all of them.
+	// We send actual SB with option contents after WILLs
+	// only if that option was requested.
+	// We repeatedly send only NAWS (when our window changes).
+	// Repeated DO requests are ignored.
+	if (G.changed
 	 && (count >= G.echo_sga_response_size)
 	) {
 		if (G.changed & CHANGED_ECHO) {
-			/* Server said WILL/WONT - confirm */
+			// Server said WILL/WONT ECHO - confirm every time
+			log1("C:%s ECHO", G.optstate_ECHO ? "DO" : "DONT");
 			put_iac3_IAC_x_y(G.optstate_ECHO ? DO : DONT, TELOPT_ECHO);
 		}
 		if (G.changed & CHANGED_SGA) {
-			/* Server said WILL - send DO */
+			// Server said WILL SGA - confirm once
+			log1("C:DO SGA");
 			put_iac3_IAC_x_y(DO, TELOPT_SGA);
-			G.flags |= FLAGS_SGA_SEEN; /* remember we did it */
+			G.flags |= FLAGS_SGA_SEEN; // remember we did it
 			G.changed -= CHANGED_SGA;
 		}
 		G.changed &= ~(CHANGED_ECHO|CHANGED_SGA);
 
 		if (!(G.flags & INITIAL_SENT)) {
+			// From now on, we'll only do DO/DONT ECHO and maybe DO SGA
+			// in the "if (G.changed)" block.
 			G.flags |= INITIAL_SENT;
-			G.echo_sga_response_size = 6; /* from now on, we'll need only 6 */
+			G.echo_sga_response_size = (G.flags & FLAGS_SGA_SEEN) ? 3 : 6;
 
-			/* Send initial IAC sequences for local options we want to advertise */
+			// Send initial IAC sequences for local options we want to advertise
+			// (there may be servers which send DO X only after we say WILL X)
 #if ENABLE_FEATURE_TELNET_WIDTH
+			log1("C:WILL %s", "NAWS");
 			put_iac3_IAC_x_y(WILL, TELOPT_NAWS);
 #endif
 #if ENABLE_FEATURE_TELNET_TTYPE
-			if (G.ttype)
+			if (G.ttype) {
+				log1("C:WILL %s", "TTYPE");
 				put_iac3_IAC_x_y(WILL, TELOPT_TTYPE);
+			}
 #endif
 #if ENABLE_FEATURE_TELNET_AUTOLOGIN
-			if (G.autologin)
+			if (G.autologin) {
+				log1("C:WILL %s", "NEW_ENVIRON");
 				put_iac3_IAC_x_y(WILL, TELOPT_NEW_ENVIRON);
+			}
 #endif
 		}
 	}
 
+	// If any of the checks below are true, then we know we
+	// went through INITIAL_SENT code path above.
+	// Therefore we always send WILL X before SB X...
 #if ENABLE_FEATURE_TELNET_WIDTH
 	if (remaining_free_bytes(conn->size) > MAX_NAWS_SIZE) {
 		if (G.changed & CHANGED_NAWS) {
-			G.flags |= FLAGS_NAWS_ON; /* remember we did it */
+			G.flags |= FLAGS_NAWS_ON; // remember we did it
 			G.changed -= CHANGED_NAWS;
 			goto generate_naws;
 		}
-		/* Handle window resize: send updated NAWS if we have room */
+		// Handle window resize: send updated NAWS
 		if (G.got_SIGWINCH) {
+			// we know that INITIAL_SENT is set, signal handler checks that
  generate_naws:
+			// Clear the flag before put_iac_naws() to avoid race
 			G.got_SIGWINCH = 0;
-			/* Clear the flag before put_iac_naws() to avoid race! */
+//log1("C:WILL %s", "NAWS, the second time, I'm telling you it's fine!");
+//put_iac3_IAC_x_y(WILL, TELOPT_NAWS);
 			put_iac_naws();
 		}
 	}
 #endif
 #if ENABLE_FEATURE_TELNET_TTYPE
 	if ((G.changed & CHANGED_TTYPE)
-	 && remaining_free_bytes(conn->size) > 2 * strlen(G.ttype)
+	 && remaining_free_bytes(conn->size) > 6 + 2 * strlen(G.ttype)
 	) {
+		log1("C:SB %s '%s'", "TTYPE", G.ttype);
 		put_iac_subopt(TELOPT_TTYPE, G.ttype);
-		G.ttype = NULL; /* remember we did it */
+		G.ttype = NULL; // remember we did it
 		G.changed -= CHANGED_TTYPE;
 	}
 #endif
 #if ENABLE_FEATURE_TELNET_AUTOLOGIN
 	if ((G.changed & CHANGED_NEW_ENVIRON)
-	 && remaining_free_bytes(conn->size) > 2 * strlen(G.autologin)
+	 && remaining_free_bytes(conn->size) > 12 + 2 * strlen(G.autologin)
 	) {
+		log1("C:SB %s '%s'", "NEW_ENVIRON", G.autologin);
 		put_iac_subopt_autologin(G.autologin);
-		G.autologin = NULL; /* remember we did it */
+		G.autologin = NULL; // remember we did it
 		G.changed -= CHANGED_NEW_ENVIRON;
 	}
 #endif
@@ -477,20 +544,19 @@ static void show_menu(void)
 
 	switch (b) {
 	case 'l':
-		if (G.optstate_ECHO) {
-			G.optstate_ECHO = 0;
-			if (G.flags & INITIAL_SENT)
-				G.changed |= CHANGED_ECHO; /* inform the server at next send */
+		if (G.optstate_ECHO == OPT_ECHO_ON) { /* are we in rawmode? */
+			G.optstate_ECHO = OPT_ECHO_OFF;
 			announce_and_switch_to_cookmode();
-			return;
+			goto echo_changed;
 		}
 		break;
 	case 'c':
-		if (!G.optstate_ECHO) {
-			G.optstate_ECHO = 1;
+		if (G.optstate_ECHO != OPT_ECHO_ON) { /* OFF or UNKNOWN? (both operate as linemode) */
+			G.optstate_ECHO = OPT_ECHO_ON;
+			announce_rawmode(); /* no "_and_switch_": we are already in rawmode */
+ echo_changed:
 			if (G.flags & INITIAL_SENT)
 				G.changed |= CHANGED_ECHO; /* inform the server at next send */
-			announce_rawmode(); /* no "_and_switch_": we are already in rawmode */
 			return;
 		}
 		break;
@@ -503,7 +569,7 @@ static void show_menu(void)
 
 	full_write1_str("continuing...\r\n");
 
-	if (!G.optstate_ECHO)
+	if (G.optstate_ECHO != OPT_ECHO_ON)
 		cookmode();
 }
 
@@ -722,7 +788,8 @@ static int read_from_net(void *this)
 			} else if (c == SB) {
 				conn->negotiation_verb = 0xff; /* reuse as counter */
 				SET_INPUT_STATE(conn, TS_SUB1, c);
-			} else if (c == DO || c == DONT || c == WILL || c == WONT) {
+				log1("S:SB %d", count ? *src : 0);
+			} else if (c == WILL || c == WONT || c == DO || c == DONT) { /* 251-254 */
 				conn->negotiation_verb = c;
 				SET_INPUT_STATE(conn, TS_OPT, c);
 			} else {
@@ -732,6 +799,12 @@ static int read_from_net(void *this)
 			break;
 
 		case TS_OPT:
+#if VERBOSE
+			{
+				static const char verbs[] ALIGN1 = "WILL\0""WONT\0""DO\0""DONT";
+				log1("S:%s %d", nth_string(verbs, conn->negotiation_verb - WILL), c);
+			}
+#endif
 			/* Process option negotiation */
 			will = (conn->negotiation_verb == WILL);
 			if (will || conn->negotiation_verb == WONT) {
@@ -748,6 +821,7 @@ static int read_from_net(void *this)
 				switch (c) {
 #if ENABLE_FEATURE_TELNET_TTYPE
 				case TELOPT_TTYPE: /* Local option: we send terminal type */
+					log1("TTYPE:'%s' %ssetting CHANGED_TTYPE", G.ttype, G.ttype ? "" : "not ");
 					if (G.ttype)
 						G.changed |= CHANGED_TTYPE;
 					break;
@@ -775,7 +849,7 @@ static int read_from_net(void *this)
 			 * if IAC SE is never seen (buggy server response?).
 			 */
 			if (--conn->negotiation_verb == 0) {
-				dbg("unterminated SB seen");
+				log1("unterminated SB (server bug?)");
 				SET_INPUT_STATE(conn, TS_NORMAL, c);
 			} else
 			/* Skip over subnegotiation bytes until we see IAC */
@@ -801,7 +875,7 @@ static int read_from_net(void *this)
 		/* Tell net writer to generate a confirmation */
 		G.changed |= CHANGED_ECHO;
 		/* Print the banner and set termios */
-		if (G.optstate_ECHO)
+		if (G.optstate_ECHO == OPT_ECHO_ON)
 			announce_and_switch_to_rawmode();
 		else
 			announce_and_switch_to_cookmode();
@@ -859,30 +933,35 @@ int telnet_main(int argc UNUSED_PARAM, char **argv)
 {
 	char *host;
 	int port;
+	int opts;
 
 	INIT_G();
 
 #if ENABLE_FEATURE_TELNET_TTYPE
 	G.ttype = getenv("TERM");
 #endif
+	opts = getopt32(argv, ""IF_VERBOSE("^")
+		IF_FEATURE_TELNET_AUTOLOGIN("al:")IF_VERBOSE("v")
+		IF_VERBOSE("\0" "vv")
+		IF_FEATURE_TELNET_AUTOLOGIN(, &G.autologin)
+		IF_VERBOSE(, &G.verbose)
+	);
 #if ENABLE_FEATURE_TELNET_AUTOLOGIN
-	if (1 == getopt32(argv, "al:", &G.autologin)) {
-		/* Only -a without -l USER picks $USER from envvar */
+	if ((opts & 3) == 1) {
+		// -a without -l USER picks USER from envvar:
 		G.autologin = getenv("USER");
 	}
-	argv += optind;
-#else
-	argv++;
 #endif
+	argv += optind;
 	if (!*argv)
 		bb_show_usage();
 	host = *argv++;
 	port = *argv ? bb_lookup_port(*argv++, "tcp", 23)
 		: bb_lookup_std_port("telnet", "tcp", 23);
-	if (*argv) /* extra params?? */
+	if (*argv) // extra params??
 		bb_show_usage();
 
-	/* Save our termios */
+	// Save our termios
 	if (tcgetattr(0, &G.termios_def) >= 0) {
 		G.flags |= DO_TERMIOS;
 		G.termios_raw = G.termios_def;
@@ -899,10 +978,10 @@ int telnet_main(int argc UNUSED_PARAM, char **argv)
 	signal(SIGWINCH, handle_SIGWINCH);
 #endif
 	signal(SIGINT, record_signo);
-	/* Without this, SIGPIPE was seen on loopback connections: */
+	// Without this, SIGPIPE was seen on loopback connections:
 	signal(SIGPIPE, SIG_IGN);
 
-	/* Initialize connections */
+	// Initialize connections
 	G.conn_stdin2net.have_buffer_to_read_into = have_buffer_to_read_from_stdin;
 	G.conn_stdin2net.have_data_to_write = have_data_to_write_to_net;
 	G.conn_stdin2net.read = read_from_stdin;
@@ -924,18 +1003,20 @@ int telnet_main(int argc UNUSED_PARAM, char **argv)
 	ioloop_insert_conn(&G.io, (connection_t*)&G.conn_net2stdout);
 
 	G.echo_sga_response_size = 3 * (2
-		+ ENABLE_FEATURE_TELNET_WIDTH
+		IF_FEATURE_TELNET_WIDTH( + 1)
 		IF_FEATURE_TELNET_TTYPE(+ !!G.ttype)
 		IF_FEATURE_TELNET_AUTOLOGIN(+ !!G.autologin)
 	);
-#if DEBUG
-	/* Terminal can change to raw mode, fix line printing */
+	// Make *any* ECHO negotiation from server, positive or negative,
+	// trigger "we have a change" logic:
+	G.optstate_ECHO = OPT_ECHO_UNKNOWN;
+#if DEBUG || VERBOSE
+	// Terminal can change to raw mode, fix line printing
 	msg_eol = "\r\n";
 #endif
-	/* EINTR flag and looping is only needed to handle ^C
-	 * in line mode, otherwise just a call to ioloop_run() would do.
-	 * TODO: replace primitive line mode with read_line_input()!!!
-	 */
+	// EINTR flag and looping is only needed to handle ^C
+	// in line mode, otherwise just a call to ioloop_run() would do.
+	// TODO: replace primitive line mode with read_line_input()!!!
 	G.io.flags |= IOLOOP_FLAG_EXIT_IF_EINTR;
 	for (;;) {
 		int rc = ioloop_run(&G.io);


More information about the busybox-cvs mailing list