aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Lewis <martin.lewis.x84@gmail.com>2020-08-04 17:27:16 -0500
committerDenys Vlasenko <vda.linux@googlemail.com>2020-08-13 16:48:07 +0200
commit9914d8b861a0edb42051bd68a37bceb0562daa70 (patch)
tree0dcf241083f2c6258ad0506cf26a12f72c27886f
parent8a485b0a363935309f976866b8a30988362fadc0 (diff)
downloadbusybox-w32-9914d8b861a0edb42051bd68a37bceb0562daa70.tar.gz
busybox-w32-9914d8b861a0edb42051bd68a37bceb0562daa70.tar.bz2
busybox-w32-9914d8b861a0edb42051bd68a37bceb0562daa70.zip
udhcpc: add support for long options
Duplicate options are currently overridden (only the last option is kept). This leads to unexpected behavior when using long options. The patch adds support for long options in compliance with RFC 3396. Fixes #13136. function old new delta udhcp_run_script 601 725 +124 optitem_unset_env_and_free - 38 +38 putenvp 46 59 +13 static.xmalloc_optname_optval 718 717 -1 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 2/1 up/down: 175/-1) Total: 174 bytes Signed-off-by: Martin Lewis <martin.lewis.x84@gmail.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--networking/udhcp/dhcpc.c138
-rw-r--r--networking/udhcp/dhcpc.h1
2 files changed, 104 insertions, 35 deletions
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
index 50dfead63..e13eb3f9f 100644
--- a/networking/udhcp/dhcpc.c
+++ b/networking/udhcp/dhcpc.c
@@ -115,6 +115,13 @@ enum {
115 115
116 116
117/*** Script execution code ***/ 117/*** Script execution code ***/
118struct dhcp_optitem {
119 unsigned len;
120 uint8_t code;
121 uint8_t malloced;
122 uint8_t *data;
123 char *env;
124};
118 125
119/* get a rough idea of how long an option will be (rounding up...) */ 126/* get a rough idea of how long an option will be (rounding up...) */
120static const uint8_t len_of_option_as_string[] ALIGN1 = { 127static const uint8_t len_of_option_as_string[] ALIGN1 = {
@@ -186,15 +193,15 @@ static int good_hostname(const char *name)
186#endif 193#endif
187 194
188/* Create "opt_name=opt_value" string */ 195/* Create "opt_name=opt_value" string */
189static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_optflag *optflag, const char *opt_name) 196static NOINLINE char *xmalloc_optname_optval(const struct dhcp_optitem *opt_item, const struct dhcp_optflag *optflag, const char *opt_name)
190{ 197{
191 unsigned upper_length; 198 unsigned upper_length;
192 int len, type, optlen; 199 int len, type, optlen;
193 char *dest, *ret; 200 char *dest, *ret;
201 uint8_t *option;
194 202
195 /* option points to OPT_DATA, need to go back to get OPT_LEN */ 203 option = opt_item->data;
196 len = option[-OPT_DATA + OPT_LEN]; 204 len = opt_item->len;
197
198 type = optflag->flags & OPTION_TYPE_MASK; 205 type = optflag->flags & OPTION_TYPE_MASK;
199 optlen = dhcp_option_lengths[type]; 206 optlen = dhcp_option_lengths[type];
200 upper_length = len_of_option_as_string[type] 207 upper_length = len_of_option_as_string[type]
@@ -386,11 +393,70 @@ static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_
386 return ret; 393 return ret;
387} 394}
388 395
389static void putenvp(llist_t **envp, char *new_opt) 396static void optitem_unset_env_and_free(void *item)
390{ 397{
391 putenv(new_opt); 398 struct dhcp_optitem *opt_item = item;
399 bb_unsetenv_and_free(opt_item->env);
400 if (opt_item->malloced)
401 free(opt_item->data);
402 free(opt_item);
403}
404
405/* Used by static options (interface, siaddr, etc) */
406static void putenvp(char *new_opt)
407{
408 struct dhcp_optitem *opt_item;
409
410 opt_item = xzalloc(sizeof(*opt_item));
411 /* opt_item->code = 0, so it won't appear in concat_option's lookup */
412 /* opt_item->malloced = 0 */
413 /* opt_item->data = NULL */
414 opt_item->env = new_opt;
415 llist_add_to(&client_data.envp, opt_item);
392 log2(" %s", new_opt); 416 log2(" %s", new_opt);
393 llist_add_to(envp, new_opt); 417 putenv(new_opt);
418}
419
420/* Support RFC3396 Long Encoded Options */
421static struct dhcp_optitem *concat_option(uint8_t *data, uint8_t len, uint8_t code)
422{
423 llist_t *item;
424 struct dhcp_optitem *opt_item;
425
426 /* Check if an option with the code already exists.
427 * A possible optimization is to create a bitmap of all existing options in the packet,
428 * and iterate over the option list only if they exist.
429 * This will result in bigger code, and because dhcp packets don't have too many options it
430 * shouldn't have a big impact on performance.
431 */
432 for (item = client_data.envp; item != NULL; item = item->link) {
433 opt_item = (struct dhcp_optitem *)item->data;
434 if (opt_item->code == code) {
435 /* This option was seen already, concatenate */
436 uint8_t *new_data;
437
438 new_data = xmalloc(len + opt_item->len);
439 memcpy(
440 mempcpy(new_data, opt_item->data, opt_item->len),
441 data, len
442 );
443 opt_item->len += len;
444 if (opt_item->malloced)
445 free(opt_item->data);
446 opt_item->malloced = 1;
447 opt_item->data = new_data;
448 return opt_item;
449 }
450 }
451
452 /* This is a new option, add a new dhcp_optitem to the list */
453 opt_item = xzalloc(sizeof(*opt_item));
454 opt_item->code = code;
455 /* opt_item->malloced = 0 */
456 opt_item->data = data;
457 opt_item->len = len;
458 llist_add_to(&client_data.envp, opt_item);
459 return opt_item;
394} 460}
395 461
396static const char* get_optname(uint8_t code, const struct dhcp_optflag **dh) 462static const char* get_optname(uint8_t code, const struct dhcp_optflag **dh)
@@ -403,7 +469,7 @@ static const char* get_optname(uint8_t code, const struct dhcp_optflag **dh)
403 * and they'll count as unknown options. 469 * and they'll count as unknown options.
404 */ 470 */
405 for (*dh = dhcp_optflags; (*dh)->code && (*dh)->code < code; (*dh)++) 471 for (*dh = dhcp_optflags; (*dh)->code && (*dh)->code < code; (*dh)++)
406 continue; 472 continue;
407 473
408 if ((*dh)->code == code) 474 if ((*dh)->code == code)
409 return nth_string(dhcp_option_strings, (*dh - dhcp_optflags)); 475 return nth_string(dhcp_option_strings, (*dh - dhcp_optflags));
@@ -412,50 +478,54 @@ static const char* get_optname(uint8_t code, const struct dhcp_optflag **dh)
412} 478}
413 479
414/* put all the parameters into the environment */ 480/* put all the parameters into the environment */
415static llist_t *fill_envp(struct dhcp_packet *packet) 481static void fill_envp(struct dhcp_packet *packet)
416{ 482{
417 uint8_t *optptr; 483 uint8_t *optptr;
418 struct dhcp_scan_state scan_state; 484 struct dhcp_scan_state scan_state;
419 char *new_opt; 485 char *new_opt;
420 llist_t *envp = NULL;
421 486
422 putenvp(&envp, xasprintf("interface=%s", client_data.interface)); 487 putenvp(xasprintf("interface=%s", client_data.interface));
423 488
424 if (!packet) 489 if (!packet)
425 return envp; 490 return;
426 491
427 init_scan_state(packet, &scan_state); 492 init_scan_state(packet, &scan_state);
428 493
429 /* Iterate over the packet options. 494 /* Iterate over the packet options.
430 * Handle each option based on whether it's an unknown / known option. 495 * Handle each option based on whether it's an unknown / known option.
431 * There may be (although unlikely) duplicate options. For now, only the last 496 * Long options are supported in compliance with RFC 3396.
432 * appearing option will be stored in the environment, and all duplicates
433 * are freed properly.
434 * Long options may be implemented in the future (see RFC 3396) if needed.
435 */ 497 */
436 while ((optptr = udhcp_scan_options(packet, &scan_state)) != NULL) { 498 while ((optptr = udhcp_scan_options(packet, &scan_state)) != NULL) {
437 const struct dhcp_optflag *dh; 499 const struct dhcp_optflag *dh;
438 const char *opt_name; 500 const char *opt_name;
501 struct dhcp_optitem *opt_item;
439 uint8_t code = optptr[OPT_CODE]; 502 uint8_t code = optptr[OPT_CODE];
440 uint8_t len = optptr[OPT_LEN]; 503 uint8_t len = optptr[OPT_LEN];
441 uint8_t *data = optptr + OPT_DATA; 504 uint8_t *data = optptr + OPT_DATA;
442 505
506 opt_item = concat_option(data, len, code);
443 opt_name = get_optname(code, &dh); 507 opt_name = get_optname(code, &dh);
444 if (opt_name) { 508 if (opt_name) {
445 new_opt = xmalloc_optname_optval(data, dh, opt_name); 509 new_opt = xmalloc_optname_optval(opt_item, dh, opt_name);
446 if (code == DHCP_SUBNET && len == 4) { 510 if (opt_item->code == DHCP_SUBNET && opt_item->len == 4) {
511 /* Generate extra envvar for DHCP_SUBNET, $mask */
447 uint32_t subnet; 512 uint32_t subnet;
448 putenvp(&envp, new_opt); 513 move_from_unaligned32(subnet, opt_item->data);
449 move_from_unaligned32(subnet, data); 514 putenvp(xasprintf("mask=%u", mton(subnet)));
450 new_opt = xasprintf("mask=%u", mton(subnet));
451 } 515 }
452 } else { 516 } else {
453 unsigned ofs; 517 unsigned ofs;
454 new_opt = xmalloc(sizeof("optNNN=") + 1 + len*2); 518 new_opt = xmalloc(sizeof("optNNN=") + 1 + opt_item->len*2);
455 ofs = sprintf(new_opt, "opt%u=", code); 519 ofs = sprintf(new_opt, "opt%u=", opt_item->code);
456 bin2hex(new_opt + ofs, (char *)data, len)[0] = '\0'; 520 bin2hex(new_opt + ofs, (char *)opt_item->data, opt_item->len)[0] = '\0';
457 } 521 }
458 putenvp(&envp, new_opt); 522 log2(" %s", new_opt);
523 putenv(new_opt);
524 /* putenv will replace the existing environment variable in case of a duplicate.
525 * Free the previous occurrence (NULL if it's the first one).
526 */
527 free(opt_item->env);
528 opt_item->env = new_opt;
459 } 529 }
460 530
461 /* Export BOOTP fields. Fields we don't (yet?) export: 531 /* Export BOOTP fields. Fields we don't (yet?) export:
@@ -473,41 +543,38 @@ static llist_t *fill_envp(struct dhcp_packet *packet)
473 /* Most important one: yiaddr as $ip */ 543 /* Most important one: yiaddr as $ip */
474 new_opt = xmalloc(sizeof("ip=255.255.255.255")); 544 new_opt = xmalloc(sizeof("ip=255.255.255.255"));
475 sprint_nip(new_opt, "ip=", (uint8_t *) &packet->yiaddr); 545 sprint_nip(new_opt, "ip=", (uint8_t *) &packet->yiaddr);
476 putenvp(&envp, new_opt); 546 putenvp(new_opt);
477 547
478 if (packet->siaddr_nip) { 548 if (packet->siaddr_nip) {
479 /* IP address of next server to use in bootstrap */ 549 /* IP address of next server to use in bootstrap */
480 new_opt = xmalloc(sizeof("siaddr=255.255.255.255")); 550 new_opt = xmalloc(sizeof("siaddr=255.255.255.255"));
481 sprint_nip(new_opt, "siaddr=", (uint8_t *) &packet->siaddr_nip); 551 sprint_nip(new_opt, "siaddr=", (uint8_t *) &packet->siaddr_nip);
482 putenvp(&envp, new_opt); 552 putenvp(new_opt);
483 } 553 }
484 if (packet->gateway_nip) { 554 if (packet->gateway_nip) {
485 /* IP address of DHCP relay agent */ 555 /* IP address of DHCP relay agent */
486 new_opt = xmalloc(sizeof("giaddr=255.255.255.255")); 556 new_opt = xmalloc(sizeof("giaddr=255.255.255.255"));
487 sprint_nip(new_opt, "giaddr=", (uint8_t *) &packet->gateway_nip); 557 sprint_nip(new_opt, "giaddr=", (uint8_t *) &packet->gateway_nip);
488 putenvp(&envp, new_opt); 558 putenvp(new_opt);
489 } 559 }
490 if (!(scan_state.overload & FILE_FIELD) && packet->file[0]) { 560 if (!(scan_state.overload & FILE_FIELD) && packet->file[0]) {
491 /* watch out for invalid packets */ 561 /* watch out for invalid packets */
492 new_opt = xasprintf("boot_file=%."DHCP_PKT_FILE_LEN_STR"s", packet->file); 562 new_opt = xasprintf("boot_file=%."DHCP_PKT_FILE_LEN_STR"s", packet->file);
493 putenvp(&envp, new_opt); 563 putenvp(new_opt);
494 } 564 }
495 if (!(scan_state.overload & SNAME_FIELD) && packet->sname[0]) { 565 if (!(scan_state.overload & SNAME_FIELD) && packet->sname[0]) {
496 /* watch out for invalid packets */ 566 /* watch out for invalid packets */
497 new_opt = xasprintf("sname=%."DHCP_PKT_SNAME_LEN_STR"s", packet->sname); 567 new_opt = xasprintf("sname=%."DHCP_PKT_SNAME_LEN_STR"s", packet->sname);
498 putenvp(&envp, new_opt); 568 putenvp(new_opt);
499 } 569 }
500
501 return envp;
502} 570}
503 571
504/* Call a script with a par file and env vars */ 572/* Call a script with a par file and env vars */
505static void udhcp_run_script(struct dhcp_packet *packet, const char *name) 573static void udhcp_run_script(struct dhcp_packet *packet, const char *name)
506{ 574{
507 llist_t *envp;
508 char *argv[3]; 575 char *argv[3];
509 576
510 envp = fill_envp(packet); 577 fill_envp(packet);
511 578
512 /* call script */ 579 /* call script */
513 log1("executing %s %s", client_data.script, name); 580 log1("executing %s %s", client_data.script, name);
@@ -517,7 +584,8 @@ static void udhcp_run_script(struct dhcp_packet *packet, const char *name)
517 spawn_and_wait(argv); 584 spawn_and_wait(argv);
518 585
519 /* Free all allocated environment variables */ 586 /* Free all allocated environment variables */
520 llist_free(envp, (void (*)(void *))bb_unsetenv_and_free); 587 llist_free(client_data.envp, optitem_unset_env_and_free);
588 client_data.envp = NULL;
521} 589}
522 590
523 591
diff --git a/networking/udhcp/dhcpc.h b/networking/udhcp/dhcpc.h
index b407a6cdb..7ad01ea8f 100644
--- a/networking/udhcp/dhcpc.h
+++ b/networking/udhcp/dhcpc.h
@@ -21,6 +21,7 @@ struct client_data_t {
21 uint8_t *vendorclass; /* Optional vendor class-id to use */ 21 uint8_t *vendorclass; /* Optional vendor class-id to use */
22 uint8_t *hostname; /* Optional hostname to use */ 22 uint8_t *hostname; /* Optional hostname to use */
23 uint8_t *fqdn; /* Optional fully qualified domain name to use */ 23 uint8_t *fqdn; /* Optional fully qualified domain name to use */
24 llist_t *envp; /* list of DHCP options used for env vars */
24 25
25 unsigned first_secs; 26 unsigned first_secs;
26 unsigned last_secs; 27 unsigned last_secs;