From 29c3de8d0973c66c9c8261c0f3d0deeccfd29994 Mon Sep 17 00:00:00 2001
From: Marius Tomaschewski <mt@suse.de>
Date: Tue, 12 Jun 2012 10:01:10 +0200
Subject: [PATCH] dhcp-4.2.4-parsing-and-printing-options

- Fix some issues in the code for parsing and printing options.
  [ISC-Bugs #27314] - properly parse a zero length option from
  a lease file.
  [ISC-Bugs #22796] - properly determine if we parsed a 16 or 32 bit
  value in evaluate_numeric_expression (extract-int).
  [ISC-Bugs #22625] - properly print options that have several fields
  followed by an array of something for example "fIa"
  [ISC-Bugs #27289] - properly parse options in declarations that have
  several fields followed by an array of something for example "fIa"

  This patch obsoletes the following (bnc#739696) patches:
  - dhclient: parse_option_param: Bad format a
    [complete, upstream corrected version]
  - zero-length option lease parse error in dhclient6
    [upstream accepted variant]
  - properly determine if we parsed a 16 or 32 bit value
    [additional upstream patch]
---
 client/dhclient.c |   19 +++++++++++++---
 common/options.c  |   60 +++++++++++++++++++++++++++++++++++++++++++++-------
 common/parse.c    |   23 ++++++++++++++++---
 common/tables.c   |    7 +++--
 common/tree.c     |   35 ++++++++++++++++++++----------
 5 files changed, 113 insertions(+), 31 deletions(-)

diff --git a/client/dhclient.c b/client/dhclient.c
index f920c64..025337a 100644
--- a/client/dhclient.c
+++ b/client/dhclient.c
@@ -2793,10 +2793,21 @@ void write_lease_option (struct option_cache *oc,
 	}
 	if (evaluate_option_cache (&ds, packet, lease, client_state,
 				   in_options, cfg_options, scope, oc, MDL)) {
-		fprintf(leaseFile, "%soption %s%s%s %s;\n", preamble,
-			name, dot, oc->option->name,
-			pretty_print_option(oc->option, ds.data, ds.len,
-					    1, 1));
+		/* The option name */
+		fprintf(leaseFile, "%soption %s%s%s", preamble,
+			name, dot, oc->option->name);
+
+		/* The option value if there is one */
+		if ((oc->option->format == NULL) ||
+		    (oc->option->format[0] != 'Z')) {
+			fprintf(leaseFile, " %s",
+				pretty_print_option(oc->option, ds.data,
+						    ds.len, 1, 1));
+		}
+
+		/* The closing semi-colon and newline */
+		fprintf(leaseFile, ";\n");
+
 		data_string_forget (&ds, MDL);
 	}
 }
diff --git a/common/options.c b/common/options.c
index bc9bb97..fa27688 100644
--- a/common/options.c
+++ b/common/options.c
@@ -1683,6 +1683,8 @@ const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
 	const unsigned char *dp = data;
 	char comma;
 	unsigned long tval;
+	isc_boolean_t a_array = ISC_FALSE;
+	int len_used;
 
 	if (emit_commas)
 		comma = ',';
@@ -1707,6 +1709,8 @@ const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
 		fmtbuf [l] = option -> format [i];
 		switch (option -> format [i]) {
 		      case 'a':
+			a_array = ISC_TRUE;
+			/* Fall through */
 		      case 'A':
 			--numelem;
 			fmtbuf [l] = 0;
@@ -1735,6 +1739,8 @@ const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
 				hunksize++;
 				comma = ':';
 				numhunk = 0;
+				a_array = ISC_TRUE;
+				hunkinc = 1;
 			}
 			fmtbuf [l + 1] = 0;
 			break;
@@ -1828,13 +1834,34 @@ const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
 		      len - hunksize);
 
 	/* If this is an array, compute its size. */
-	if (!numhunk)
-		numhunk = len / hunksize;
-	/* See if we got an exact number of hunks. */
-	if (numhunk > 0 && numhunk * hunksize < len)
-		log_error ("%s: %d extra bytes at end of array\n",
-		      option -> name,
-		      len - numhunk * hunksize);
+	if (numhunk == 0) {
+		if (a_array == ISC_TRUE) {
+			/*
+			 * It is an 'a' type array - we repeat the
+			 * last format type.  A binary string for 'X'
+			 * is also like this.  hunkinc is the size
+			 * of the last format type and we add 1 to
+			 * cover the entire first record.
+			 */
+			numhunk = ((len - hunksize) / hunkinc) + 1;
+			len_used = hunksize + ((numhunk - 1) * hunkinc);
+		} else {
+			/*
+			 * It is an 'A' type array - we repeat the
+			 * entire record
+			 */
+			numhunk = len / hunksize;
+			len_used = numhunk * hunksize;
+		}
+
+		/* See if we got an exact number of hunks. */
+		if (len_used < len) {
+			log_error ("%s: %d extra bytes at end of array\n",
+				   option -> name,
+				   len - len_used);
+		}
+	}
+
 
 	/* A one-hunk array prints the same as a single hunk. */
 	if (numhunk < 0)
@@ -1842,7 +1869,24 @@ const char *pretty_print_option (option, data, len, emit_commas, emit_quotes)
 
 	/* Cycle through the array (or hunk) printing the data. */
 	for (i = 0; i < numhunk; i++) {
-		for (j = 0; j < numelem; j++) {
+		if ((a_array == ISC_TRUE) && (i != 0) && (numelem > 0)) {
+			/*
+			 * For 'a' type of arrays we repeat
+			 * only the last format character
+			 * We should never hit the case of numelem == 0
+			 * but let's include the check to be safe.
+			 */
+			j = numelem - 1;
+		} else {
+			/*
+			 * for other types of arrays or the first
+			 * time through for 'a' types, we go through
+			 * the entire set of format characters.
+			 */
+			j = 0;
+		}
+
+		for (; j < numelem; j++) {
 			switch (fmtbuf [j]) {
 			      case 't':
 				/* endbuf-1 leaves room for NULL. */
diff --git a/common/parse.c b/common/parse.c
index 434085a..f72c0d6 100644
--- a/common/parse.c
+++ b/common/parse.c
@@ -5518,11 +5518,26 @@ int parse_option_decl (oc, cfile)
 	if (status != ISC_R_SUCCESS || option == NULL)
 		return 0;
 
+	fmt = option->format;
+
 	/* Parse the option data... */
 	do {
-		for (fmt = option -> format; *fmt; fmt++) {
-			if (*fmt == 'A')
+		for (; *fmt; fmt++) {
+			if (*fmt == 'A') {
+				/* 'A' is an array of records, start at
+				 *  the beginning
+				 */
+				fmt = option->format;
+				break;
+			}
+
+			if (*fmt == 'a') {
+				/* 'a' is an array of the last field,
+				 * back up one format character
+				 */
+				fmt--;
 				break;
+			}
 			if (*fmt == 'o' && fmt != option -> format)
 				continue;
 			switch (*fmt) {
@@ -5718,7 +5733,7 @@ int parse_option_decl (oc, cfile)
 				goto alloc;
 
 			      case 'Z':	/* Zero-length option */
-				token = next_token(&val, (unsigned *)0, cfile);
+				token = peek_token(&val, (unsigned *)0, cfile);
 				if (token != SEMI) {
 					parse_warn(cfile,
 						   "semicolon expected.");
@@ -5735,7 +5750,7 @@ int parse_option_decl (oc, cfile)
 			}
 		}
 		token = next_token (&val, (unsigned *)0, cfile);
-	} while (*fmt == 'A' && token == COMMA);
+	} while (*fmt && token == COMMA);
 
 	if (token != SEMI) {
 		parse_warn (cfile, "semicolon expected.");
diff --git a/common/tables.c b/common/tables.c
index c820d83..d224dc1 100644
--- a/common/tables.c
+++ b/common/tables.c
@@ -64,9 +64,10 @@ HASH_FUNCTIONS (option_code, const unsigned *, struct option,
        some event.  The special all-ones value means 'infinite'.  May either
        be printed as a decimal, eg, "3600", or as this name, eg, "infinite".
    f - flag (true or false)
-   A - array of whatever precedes (e.g., IA means array of IP addresses)
-   a - array of the preceding character (e.g., IIa means two or more IP
-       addresses)
+   A - array of all that precedes (e.g., fIA means array of records of
+       a flag and an IP address)
+   a - array of the preceding character (e.g., fIa means a single flag
+       followed by an array of IP addresses)
    U - name of an option space (universe)
    F - implicit flag - the presence of the option indicates that the
        flag is true.
diff --git a/common/tree.c b/common/tree.c
index 3c978b0..7754398 100644
--- a/common/tree.c
+++ b/common/tree.c
@@ -2409,6 +2409,7 @@ int evaluate_numeric_expression (result, packet, lease, client_state,
 	struct binding *binding;
 	struct binding_value *bv;
 	unsigned long ileft, iright;
+	int rc = 0;
 
 	switch (expr -> op) {
 	      case expr_check:
@@ -2477,32 +2478,42 @@ int evaluate_numeric_expression (result, packet, lease, client_state,
 		status = (evaluate_data_expression
 			  (&data, packet, lease, client_state, in_options,
 			   cfg_options, scope, expr -> data.extract_int, MDL));
-		if (status && data.len >= 2)
+		if (status && data.len >= 2) {
 			*result = getUShort (data.data);
+			rc = 1;
+		}
 #if defined (DEBUG_EXPRESSIONS)
-		log_debug ("num: extract_int16 (%s) = %ld",
-		      ((status && data.len >= 2) ?
-		       print_hex_1 (data.len, data.data, 60) : "NULL"),
-		      *result);
+		if (rc == 1) {
+			log_debug ("num: extract_int16 (%s) = %ld",
+				   print_hex_1(data.len, data.data, 60)
+				   *result);
+		} else {
+			log_debug ("num: extract_int16 (NULL) = NULL");
+		}
 #endif
 		if (status) data_string_forget (&data, MDL);
-		return (status && data.len >= 2);
+		return (rc);
 
 	      case expr_extract_int32:
 		memset (&data, 0, sizeof data);
 		status = (evaluate_data_expression
 			  (&data, packet, lease, client_state, in_options,
 			   cfg_options, scope, expr -> data.extract_int, MDL));
-		if (status && data.len >= 4)
+		if (status && data.len >= 4) {
 			*result = getULong (data.data);
+			rc = 1;
+		}
 #if defined (DEBUG_EXPRESSIONS)
-		log_debug ("num: extract_int32 (%s) = %ld",
-		      ((status && data.len >= 4) ?
-		       print_hex_1 (data.len, data.data, 60) : "NULL"),
-		      *result);
+		if (rc == 1) {
+			log_debug ("num: extract_int32 (%s) = %ld",
+				   print_hex_1 (data.len, data.data, 60),
+				   *result);
+		} else {
+			log_debug ("num: extract_int32 (NULL) = NULL");
+		}
 #endif
 		if (status) data_string_forget (&data, MDL);
-		return (status && data.len >= 4);
+		return (rc);
 
 	      case expr_const_int:
 		*result = expr -> data.const_int;
-- 
1.7.7

