aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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;