udhcp: support string user options, closes 10946

function                                             old     new   delta
udhcp_str2optset                                     536     628     +92
packed_usage                                       32757   32760      +3
udhcpc_main                                         2708    2692     -16
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/1 up/down: 95/-16)             Total: 79 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c
index d3eea5d..fbf9c68 100644
--- a/networking/udhcp/common.c
+++ b/networking/udhcp/common.c
@@ -378,23 +378,6 @@
  * Called to parse "udhcpc -x OPTNAME:OPTVAL"
  * and to parse udhcpd.conf's "opt OPTNAME OPTVAL" directives.
  */
-/* helper for the helper */
-static char *allocate_tempopt_if_needed(
-		const struct dhcp_optflag *optflag,
-		char *buffer,
-		int *length_p)
-{
-	char *allocated = NULL;
-	if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_BIN) {
-		const char *end;
-		allocated = xstrdup(buffer); /* more than enough */
-		end = hex2bin(allocated, buffer, 255);
-		if (errno)
-			bb_error_msg_and_die("malformed hex string '%s'", buffer);
-		*length_p = end - allocated;
-	}
-	return allocated;
-}
 /* helper: add an option to the opt_list */
 static NOINLINE void attach_option(
 		struct option_set **opt_list,
@@ -403,9 +386,16 @@
 		int length)
 {
 	struct option_set *existing;
-	char *allocated;
+	char *allocated = NULL;
 
-	allocated = allocate_tempopt_if_needed(optflag, buffer, &length);
+	if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_BIN) {
+		const char *end;
+		allocated = xstrdup(buffer); /* more than enough */
+		end = hex2bin(allocated, buffer, 255);
+		if (errno)
+			bb_error_msg_and_die("malformed hex string '%s'", buffer);
+		length = end - allocated;
+	}
 #if ENABLE_FEATURE_UDHCP_RFC3397
 	if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_DNS_STRING) {
 		/* reuse buffer and length for RFC1035-formatted string */
@@ -463,12 +453,12 @@
 int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dhcp_optflag *optflags, const char *option_strings)
 {
 	struct option_set **opt_list = arg;
-	char *opt, *val;
+	char *opt;
 	char *str;
 	const struct dhcp_optflag *optflag;
-	struct dhcp_optflag bin_optflag;
+	struct dhcp_optflag userdef_optflag;
 	unsigned optcode;
-	int retval, length;
+	int retval;
 	/* IP_PAIR needs 8 bytes, STATIC_ROUTES needs 9 max */
 	char buffer[9] ALIGNED(4);
 	uint16_t *result_u16 = (uint16_t *) buffer;
@@ -476,28 +466,40 @@
 
 	/* Cheat, the only *const* str possible is "" */
 	str = (char *) const_str;
-	opt = strtok(str, " \t=");
+	opt = strtok(str, " \t=:");
 	if (!opt)
 		return 0;
 
 	optcode = bb_strtou(opt, NULL, 0);
 	if (!errno && optcode < 255) {
-		/* Raw (numeric) option code */
-		bin_optflag.flags = OPTION_BIN;
-		bin_optflag.code = optcode;
-		optflag = &bin_optflag;
+		/* Raw (numeric) option code.
+		 * Initially assume binary (hex-str), but if "str" or 'str'
+		 * is seen later, switch to STRING.
+		 */
+		userdef_optflag.flags = OPTION_BIN;
+		userdef_optflag.code = optcode;
+		optflag = &userdef_optflag;
 	} else {
 		optflag = &optflags[udhcp_option_idx(opt, option_strings)];
 	}
 
+	/* Loop to handle OPTION_LIST case, else execute just once */
 	retval = 0;
 	do {
-		val = strtok(NULL, ", \t");
+		int length;
+		char *val;
+
+		if (optflag->flags == OPTION_BIN)
+			val = trim(strtok(NULL, "")); /* do not split "'q w e'" */
+		else
+			val = strtok(NULL, ", \t");
 		if (!val)
 			break;
+
 		length = dhcp_option_lengths[optflag->flags & OPTION_TYPE_MASK];
 		retval = 0;
 		opt = buffer; /* new meaning for variable opt */
+
 		switch (optflag->flags & OPTION_TYPE_MASK) {
 		case OPTION_IP:
 			retval = udhcp_str2nip(val, buffer);
@@ -510,6 +512,7 @@
 			if (retval)
 				retval = udhcp_str2nip(val, buffer + 4);
 			break;
+case_OPTION_STRING:
 		case OPTION_STRING:
 		case OPTION_STRING_HOST:
 #if ENABLE_FEATURE_UDHCP_RFC3397
@@ -577,12 +580,26 @@
 			}
 			break;
 		}
-		case OPTION_BIN: /* handled in attach_option() */
+		case OPTION_BIN:
+			/* Raw (numeric) option code. Is it a string? */
+			if (val[0] == '"' || val[0] == '\'') {
+				char delim = val[0];
+				char *end = last_char_is(val + 1, delim);
+				if (end) {
+					*end = '\0';
+					val++;
+					userdef_optflag.flags = OPTION_STRING;
+					goto case_OPTION_STRING;
+				}
+			}
+			/* No: hex-str option, handled in attach_option() */
 			opt = val;
 			retval = 1;
+			break;
 		default:
 			break;
 		}
+
 		if (retval)
 			attach_option(opt_list, optflag, opt, length);
 	} while (retval && (optflag->flags & OPTION_LIST));