diff options
Diffstat (limited to 'networking/udhcp/dhcpc.c')
-rw-r--r-- | networking/udhcp/dhcpc.c | 301 |
1 files changed, 113 insertions, 188 deletions
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c index 5a1f8fd7a..50dfead63 100644 --- a/networking/udhcp/dhcpc.c +++ b/networking/udhcp/dhcpc.c | |||
@@ -159,61 +159,27 @@ static int mton(uint32_t mask) | |||
159 | } | 159 | } |
160 | 160 | ||
161 | #if ENABLE_FEATURE_UDHCPC_SANITIZEOPT | 161 | #if ENABLE_FEATURE_UDHCPC_SANITIZEOPT |
162 | /* Check if a given label represents a valid DNS label | 162 | /* Check if a given name represents a valid DNS name */ |
163 | * Return pointer to the first character after the label | 163 | /* See RFC1035, 2.3.1 */ |
164 | * (NUL or dot) upon success, NULL otherwise. | ||
165 | * See RFC1035, 2.3.1 | ||
166 | */ | ||
167 | /* We don't need to be particularly anal. For example, allowing _, hyphen | 164 | /* We don't need to be particularly anal. For example, allowing _, hyphen |
168 | * at the end, or leading and trailing dots would be ok, since it | 165 | * at the end, or leading and trailing dots would be ok, since it |
169 | * can't be used for attacks. (Leading hyphen can be, if someone uses | 166 | * can't be used for attacks. (Leading hyphen can be, if someone uses cmd "$hostname" |
170 | * cmd "$hostname" | ||
171 | * in the script: then hostname may be treated as an option) | 167 | * in the script: then hostname may be treated as an option) |
172 | */ | 168 | */ |
173 | static const char *valid_domain_label(const char *label) | ||
174 | { | ||
175 | unsigned char ch; | ||
176 | //unsigned pos = 0; | ||
177 | |||
178 | if (label[0] == '-') | ||
179 | return NULL; | ||
180 | for (;;) { | ||
181 | ch = *label; | ||
182 | if ((ch|0x20) < 'a' || (ch|0x20) > 'z') { | ||
183 | if (ch < '0' || ch > '9') { | ||
184 | if (ch == '\0' || ch == '.') | ||
185 | return label; | ||
186 | /* DNS allows only '-', but we are more permissive */ | ||
187 | if (ch != '-' && ch != '_') | ||
188 | return NULL; | ||
189 | } | ||
190 | } | ||
191 | label++; | ||
192 | //pos++; | ||
193 | //Do we want this? | ||
194 | //if (pos > 63) /* NS_MAXLABEL; labels must be 63 chars or less */ | ||
195 | // return NULL; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | /* Check if a given name represents a valid DNS name */ | ||
200 | /* See RFC1035, 2.3.1 */ | ||
201 | static int good_hostname(const char *name) | 169 | static int good_hostname(const char *name) |
202 | { | 170 | { |
203 | //const char *start = name; | 171 | if (*name == '-') /* Can't start with '-' */ |
204 | 172 | return 0; | |
205 | for (;;) { | 173 | |
206 | name = valid_domain_label(name); | 174 | while (*name) { |
207 | if (!name) | 175 | unsigned char ch = *name++; |
208 | return 0; | 176 | if (!isalnum(ch)) |
209 | if (!name[0]) | 177 | /* DNS allows only '-', but we are more permissive */ |
210 | return 1; | 178 | if (ch != '-' && ch != '_' && ch != '.') |
211 | //Do we want this? | 179 | return 0; |
212 | //return ((name - start) < 1025); /* NS_MAXDNAME */ | 180 | // TODO: do we want to validate lengths against NS_MAXLABEL and NS_MAXDNAME? |
213 | name++; | ||
214 | if (*name == '\0') | ||
215 | return 1; // We allow trailing dot too | ||
216 | } | 181 | } |
182 | return 1; | ||
217 | } | 183 | } |
218 | #else | 184 | #else |
219 | # define good_hostname(name) 1 | 185 | # define good_hostname(name) 1 |
@@ -242,9 +208,8 @@ static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_ | |||
242 | case OPTION_IP: | 208 | case OPTION_IP: |
243 | case OPTION_IP_PAIR: | 209 | case OPTION_IP_PAIR: |
244 | dest += sprint_nip(dest, "", option); | 210 | dest += sprint_nip(dest, "", option); |
245 | if (type == OPTION_IP) | 211 | if (type == OPTION_IP_PAIR) |
246 | break; | 212 | dest += sprint_nip(dest, "/", option + 4); |
247 | dest += sprint_nip(dest, "/", option + 4); | ||
248 | break; | 213 | break; |
249 | // case OPTION_BOOLEAN: | 214 | // case OPTION_BOOLEAN: |
250 | // dest += sprintf(dest, *option ? "yes" : "no"); | 215 | // dest += sprintf(dest, *option ? "yes" : "no"); |
@@ -346,7 +311,7 @@ static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_ | |||
346 | * IPv4MaskLen <= 32, | 311 | * IPv4MaskLen <= 32, |
347 | * 6rdPrefixLen <= 128, | 312 | * 6rdPrefixLen <= 128, |
348 | * 6rdPrefixLen + (32 - IPv4MaskLen) <= 128 | 313 | * 6rdPrefixLen + (32 - IPv4MaskLen) <= 128 |
349 | * (2nd condition need no check - it follows from 1st and 3rd). | 314 | * (2nd condition needs no check - it follows from 1st and 3rd). |
350 | * Else, return envvar with empty value ("optname=") | 315 | * Else, return envvar with empty value ("optname=") |
351 | */ | 316 | */ |
352 | if (len >= (1 + 1 + 16 + 4) | 317 | if (len >= (1 + 1 + 16 + 4) |
@@ -360,17 +325,12 @@ static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_ | |||
360 | /* 6rdPrefix */ | 325 | /* 6rdPrefix */ |
361 | dest += sprint_nip6(dest, /* "", */ option); | 326 | dest += sprint_nip6(dest, /* "", */ option); |
362 | option += 16; | 327 | option += 16; |
363 | len -= 1 + 1 + 16 + 4; | 328 | len -= 1 + 1 + 16; |
364 | /* "+ 4" above corresponds to the length of IPv4 addr | 329 | *dest++ = ' '; |
365 | * we consume in the loop below */ | 330 | /* 6rdBRIPv4Address(es), use common IPv4 logic to process them */ |
366 | while (1) { | 331 | type = OPTION_IP; |
367 | /* 6rdBRIPv4Address(es) */ | 332 | optlen = 4; |
368 | dest += sprint_nip(dest, " ", option); | 333 | continue; |
369 | option += 4; | ||
370 | len -= 4; /* do we have yet another 4+ bytes? */ | ||
371 | if (len < 0) | ||
372 | break; /* no */ | ||
373 | } | ||
374 | } | 334 | } |
375 | 335 | ||
376 | return ret; | 336 | return ret; |
@@ -392,23 +352,18 @@ static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_ | |||
392 | */ | 352 | */ |
393 | option++; | 353 | option++; |
394 | len--; | 354 | len--; |
355 | if (option[-1] == 1) { | ||
356 | /* use common IPv4 logic to process IP addrs */ | ||
357 | type = OPTION_IP; | ||
358 | optlen = 4; | ||
359 | continue; | ||
360 | } | ||
395 | if (option[-1] == 0) { | 361 | if (option[-1] == 0) { |
396 | dest = dname_dec(option, len, ret); | 362 | dest = dname_dec(option, len, ret); |
397 | if (dest) { | 363 | if (dest) { |
398 | free(ret); | 364 | free(ret); |
399 | return dest; | 365 | return dest; |
400 | } | 366 | } |
401 | } else | ||
402 | if (option[-1] == 1) { | ||
403 | const char *pfx = ""; | ||
404 | while (1) { | ||
405 | len -= 4; | ||
406 | if (len < 0) | ||
407 | break; | ||
408 | dest += sprint_nip(dest, pfx, option); | ||
409 | pfx = " "; | ||
410 | option += 4; | ||
411 | } | ||
412 | } | 367 | } |
413 | return ret; | 368 | return ret; |
414 | #endif | 369 | #endif |
@@ -431,59 +386,78 @@ static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_ | |||
431 | return ret; | 386 | return ret; |
432 | } | 387 | } |
433 | 388 | ||
434 | /* put all the parameters into the environment */ | 389 | static void putenvp(llist_t **envp, char *new_opt) |
435 | static char **fill_envp(struct dhcp_packet *packet) | 390 | { |
391 | putenv(new_opt); | ||
392 | log2(" %s", new_opt); | ||
393 | llist_add_to(envp, new_opt); | ||
394 | } | ||
395 | |||
396 | static const char* get_optname(uint8_t code, const struct dhcp_optflag **dh) | ||
436 | { | 397 | { |
437 | int envc; | 398 | /* Find the option: |
438 | int i; | 399 | * dhcp_optflags is sorted so we stop searching when dh->code >= code, which is faster |
439 | char **envp, **curr; | 400 | * than iterating over the entire array. |
440 | const char *opt_name; | 401 | * Options which don't have a match in dhcp_option_strings[], e.g DHCP_REQUESTED_IP, |
441 | uint8_t *temp; | 402 | * are located after the sorted array, so these entries will never be reached |
442 | uint8_t overload = 0; | 403 | * and they'll count as unknown options. |
443 | |||
444 | #define BITMAP unsigned | ||
445 | #define BBITS (sizeof(BITMAP) * 8) | ||
446 | #define BMASK(i) (1 << (i & (sizeof(BITMAP) * 8 - 1))) | ||
447 | #define FOUND_OPTS(i) (found_opts[(unsigned)i / BBITS]) | ||
448 | BITMAP found_opts[256 / BBITS]; | ||
449 | |||
450 | memset(found_opts, 0, sizeof(found_opts)); | ||
451 | |||
452 | /* We need 7 elements for: | ||
453 | * "interface=IFACE" | ||
454 | * "ip=N.N.N.N" from packet->yiaddr | ||
455 | * "giaddr=IP" from packet->gateway_nip (unless 0) | ||
456 | * "siaddr=IP" from packet->siaddr_nip (unless 0) | ||
457 | * "boot_file=FILE" from packet->file (unless overloaded) | ||
458 | * "sname=SERVER_HOSTNAME" from packet->sname (unless overloaded) | ||
459 | * terminating NULL | ||
460 | */ | 404 | */ |
461 | envc = 7; | 405 | for (*dh = dhcp_optflags; (*dh)->code && (*dh)->code < code; (*dh)++) |
462 | /* +1 element for each option, +2 for subnet option: */ | 406 | continue; |
463 | if (packet) { | 407 | |
464 | /* note: do not search for "pad" (0) and "end" (255) options */ | 408 | if ((*dh)->code == code) |
465 | //TODO: change logic to scan packet _once_ | 409 | return nth_string(dhcp_option_strings, (*dh - dhcp_optflags)); |
466 | for (i = 1; i < 255; i++) { | 410 | |
467 | temp = udhcp_get_option(packet, i); | 411 | return NULL; |
468 | if (temp) { | 412 | } |
469 | if (i == DHCP_OPTION_OVERLOAD) | 413 | |
470 | overload |= *temp; | 414 | /* put all the parameters into the environment */ |
471 | else if (i == DHCP_SUBNET) | 415 | static llist_t *fill_envp(struct dhcp_packet *packet) |
472 | envc++; /* for $mask */ | 416 | { |
473 | envc++; | 417 | uint8_t *optptr; |
474 | /*if (i != DHCP_MESSAGE_TYPE)*/ | 418 | struct dhcp_scan_state scan_state; |
475 | FOUND_OPTS(i) |= BMASK(i); | 419 | char *new_opt; |
476 | } | 420 | llist_t *envp = NULL; |
477 | } | ||
478 | } | ||
479 | curr = envp = xzalloc(sizeof(envp[0]) * envc); | ||
480 | 421 | ||
481 | *curr = xasprintf("interface=%s", client_data.interface); | 422 | putenvp(&envp, xasprintf("interface=%s", client_data.interface)); |
482 | putenv(*curr++); | ||
483 | 423 | ||
484 | if (!packet) | 424 | if (!packet) |
485 | return envp; | 425 | return envp; |
486 | 426 | ||
427 | init_scan_state(packet, &scan_state); | ||
428 | |||
429 | /* Iterate over the packet options. | ||
430 | * 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 | ||
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 | */ | ||
436 | while ((optptr = udhcp_scan_options(packet, &scan_state)) != NULL) { | ||
437 | const struct dhcp_optflag *dh; | ||
438 | const char *opt_name; | ||
439 | uint8_t code = optptr[OPT_CODE]; | ||
440 | uint8_t len = optptr[OPT_LEN]; | ||
441 | uint8_t *data = optptr + OPT_DATA; | ||
442 | |||
443 | opt_name = get_optname(code, &dh); | ||
444 | if (opt_name) { | ||
445 | new_opt = xmalloc_optname_optval(data, dh, opt_name); | ||
446 | if (code == DHCP_SUBNET && len == 4) { | ||
447 | uint32_t subnet; | ||
448 | putenvp(&envp, new_opt); | ||
449 | move_from_unaligned32(subnet, data); | ||
450 | new_opt = xasprintf("mask=%u", mton(subnet)); | ||
451 | } | ||
452 | } else { | ||
453 | unsigned ofs; | ||
454 | new_opt = xmalloc(sizeof("optNNN=") + 1 + len*2); | ||
455 | ofs = sprintf(new_opt, "opt%u=", code); | ||
456 | bin2hex(new_opt + ofs, (char *)data, len)[0] = '\0'; | ||
457 | } | ||
458 | putenvp(&envp, new_opt); | ||
459 | } | ||
460 | |||
487 | /* Export BOOTP fields. Fields we don't (yet?) export: | 461 | /* Export BOOTP fields. Fields we don't (yet?) export: |
488 | * uint8_t op; // always BOOTREPLY | 462 | * uint8_t op; // always BOOTREPLY |
489 | * uint8_t htype; // hardware address type. 1 = 10mb ethernet | 463 | * uint8_t htype; // hardware address type. 1 = 10mb ethernet |
@@ -497,77 +471,31 @@ static char **fill_envp(struct dhcp_packet *packet) | |||
497 | * uint8_t chaddr[16]; // link-layer client hardware address (MAC) | 471 | * uint8_t chaddr[16]; // link-layer client hardware address (MAC) |
498 | */ | 472 | */ |
499 | /* Most important one: yiaddr as $ip */ | 473 | /* Most important one: yiaddr as $ip */ |
500 | *curr = xmalloc(sizeof("ip=255.255.255.255")); | 474 | new_opt = xmalloc(sizeof("ip=255.255.255.255")); |
501 | sprint_nip(*curr, "ip=", (uint8_t *) &packet->yiaddr); | 475 | sprint_nip(new_opt, "ip=", (uint8_t *) &packet->yiaddr); |
502 | putenv(*curr++); | 476 | putenvp(&envp, new_opt); |
477 | |||
503 | if (packet->siaddr_nip) { | 478 | if (packet->siaddr_nip) { |
504 | /* IP address of next server to use in bootstrap */ | 479 | /* IP address of next server to use in bootstrap */ |
505 | *curr = xmalloc(sizeof("siaddr=255.255.255.255")); | 480 | new_opt = xmalloc(sizeof("siaddr=255.255.255.255")); |
506 | sprint_nip(*curr, "siaddr=", (uint8_t *) &packet->siaddr_nip); | 481 | sprint_nip(new_opt, "siaddr=", (uint8_t *) &packet->siaddr_nip); |
507 | putenv(*curr++); | 482 | putenvp(&envp, new_opt); |
508 | } | 483 | } |
509 | if (packet->gateway_nip) { | 484 | if (packet->gateway_nip) { |
510 | /* IP address of DHCP relay agent */ | 485 | /* IP address of DHCP relay agent */ |
511 | *curr = xmalloc(sizeof("giaddr=255.255.255.255")); | 486 | new_opt = xmalloc(sizeof("giaddr=255.255.255.255")); |
512 | sprint_nip(*curr, "giaddr=", (uint8_t *) &packet->gateway_nip); | 487 | sprint_nip(new_opt, "giaddr=", (uint8_t *) &packet->gateway_nip); |
513 | putenv(*curr++); | 488 | putenvp(&envp, new_opt); |
514 | } | 489 | } |
515 | if (!(overload & FILE_FIELD) && packet->file[0]) { | 490 | if (!(scan_state.overload & FILE_FIELD) && packet->file[0]) { |
516 | /* watch out for invalid packets */ | 491 | /* watch out for invalid packets */ |
517 | *curr = xasprintf("boot_file=%."DHCP_PKT_FILE_LEN_STR"s", packet->file); | 492 | new_opt = xasprintf("boot_file=%."DHCP_PKT_FILE_LEN_STR"s", packet->file); |
518 | putenv(*curr++); | 493 | putenvp(&envp, new_opt); |
519 | } | 494 | } |
520 | if (!(overload & SNAME_FIELD) && packet->sname[0]) { | 495 | if (!(scan_state.overload & SNAME_FIELD) && packet->sname[0]) { |
521 | /* watch out for invalid packets */ | 496 | /* watch out for invalid packets */ |
522 | *curr = xasprintf("sname=%."DHCP_PKT_SNAME_LEN_STR"s", packet->sname); | 497 | new_opt = xasprintf("sname=%."DHCP_PKT_SNAME_LEN_STR"s", packet->sname); |
523 | putenv(*curr++); | 498 | putenvp(&envp, new_opt); |
524 | } | ||
525 | |||
526 | /* Export known DHCP options */ | ||
527 | opt_name = dhcp_option_strings; | ||
528 | i = 0; | ||
529 | while (*opt_name) { | ||
530 | uint8_t code = dhcp_optflags[i].code; | ||
531 | BITMAP *found_ptr = &FOUND_OPTS(code); | ||
532 | BITMAP found_mask = BMASK(code); | ||
533 | if (!(*found_ptr & found_mask)) | ||
534 | goto next; | ||
535 | *found_ptr &= ~found_mask; /* leave only unknown options */ | ||
536 | temp = udhcp_get_option(packet, code); | ||
537 | *curr = xmalloc_optname_optval(temp, &dhcp_optflags[i], opt_name); | ||
538 | putenv(*curr++); | ||
539 | if (code == DHCP_SUBNET && temp[-OPT_DATA + OPT_LEN] == 4) { | ||
540 | /* Subnet option: make things like "$ip/$mask" possible */ | ||
541 | uint32_t subnet; | ||
542 | move_from_unaligned32(subnet, temp); | ||
543 | *curr = xasprintf("mask=%u", mton(subnet)); | ||
544 | putenv(*curr++); | ||
545 | } | ||
546 | next: | ||
547 | opt_name += strlen(opt_name) + 1; | ||
548 | i++; | ||
549 | } | ||
550 | /* Export unknown options */ | ||
551 | for (i = 0; i < 256;) { | ||
552 | BITMAP bitmap = FOUND_OPTS(i); | ||
553 | if (!bitmap) { | ||
554 | i += BBITS; | ||
555 | continue; | ||
556 | } | ||
557 | if (bitmap & BMASK(i)) { | ||
558 | unsigned len, ofs; | ||
559 | |||
560 | temp = udhcp_get_option(packet, i); | ||
561 | /* udhcp_get_option returns ptr to data portion, | ||
562 | * need to go back to get len | ||
563 | */ | ||
564 | len = temp[-OPT_DATA + OPT_LEN]; | ||
565 | *curr = xmalloc(sizeof("optNNN=") + 1 + len*2); | ||
566 | ofs = sprintf(*curr, "opt%u=", i); | ||
567 | *bin2hex(*curr + ofs, (void*) temp, len) = '\0'; | ||
568 | putenv(*curr++); | ||
569 | } | ||
570 | i++; | ||
571 | } | 499 | } |
572 | 500 | ||
573 | return envp; | 501 | return envp; |
@@ -576,7 +504,7 @@ static char **fill_envp(struct dhcp_packet *packet) | |||
576 | /* Call a script with a par file and env vars */ | 504 | /* Call a script with a par file and env vars */ |
577 | static void udhcp_run_script(struct dhcp_packet *packet, const char *name) | 505 | static void udhcp_run_script(struct dhcp_packet *packet, const char *name) |
578 | { | 506 | { |
579 | char **envp, **curr; | 507 | llist_t *envp; |
580 | char *argv[3]; | 508 | char *argv[3]; |
581 | 509 | ||
582 | envp = fill_envp(packet); | 510 | envp = fill_envp(packet); |
@@ -588,11 +516,8 @@ static void udhcp_run_script(struct dhcp_packet *packet, const char *name) | |||
588 | argv[2] = NULL; | 516 | argv[2] = NULL; |
589 | spawn_and_wait(argv); | 517 | spawn_and_wait(argv); |
590 | 518 | ||
591 | for (curr = envp; *curr; curr++) { | 519 | /* Free all allocated environment variables */ |
592 | log2(" %s", *curr); | 520 | llist_free(envp, (void (*)(void *))bb_unsetenv_and_free); |
593 | bb_unsetenv_and_free(*curr); | ||
594 | } | ||
595 | free(envp); | ||
596 | } | 521 | } |
597 | 522 | ||
598 | 523 | ||