/* vi: set sw=4 ts=4: */ /* script.c * * Functions to call the DHCP client notification scripts * * Russ Dill July 2001 * * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. */ #include "common.h" #include "dhcpc.h" #include "options.h" /* get a rough idea of how long an option will be (rounding up...) */ static const uint8_t len_of_option_as_string[] = { [OPTION_IP] = sizeof("255.255.255.255 "), [OPTION_IP_PAIR] = sizeof("255.255.255.255 ") * 2, [OPTION_STATIC_ROUTES]= sizeof("255.255.255.255/32 255.255.255.255 "), [OPTION_STRING] = 1, #if ENABLE_FEATURE_UDHCP_RFC3397 [OPTION_STR1035] = 1, #endif [OPTION_BOOLEAN] = sizeof("yes "), [OPTION_U8] = sizeof("255 "), [OPTION_U16] = sizeof("65535 "), [OPTION_S16] = sizeof("-32768 "), [OPTION_U32] = sizeof("4294967295 "), [OPTION_S32] = sizeof("-2147483684 "), }; /* note: ip is a pointer to an IP in network order, possibly misaliged */ static int sprint_nip(char *dest, const char *pre, const uint8_t *ip) { return sprintf(dest, "%s%u.%u.%u.%u", pre, ip[0], ip[1], ip[2], ip[3]); } /* really simple implementation, just count the bits */ static int mton(uint32_t mask) { int i = 0; mask = ntohl(mask); /* 111110000-like bit pattern */ while (mask) { i++; mask <<= 1; } return i; } /* Create "opt_name=opt_value" string */ static NOINLINE char *xmalloc_optname_optval(uint8_t *option, const struct dhcp_option *type_p, const char *opt_name) { unsigned upper_length; int len, type, optlen; uint16_t val_u16; int16_t val_s16; uint32_t val_u32; int32_t val_s32; char *dest, *ret; /* option points to OPT_DATA, need to go back and get OPT_LEN */ len = option[OPT_LEN - OPT_DATA]; type = type_p->flags & TYPE_MASK; optlen = dhcp_option_lengths[type]; upper_length = len_of_option_as_string[type] * (len / optlen); dest = ret = xmalloc(upper_length + strlen(opt_name) + 2); dest += sprintf(ret, "%s=", opt_name); while (len >= optlen) { switch (type) { case OPTION_IP_PAIR: dest += sprint_nip(dest, "", option); *dest++ = '/'; option += 4; optlen = 4; case OPTION_IP: dest += sprint_nip(dest, "", option); // TODO: it can be a list only if (type_p->flags & OPTION_LIST). // Should we bail out/warn if we see multi-ip option which is // not allowed to be such? For example, DHCP_BROADCAST... break; case OPTION_BOOLEAN: dest += sprintf(dest, *option ? "yes" : "no"); break; case OPTION_U8: dest += sprintf(dest, "%u", *option); break; case OPTION_U16: move_from_unaligned16(val_u16, option); dest += sprintf(dest, "%u", ntohs(val_u16)); break; case OPTION_S16: move_from_unaligned16(val_s16, option); dest += sprintf(dest, "%d", ntohs(val_s16)); break; case OPTION_U32: move_from_unaligned32(val_u32, option); dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32)); break; case OPTION_S32: move_from_unaligned32(val_s32, option); dest += sprintf(dest, "%ld", (long) ntohl(val_s32)); break; case OPTION_STRING: memcpy(dest, option, len); dest[len] = '\0'; return ret; /* Short circuit this case */ case OPTION_STATIC_ROUTES: { /* Option binary format: * mask [one byte, 0..32] * ip [big endian, 0..4 bytes depending on mask] * router [big endian, 4 bytes] * may be repeated * * We convert it to a string "IP/MASK ROUTER IP2/MASK2 ROUTER2" */ const char *pfx = ""; while (len >= 1 + 4) { /* mask + 0-byte ip + router */ uint32_t nip; uint8_t *p; unsigned mask; int bytes; mask = *option++; if (mask > 32) break; len--; nip = 0; p = (void*) &nip; bytes = (mask + 7) / 8; /* 0 -> 0, 1..8 -> 1, 9..16 -> 2 etc */ while (--bytes >= 0) { *p++ = *option++; len--; } if (len < 4) break; /* print ip/mask */ dest += sprint_nip(dest, pfx, (void*) &nip); pfx = " "; dest += sprintf(dest, "/%u ", mask); /* print router */ dest += sprint_nip(dest, "", option); option += 4; len -= 4; } return ret; } #if ENABLE_FEATURE_UDHCP_RFC3397 case OPTION_STR1035: /* unpack option into dest; use ret for prefix (i.e., "optname=") */ dest = dname_dec(option, len, ret); if (dest) { free(ret); return dest; } /* error. return "optname=" string */ return ret; #endif } option += optlen; len -= optlen; if (len <= 0) break; *dest++ = ' '; *dest = '\0'; } return ret; } /* put all the parameters into the environment */ static char **fill_envp(struct dhcp_packet *packet) { int num_options = 0; int i; char **envp, **curr; const char *opt_name; uint8_t *temp; uint8_t over = 0; if (packet) { for (i = 0; dhcp_options[i].code; i++) { if (get_option(packet, dhcp_options[i].code)) { num_options++; if (dhcp_options[i].code == DHCP_SUBNET) num_options++; /* for mton */ } } if (packet->siaddr_nip) num_options++; temp = get_option(packet, DHCP_OPTION_OVERLOAD); if (temp) over = *temp; if (!(over & FILE_FIELD) && packet->file[0]) num_options++; if (!(over & SNAME_FIELD) && packet->sname[0]) num_options++; } curr = envp = xzalloc(sizeof(char *) * (num_options + 3)); *curr = xasprintf("interface=%s", client_config.interface); putenv(*curr++); if (packet == NULL) return envp; *curr = xmalloc(sizeof("ip=255.255.255.255")); sprint_nip(*curr, "ip=", (uint8_t *) &packet->yiaddr); putenv(*curr++); opt_name = dhcp_option_strings; i = 0; while (*opt_name) { temp = get_option(packet, dhcp_options[i].code); if (!temp) goto next; *curr = xmalloc_optname_optval(temp, &dhcp_options[i], opt_name); putenv(*curr++); /* Fill in a subnet bits option for things like /24 */ if (dhcp_options[i].code == DHCP_SUBNET) { uint32_t subnet; move_from_unaligned32(subnet, temp); *curr = xasprintf("mask=%d", mton(subnet)); putenv(*curr++); } next: opt_name += strlen(opt_name) + 1; i++; } if (packet->siaddr_nip) { *curr = xmalloc(sizeof("siaddr=255.255.255.255")); sprint_nip(*curr, "siaddr=", (uint8_t *) &packet->siaddr_nip); putenv(*curr++); } if (!(over & FILE_FIELD) && packet->file[0]) { /* watch out for invalid packets */ *curr = xasprintf("boot_file=%."DHCP_PKT_FILE_LEN_STR"s", packet->file); putenv(*curr++); } if (!(over & SNAME_FIELD) && packet->sname[0]) { /* watch out for invalid packets */ *curr = xasprintf("sname=%."DHCP_PKT_SNAME_LEN_STR"s", packet->sname); putenv(*curr++); } return envp; } /* Call a script with a par file and env vars */ void FAST_FUNC udhcp_run_script(struct dhcp_packet *packet, const char *name) { char **envp, **curr; char *argv[3]; if (client_config.script == NULL) return; envp = fill_envp(packet); /* call script */ log1("Executing %s %s", client_config.script, name); argv[0] = (char*) client_config.script; argv[1] = (char*) name; argv[2] = NULL; spawn_and_wait(argv); for (curr = envp; *curr; curr++) { log2(" %s", *curr); bb_unsetenv(*curr); free(*curr); } free(envp); }