diff options
-rw-r--r-- | networking/udhcp/dhcpc.c | 138 | ||||
-rw-r--r-- | networking/udhcp/dhcpc.h | 1 |
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 ***/ |
118 | struct 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...) */ |
120 | static const uint8_t len_of_option_as_string[] ALIGN1 = { | 127 | static 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 */ |
189 | static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_optflag *optflag, const char *opt_name) | 196 | static 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 | ||
389 | static void putenvp(llist_t **envp, char *new_opt) | 396 | static 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) */ | ||
406 | static 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 */ | ||
421 | static 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 | ||
396 | static const char* get_optname(uint8_t code, const struct dhcp_optflag **dh) | 462 | static 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 */ |
415 | static llist_t *fill_envp(struct dhcp_packet *packet) | 481 | static 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 */ |
505 | static void udhcp_run_script(struct dhcp_packet *packet, const char *name) | 573 | static 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; |