die_if_bad_username: tighten up a bit

function                                             old     new   delta
die_if_bad_username                                   77      97     +20

Based on patches from Tito.
The changes are:
better comments
we disallow '@' now - in practice such usernames will be unusable
use of the portable filename character set plus '$'
don't use isalnum as it allows non-ASCII letters in legacy 8-bit locales (pointed out by Rich Felker)
enforce maximum length of LOGIN_NAME_MAX (including NUL)
don't allow '$', '.', and '-' as first char
don't print the illegal char in error message as if it is a wide char it will be unreadable
print the position of the illegal character

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/libbb/die_if_bad_username.c b/libbb/die_if_bad_username.c
index 9946e3a..cf1297b 100644
--- a/libbb/die_if_bad_username.c
+++ b/libbb/die_if_bad_username.c
@@ -18,23 +18,45 @@
 
 void FAST_FUNC die_if_bad_username(const char *name)
 {
-	/* 1st char being dash or dot isn't valid: */
-	goto skip;
-	/* For example, name like ".." can make adduser
-	 * chown "/home/.." recursively - NOT GOOD
+	const char *start = name;
+
+	/* 1st char being dash or dot isn't valid:
+	 * for example, name like ".." can make adduser
+	 * chown "/home/.." recursively - NOT GOOD.
+	 * Name of just a single "$" is also rejected.
 	 */
+	goto skip;
 
 	do {
-		if (*name == '-' || *name == '.')
-			continue;
- skip:
-		if (isalnum(*name)
-		 || *name == '_'
-		 || *name == '@'
+		unsigned char ch;
+
+		/* These chars are valid unless they are at the 1st pos: */
+		if (*name == '-'
+		 || *name == '.'
+		/* $ is allowed if it's the last char: */
 		 || (*name == '$' && !name[1])
 		) {
 			continue;
 		}
-		bb_error_msg_and_die("illegal character '%c'", *name);
+ skip:
+		ch = *name;
+		if (ch == '_'
+		/* || ch == '@' -- we disallow this too. Think about "user@host" */
+		/* open-coded isalnum: */
+		 || (ch >= '0' && ch <= '9')
+		 || ((ch|0x20) >= 'a' && (ch|0x20) <= 'z')
+		) {
+			continue;
+		}
+		bb_error_msg_and_die("illegal character with code %u at position %u",
+				(unsigned)ch, (unsigned)(name - start));
 	} while (*++name);
+
+	/* The minimum size of the login name is one char or two if
+	 * last char is the '$'. Violations of this are caught above.
+	 * The maximum size of the login name is LOGIN_NAME_MAX
+	 * including the terminating null byte.
+	 */
+	if (name - start >= LOGIN_NAME_MAX)
+		bb_error_msg_and_die("name is too long");
 }