From f7c5421560640d23fc10803b9d59a9ff1d83e467 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:29:22 -0800 Subject: auto import from //depot/cupcake/@135843 --- dhcp.c | 1251 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1251 insertions(+) create mode 100644 dhcp.c (limited to 'dhcp.c') diff --git a/dhcp.c b/dhcp.c new file mode 100644 index 0000000..2469429 --- /dev/null +++ b/dhcp.c @@ -0,0 +1,1251 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "dhcp.h" + +#define REQUEST (1 << 0) +#define UINT8 (1 << 1) +#define UINT16 (1 << 2) +#define SINT16 (1 << 3) +#define UINT32 (1 << 4) +#define SINT32 (1 << 5) +#define IPV4 (1 << 6) +#define STRING (1 << 7) +#define PAIR (1 << 8) +#define ARRAY (1 << 9) +#define RFC3361 (1 << 10) +#define RFC3397 (1 << 11) +#define RFC3442 (1 << 12) + +#define IPV4R IPV4 | REQUEST + +/* Our aggregate option buffer. + * We ONLY use this when options are split, which for most purposes is + * practically never. See RFC3396 for details. */ +static uint8_t *dhcp_opt_buffer = NULL; + +struct dhcp_opt { + uint8_t option; + int type; + const char *var; +}; + +static const struct dhcp_opt const dhcp_opts[] = { + { 1, IPV4 | REQUEST, "subnet_mask" }, + { 2, UINT32, "time_offset" }, + { 3, IPV4 | ARRAY | REQUEST, "routers" }, + { 4, IPV4 | ARRAY, "time_servers" }, + { 5, IPV4 | ARRAY, "ien116_name_servers" }, + { 6, IPV4 | ARRAY, "domain_name_servers" }, + { 7, IPV4 | ARRAY, "log_servers" }, + { 8, IPV4 | ARRAY, "cookie_servers" }, + { 9, IPV4 | ARRAY, "lpr_servers" }, + { 10, IPV4 | ARRAY, "impress_servers" }, + { 11, IPV4 | ARRAY, "resource_location_servers" }, + { 12, STRING, "host_name" }, + { 13, UINT16, "boot_size" }, + { 14, STRING, "merit_dump" }, + { 15, STRING, "domain_name" }, + { 16, IPV4, "swap_server" }, + { 17, STRING, "root_path" }, + { 18, STRING, "extensions_path" }, + { 19, UINT8, "ip_forwarding" }, + { 20, UINT8, "non_local_source_routing" }, + { 21, IPV4 | ARRAY, "policy_filter" }, + { 22, SINT16, "max_dgram_reassembly" }, + { 23, UINT16, "default_ip_ttl" }, + { 24, UINT32, "path_mtu_aging_timeout" }, + { 25, UINT16 | ARRAY, "path_mtu_plateau_table" }, + { 26, UINT16, "interface_mtu" }, + { 27, UINT8, "all_subnets_local" }, + { 28, IPV4 | REQUEST, "broadcast_address" }, + { 29, UINT8, "perform_mask_discovery" }, + { 30, UINT8, "mask_supplier" }, + { 31, UINT8, "router_discovery" }, + { 32, IPV4, "router_solicitation_address" }, + { 33, IPV4 | ARRAY | REQUEST, "static_routes" }, + { 34, UINT8, "trailer_encapsulation" }, + { 35, UINT32, "arp_cache_timeout" }, + { 36, UINT16, "ieee802_3_encapsulation" }, + { 37, UINT8, "default_tcp_ttl" }, + { 38, UINT32, "tcp_keepalive_interval" }, + { 39, UINT8, "tcp_keepalive_garbage" }, + { 40, STRING, "nis_domain" }, + { 41, IPV4 | ARRAY, "nis_servers" }, + { 42, IPV4 | ARRAY, "ntp_servers" }, + { 43, STRING, "vendor_encapsulated_options" }, + { 44, IPV4 | ARRAY, "netbios_name_servers" }, + { 45, IPV4, "netbios_dd_server" }, + { 46, UINT8, "netbios_node_type" }, + { 47, STRING, "netbios_scope" }, + { 48, IPV4 | ARRAY, "font_servers" }, + { 49, IPV4 | ARRAY, "x_display_manager" }, + { 50, IPV4, "dhcp_requested_address" }, + { 51, UINT32 | REQUEST, "dhcp_lease_time" }, + { 52, UINT8, "dhcp_option_overload" }, + { 53, UINT8, "dhcp_message_type" }, + { 54, IPV4, "dhcp_server_identifier" }, + { 55, UINT8 | ARRAY, "dhcp_parameter_request_list" }, + { 56, STRING, "dhcp_message" }, + { 57, UINT16, "dhcp_max_message_size" }, + { 58, UINT32 | REQUEST, "dhcp_renewal_time" }, + { 59, UINT32 | REQUEST, "dhcp_rebinding_time" }, + { 64, STRING, "nisplus_domain" }, + { 65, IPV4 | ARRAY, "nisplus_servers" }, + { 66, STRING, "tftp_server_name" }, + { 67, STRING, "bootfile_name" }, + { 68, IPV4 | ARRAY, "mobile_ip_home_agent" }, + { 69, IPV4 | ARRAY, "smtp_server" }, + { 70, IPV4 | ARRAY, "pop_server" }, + { 71, IPV4 | ARRAY, "nntp_server" }, + { 72, IPV4 | ARRAY, "www_server" }, + { 73, IPV4 | ARRAY, "finger_server" }, + { 74, IPV4 | ARRAY, "irc_server" }, + { 75, IPV4 | ARRAY, "streettalk_server" }, + { 76, IPV4 | ARRAY, "streettalk_directory_assistance_server" }, + { 77, STRING, "user_class" }, + { 81, STRING | RFC3397, "fqdn_name" }, + { 85, IPV4 | ARRAY, "nds_servers" }, + { 86, STRING, "nds_tree_name" }, + { 87, STRING, "nds_context" }, + { 88, STRING | RFC3397, "bcms_controller_names" }, + { 89, IPV4 | ARRAY, "bcms_controller_address" }, + { 91, UINT32, "client_last_transaction_time" }, + { 92, IPV4 | ARRAY, "associated_ip" }, + { 98, STRING, "uap_servers" }, + { 112, IPV4 | ARRAY, "netinfo_server_address" }, + { 113, STRING, "netinfo_server_tag" }, + { 114, STRING, "default_url" }, + { 118, IPV4, "subnet_selection" }, + { 119, STRING | RFC3397, "domain_search" }, + { 121, RFC3442 | REQUEST, "classless_static_routes" }, + { 249, RFC3442, "ms_classless_static_routes" }, + { 0, 0, NULL } +}; + +void +print_options(void) +{ + const struct dhcp_opt *opt; + + for (opt = dhcp_opts; opt->option; opt++) + if (opt->var) + printf("%03d %s\n", opt->option, opt->var); +} + +int make_option_mask(uint8_t *mask, char **opts, int add) +{ + char *token, *p = *opts, *t; + const struct dhcp_opt *opt; + int match, n; + + while ((token = strsep(&p, ", "))) { + if (*token == '\0') + continue; + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + match = 0; + if (strcmp(opt->var, token) == 0) + match = 1; + else { + errno = 0; + n = strtol(token, &t, 0); + if (errno == 0 && !*t) + if (opt->option == n) + match = 1; + } + if (match) { + if (add == 1) + add_option_mask(mask, + opt->option); + else + del_option_mask(mask, + opt->option); + break; + } + } + if (!opt->option) { + *opts = token; + errno = ENOENT; + return -1; + } + } + return 0; +} + +static int +valid_length(uint8_t option, int dl, int *type) +{ + const struct dhcp_opt *opt; + ssize_t sz; + + if (dl == 0) + return -1; + + for (opt = dhcp_opts; opt->option; opt++) { + if (opt->option != option) + continue; + + if (type) + *type = opt->type; + + if (opt->type == 0 || opt->type & STRING || opt->type & RFC3442) + return 0; + + sz = 0; + if (opt->type & UINT32 || opt->type & IPV4) + sz = sizeof(uint32_t); + if (opt->type & UINT16) + sz = sizeof(uint16_t); + if (opt->type & UINT8) + sz = sizeof(uint8_t); + if (opt->type & IPV4 || opt->type & ARRAY) + return dl % sz; + return (dl == sz ? 0 : -1); + } + + /* unknown option, so let it pass */ + return 0; +} + +static void +free_option_buffer(void) +{ + free(dhcp_opt_buffer); +} + +#define get_option_raw(dhcp, opt) get_option(dhcp, opt, NULL, NULL) +static const uint8_t * +get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type) +{ + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l, ol = 0; + uint8_t o = 0; + uint8_t overl = 0; + uint8_t *bp = NULL; + const uint8_t *op = NULL; + int bl = 0; + + while (p < e) { + o = *p++; + if (o == opt) { + if (op) { + if (!dhcp_opt_buffer) { + dhcp_opt_buffer = xmalloc(sizeof(struct dhcp_message)); + atexit(free_option_buffer); + } + if (!bp) + bp = dhcp_opt_buffer; + memcpy(bp, op, ol); + bp += ol; + } + ol = *p; + op = p + 1; + bl += ol; + } + switch (o) { + case DHO_PAD: + continue; + case DHO_END: + if (overl & 1) { + /* bit 1 set means parse boot file */ + overl &= ~1; + p = dhcp->bootfile; + e = p + sizeof(dhcp->bootfile); + } else if (overl & 2) { + /* bit 2 set means parse server name */ + overl &= ~2; + p = dhcp->servername; + e = p + sizeof(dhcp->servername); + } else + goto exit; + break; + case DHO_OPTIONSOVERLOADED: + /* Ensure we only get this option once */ + if (!overl) + overl = p[1]; + break; + } + l = *p++; + p += l; + } + +exit: + if (valid_length(opt, bl, type) == -1) { + errno = EINVAL; + return NULL; + } + if (len) + *len = bl; + if (bp) { + memcpy(bp, op, ol); + return (const uint8_t *)&dhcp_opt_buffer; + } + if (op) + return op; + errno = ENOENT; + return NULL; +} + +int +get_option_addr(uint32_t *a, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p = get_option_raw(dhcp, option); + + if (!p) + return -1; + memcpy(a, p, sizeof(*a)); + return 0; +} + +int +get_option_uint32(uint32_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + uint32_t a; + + if (get_option_addr(&a, dhcp, option) == -1) + return -1; + + *i = ntohl(a); + return 0; +} + +int +get_option_uint16(uint16_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p = get_option_raw(dhcp, option); + uint16_t d; + + if (!p) + return -1; + memcpy(&d, p, sizeof(d)); + *i = ntohs(d); + return 0; +} + +int +get_option_uint8(uint8_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p = get_option_raw(dhcp, option); + + if (!p) + return -1; + *i = *(p); + return 0; +} + +/* Decode an RFC3397 DNS search order option into a space + * seperated string. Returns length of string (including + * terminating zero) or zero on error. out may be NULL + * to just determine output length. */ +static ssize_t +decode_rfc3397(char *out, ssize_t len, int pl, const uint8_t *p) +{ + const uint8_t *r, *q = p; + int count = 0, l, hops; + uint8_t ltype; + + while (q - p < pl) { + r = NULL; + hops = 0; + /* We check we are inside our length again incase + * the data is NOT terminated correctly. */ + while ((l = *q++) && q - p < pl) { + ltype = l & 0xc0; + if (ltype == 0x80 || ltype == 0x40) + return 0; + else if (ltype == 0xc0) { /* pointer */ + l = (l & 0x3f) << 8; + l |= *q++; + /* save source of first jump. */ + if (!r) + r = q; + hops++; + if (hops > 255) + return 0; + q = p + l; + if (q - p >= pl) + return 0; + } else { + /* straightforward name segment, add with '.' */ + count += l + 1; + if (out) { + if ((ssize_t)l + 1 > len) { + errno = ENOBUFS; + return -1; + } + memcpy(out, q, l); + out += l; + *out++ = '.'; + len -= l; + len--; + } + q += l; + } + } + /* change last dot to space */ + if (out) + *(out - 1) = ' '; + if (r) + q = r; + } + + /* change last space to zero terminator */ + if (out) + *(out - 1) = 0; + + return count; +} + +static ssize_t +decode_rfc3442(char *out, ssize_t len, int pl, const uint8_t *p) +{ + const uint8_t *e; + ssize_t bytes = 0; + ssize_t b; + uint8_t cidr; + uint8_t ocets; + struct in_addr addr; + char *o = out; + + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (pl < 5) { + errno = EINVAL; + return -1; + } + + e = p + pl; + while (p < e) { + cidr = *p++; + if (cidr > 32) { + errno = EINVAL; + return -1; + } + ocets = (cidr + 7) / 8; + if (!out) { + p += 4 + ocets; + bytes += ((4 * 4) * 2) + 4; + continue; + } + if ((((4 * 4) * 2) + 4) > len) { + errno = ENOBUFS; + return -1; + } + if (o != out) { + *o++ = ' '; + len--; + } + /* If we have ocets then we have a destination and netmask */ + if (ocets > 0) { + addr.s_addr = 0; + memcpy(&addr.s_addr, p, (size_t)ocets); + b = snprintf(o, len, "%s/%d", inet_ntoa(addr), cidr); + p += ocets; + } else + b = snprintf(o, len, "0.0.0.0/0"); + o += b; + len -= b; + + /* Finally, snag the router */ + memcpy(&addr.s_addr, p, 4); + p += 4; + b = snprintf(o, len, " %s", inet_ntoa(addr)); + o += b; + len -= b; + } + + if (out) + return o - out; + return bytes; +} + +static struct rt * +decode_rfc3442_rt(int dl, const uint8_t *data) +{ + const uint8_t *p = data; + const uint8_t *e; + uint8_t cidr; + uint8_t ocets; + struct rt *routes = NULL; + struct rt *rt = NULL; + + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (dl < 5) + return NULL; + + e = p + dl; + while (p < e) { + cidr = *p++; + if (cidr > 32) { + free_routes(routes); + errno = EINVAL; + return NULL; + } + + if (rt) { + rt->next = xzalloc(sizeof(*rt)); + rt = rt->next; + } else { + routes = rt = xzalloc(sizeof(*routes)); + } + rt->next = NULL; + + ocets = (cidr + 7) / 8; + /* If we have ocets then we have a destination and netmask */ + if (ocets > 0) { + memcpy(&rt->dest.s_addr, p, (size_t)ocets); + memset(&rt->net.s_addr, 255, (size_t)ocets - 1); + memset((uint8_t *)&rt->net.s_addr + + (ocets - 1), + (256 - (1 << (32 - cidr) % 8)), 1); + p += ocets; + } else { + rt->dest.s_addr = 0; + rt->net.s_addr = 0; + } + + /* Finally, snag the router */ + memcpy(&rt->gate.s_addr, p, 4); + p += 4; + } + return routes; +} + +static char * +decode_rfc3361(int dl, const uint8_t *data) +{ + uint8_t enc; + unsigned int l; + char *sip = NULL; + struct in_addr addr; + char *p; + + if (dl < 2) { + errno = EINVAL; + return 0; + } + + enc = *data++; + dl--; + switch (enc) { + case 0: + if ((l = decode_rfc3397(NULL, 0, dl, data)) > 0) { + sip = xmalloc(l); + decode_rfc3397(sip, l, dl, data); + } + break; + case 1: + if (dl == 0 || dl % 4 != 0) { + errno = EINVAL; + break; + } + addr.s_addr = INADDR_BROADCAST; + l = ((dl / sizeof(addr.s_addr)) * ((4 * 4) + 1)) + 1; + sip = p = xmalloc(l); + while (l != 0) { + memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); + data += sizeof(addr.s_addr); + p += snprintf(p, l - (p - sip), "%s ", inet_ntoa(addr)); + l -= sizeof(addr.s_addr); + } + *--p = '\0'; + break; + default: + errno = EINVAL; + return 0; + } + + return sip; +} + +char * +get_option_string(const struct dhcp_message *dhcp, uint8_t option) +{ + int type = 0; + int len; + const uint8_t *p; + char *s; + + p = get_option(dhcp, option, &len, &type); + if (!p || *p == '\0') + return NULL; + + if (type & RFC3397) { + type = decode_rfc3397(NULL, 0, len, p); + if (!type) { + errno = EINVAL; + return NULL; + } + s = xmalloc(sizeof(char) * type); + decode_rfc3397(s, type, len, p); + return s; + } + + if (type & RFC3361) + return decode_rfc3361(len, p); + + s = xmalloc(sizeof(char) * (len + 1)); + memcpy(s, p, len); + s[len] = '\0'; + return s; +} + +/* This calculates the netmask that we should use for static routes. + * This IS different from the calculation used to calculate the netmask + * for an interface address. */ +static uint32_t +route_netmask(uint32_t ip_in) +{ + /* used to be unsigned long - check if error */ + uint32_t p = ntohl(ip_in); + uint32_t t; + + if (IN_CLASSA(p)) + t = ~IN_CLASSA_NET; + else { + if (IN_CLASSB(p)) + t = ~IN_CLASSB_NET; + else { + if (IN_CLASSC(p)) + t = ~IN_CLASSC_NET; + else + t = 0; + } + } + + while (t & p) + t >>= 1; + + return (htonl(~t)); +} + +/* We need to obey routing options. + * If we have a CSR then we only use that. + * Otherwise we add static routes and then routers. */ +struct rt * +get_option_routes(const struct dhcp_message *dhcp) +{ + const uint8_t *p; + const uint8_t *e; + struct rt *routes = NULL; + struct rt *route = NULL; + int len; + + /* If we have CSR's then we MUST use these only */ + p = get_option(dhcp, DHO_CSR, &len, NULL); + /* Check for crappy MS option */ + if (!p) + p = get_option(dhcp, DHO_MSCSR, &len, NULL); + if (p) { + routes = decode_rfc3442_rt(len, p); + if (routes) + return routes; + } + + /* OK, get our static routes first. */ + p = get_option(dhcp, DHO_STATICROUTE, &len, NULL); + if (p) { + e = p + len; + while (p < e) { + if (route) { + route->next = xmalloc(sizeof(*route)); + route = route->next; + } else + routes = route = xmalloc(sizeof(*routes)); + route->next = NULL; + memcpy(&route->dest.s_addr, p, 4); + p += 4; + memcpy(&route->gate.s_addr, p, 4); + p += 4; + route->net.s_addr = route_netmask(route->dest.s_addr); + } + } + + /* Now grab our routers */ + p = get_option(dhcp, DHO_ROUTER, &len, NULL); + if (p) { + e = p + len; + while (p < e) { + if (route) { + route->next = xzalloc(sizeof(*route)); + route = route->next; + } else + routes = route = xzalloc(sizeof(*route)); + memcpy(&route->gate.s_addr, p, 4); + p += 4; + } + } + + return routes; +} + +static size_t +encode_rfc1035(const char *src, uint8_t *dst, size_t len) +{ + const char *c = src; + uint8_t *p = dst; + uint8_t *lp = p++; + + if (len == 0) + return 0; + while (c < src + len) { + if (*c == '\0') + break; + if (*c == '.') { + /* Skip the trailing . */ + if (c == src + len - 1) + break; + *lp = p - lp - 1; + if (*lp == '\0') + return p - dst; + lp = p++; + } else + *p++ = (uint8_t) *c; + c++; + } + *lp = p - lp - 1; + *p++ = '\0'; + return p - dst; +} + +ssize_t +make_message(struct dhcp_message **message, + const struct interface *iface, const struct dhcp_lease *lease, + uint32_t xid, uint8_t type, const struct options *options) +{ + struct dhcp_message *dhcp; + uint8_t *m, *lp, *p; + uint8_t *n_params = NULL; + time_t up = uptime() - iface->start_uptime; + uint32_t ul; + uint16_t sz; + const struct dhcp_opt *opt; + + dhcp = xzalloc(sizeof (*dhcp)); + m = (uint8_t *)dhcp; + p = dhcp->options; + + if ((type == DHCP_INFORM || + type == DHCP_RELEASE || + type == DHCP_REQUEST) && + !IN_LINKLOCAL(ntohl(iface->addr.s_addr))) + { + dhcp->ciaddr = iface->addr.s_addr; + /* Just incase we haven't actually configured the address yet */ + if (type == DHCP_INFORM && iface->addr.s_addr == 0) + dhcp->ciaddr = lease->addr.s_addr; + /* Zero the address if we're currently on a different subnet */ + if (type == DHCP_REQUEST && + iface->net.s_addr != lease->net.s_addr) + dhcp->ciaddr = 0; + } + + dhcp->op = DHCP_BOOTREQUEST; + dhcp->hwtype = iface->family; + switch (iface->family) { + case ARPHRD_ETHER: + case ARPHRD_IEEE802: + dhcp->hwlen = ETHER_ADDR_LEN; + memcpy(&dhcp->chaddr, &iface->hwaddr, ETHER_ADDR_LEN); + break; + case ARPHRD_IEEE1394: + case ARPHRD_INFINIBAND: + dhcp->hwlen = 0; + if (dhcp->ciaddr == 0) + dhcp->flags = htons(BROADCAST_FLAG); + break; + } + + if (up < 0 || up > (time_t)UINT16_MAX) + dhcp->secs = htons((uint16_t)UINT16_MAX); + else + dhcp->secs = htons(up); + dhcp->xid = xid; + dhcp->cookie = htonl(MAGIC_COOKIE); + + *p++ = DHO_MESSAGETYPE; + *p++ = 1; + *p++ = type; + + if (type == DHCP_REQUEST) { + *p++ = DHO_MAXMESSAGESIZE; + *p++ = 2; + sz = get_mtu(iface->name); + if (sz < MTU_MIN) { + if (set_mtu(iface->name, MTU_MIN) == 0) + sz = MTU_MIN; + } + sz = htons(sz); + memcpy(p, &sz, 2); + p += 2; + } + + if (iface->clientid) { + *p++ = DHO_CLIENTID; + memcpy(p, iface->clientid, iface->clientid[0] + 1); + p += iface->clientid[0] + 1; + } + + if (type != DHCP_DECLINE && type != DHCP_RELEASE) { + if (options->userclass[0]) { + *p++ = DHO_USERCLASS; + memcpy(p, options->userclass, options->userclass[0] + 1); + p += options->userclass[0] + 1; + } + + if (options->vendorclassid[0]) { + *p++ = DHO_VENDORCLASSID; + memcpy(p, options->vendorclassid, + options->vendorclassid[0] + 1); + p += options->vendorclassid[0] + 1; + } + } + + if (type == DHCP_DISCOVER || type == DHCP_REQUEST) { +#define PUTADDR(_type, _val) \ + { \ + *p++ = _type; \ + *p++ = 4; \ + memcpy(p, &_val.s_addr, 4); \ + p += 4; \ + } + if (lease->addr.s_addr && + lease->addr.s_addr != iface->addr.s_addr && + !IN_LINKLOCAL(ntohl(lease->addr.s_addr))) + { + PUTADDR(DHO_IPADDRESS, lease->addr); + if (lease->server.s_addr) + PUTADDR(DHO_SERVERID, lease->server); + } +#undef PUTADDR + + if (options->leasetime != 0) { + *p++ = DHO_LEASETIME; + *p++ = 4; + ul = htonl(options->leasetime); + memcpy(p, &ul, 4); + p += 4; + } + } + + if (type == DHCP_DISCOVER || + type == DHCP_INFORM || + type == DHCP_REQUEST) + { + if (options->hostname[0]) { + *p++ = DHO_HOSTNAME; + memcpy(p, options->hostname, options->hostname[0] + 1); + p += options->hostname[0] + 1; + } + if (options->fqdn != FQDN_DISABLE) { + /* IETF DHC-FQDN option (81), RFC4702 */ + *p++ = DHO_FQDN; + lp = p; + *p++ = 3; + /* + * Flags: 0000NEOS + * S: 1 => Client requests Server to update + * a RR in DNS as well as PTR + * O: 1 => Server indicates to client that + * DNS has been updated + * E: 1 => Name data is DNS format + * N: 1 => Client requests Server to not + * update DNS + */ + *p++ = (options->fqdn & 0x09) | 0x04; + *p++ = 0; /* from server for PTR RR */ + *p++ = 0; /* from server for A RR if S=1 */ + ul = encode_rfc1035(options->hostname + 1, p, + options->hostname[0]); + *lp += ul; + p += ul; + } + + /* vendor is already encoded correctly, so just add it */ + if (options->vendor[0]) { + *p++ = DHO_VENDOR; + memcpy(p, options->vendor, options->vendor[0] + 1); + p += options->vendor[0] + 1; + } + + *p++ = DHO_PARAMETERREQUESTLIST; + n_params = p; + *p++ = 0; + for (opt = dhcp_opts; opt->option; opt++) { + if (!(opt->type & REQUEST || + has_option_mask(options->requestmask, opt->option))) + continue; + switch (opt->option) { + case DHO_RENEWALTIME: /* FALLTHROUGH */ + case DHO_REBINDTIME: + if (type == DHCP_INFORM) + continue; + break; + } + *p++ = opt->option; + } + *n_params = p - n_params - 1; + } + *p++ = DHO_END; + +#ifdef BOOTP_MESSAGE_LENTH_MIN + /* Some crappy DHCP servers think they have to obey the BOOTP minimum + * message length. + * They are wrong, but we should still cater for them. */ + while (p - m < BOOTP_MESSAGE_LENTH_MIN) + *p++ = DHO_PAD; +#endif + + *message = dhcp; + return p - m; +} + +ssize_t +write_lease(const struct interface *iface, const struct dhcp_message *dhcp) +{ + int fd; + ssize_t bytes = sizeof(*dhcp); + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l; + uint8_t o = 0; + + fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0400); +#ifdef ANDROID + if (fd == -1 && errno == EACCES) { + /* the lease file might have been created when dhcpcd was running as root */ + unlink(iface->leasefile); + fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0400); + } +#endif + if (fd == -1) + return -1; + + /* Only write as much as we need */ + while (p < e) { + o = *p; + if (o == DHO_END) { + bytes = p - (const uint8_t *)dhcp; + break; + } + p++; + if (o != DHO_PAD) { + l = *p++; + p += l; + } + } + bytes = write(fd, dhcp, bytes); + close(fd); + return bytes; +} + +struct dhcp_message * +read_lease(const struct interface *iface) +{ + int fd; + struct dhcp_message *dhcp; + ssize_t bytes; + + fd = open(iface->leasefile, O_RDONLY); + if (fd == -1) + return NULL; + dhcp = xmalloc(sizeof(*dhcp)); + memset(dhcp, 0, sizeof(*dhcp)); + bytes = read(fd, dhcp, sizeof(*dhcp)); + close(fd); + if (bytes < 0) { + free(dhcp); + dhcp = NULL; + } + return dhcp; +} + +static ssize_t +print_string(char *s, ssize_t len, int dl, const uint8_t *data) +{ + uint8_t c; + const uint8_t *e; + ssize_t bytes = 0; + ssize_t r; + + e = data + dl; + while (data < e) { + c = *data++; + if (!isascii(c) || !isprint(c)) { + if (s) { + if (len < 5) { + errno = ENOBUFS; + return -1; + } + r = snprintf(s, len, "\\%03o", c); + len -= r; + bytes += r; + s += r; + } else + bytes += 4; + continue; + } + switch (c) { + case '"': /* FALLTHROUGH */ + case '\'': /* FALLTHROUGH */ + case '$': /* FALLTHROUGH */ + case '`': /* FALLTHROUGH */ + case '\\': /* FALLTHROUGH */ + if (s) { + if (len < 3) { + errno = ENOBUFS; + return -1; + } + *s++ = '\\'; + len--; + } + bytes++; + break; + } + if (s) { + *s++ = c; + len--; + } + bytes++; + } + + /* NULL */ + if (s) + *s = '\0'; + bytes++; + return bytes; +} + +static ssize_t +print_option(char *s, ssize_t len, int type, int dl, const uint8_t *data) +{ + const uint8_t *e, *t; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + struct in_addr addr; + ssize_t bytes = 0; + ssize_t l; + char *tmp; + + if (type & RFC3397) { + l = decode_rfc3397(NULL, 0, dl, data); + if (l < 1) + return l; + tmp = xmalloc(l); + decode_rfc3397(tmp, l, dl, data); + l = print_string(s, len, l - 1, (uint8_t *)tmp); + free(tmp); + return l; + } + + if (type & RFC3442) + return decode_rfc3442(s, len, dl, data); + + if (type & STRING) { + /* Some DHCP servers return NULL strings */ + if (*data == '\0') + return 0; + return print_string(s, len, dl, data); + } + + if (!s) { + if (type & UINT8) + l = 3; + else if (type & UINT16) + l = 5; + else if (type & SINT16) + l = 6; + else if (type & UINT32) + l = 10; + else if (type & SINT32) + l = 11; + else if (type & IPV4) + l = 16; + else { + errno = EINVAL; + return -1; + } + return (l + 1) * dl; + } + + t = data; + e = data + dl; + while (data < e) { + if (data != t) { + *s++ = ' '; + bytes++; + len--; + } + if (type & UINT8) { + l = snprintf(s, len, "%d", *data); + data++; + } else if (type & UINT16) { + memcpy(&u16, data, sizeof(u16)); + u16 = ntohs(u16); + l = snprintf(s, len, "%d", u16); + data += sizeof(u16); + } else if (type & SINT16) { + memcpy(&s16, data, sizeof(s16)); + s16 = ntohs(s16); + l = snprintf(s, len, "%d", s16); + data += sizeof(s16); + } else if (type & UINT32) { + memcpy(&u32, data, sizeof(u32)); + u32 = ntohl(u32); + l = snprintf(s, len, "%d", u32); + data += sizeof(u32); + } else if (type & SINT32) { + memcpy(&s32, data, sizeof(s32)); + s32 = ntohl(s32); + l = snprintf(s, len, "%d", s32); + data += sizeof(s32); + } else if (type & IPV4) { + memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); + l = snprintf(s, len, "%s", inet_ntoa(addr)); + data += sizeof(addr.s_addr); + } else + l = 0; + len -= l; + bytes += l; + s += l; + } + + return bytes; +} + +static void +setvar(char ***e, const char *prefix, const char *var, const char *value) +{ + size_t len = strlen(prefix) + strlen(var) + strlen(value) + 4; + + **e = xmalloc(len); + snprintf(**e, len, "%s_%s=%s", prefix, var, value); + (*e)++; +} + +ssize_t +configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, + const struct options *options) +{ + unsigned int i; + const uint8_t *p; + int pl; + struct in_addr addr; + struct in_addr net; + struct in_addr brd; + char *val, *v; + const struct dhcp_opt *opt; + ssize_t len, e = 0; + char **ep; + char cidr[4]; + uint8_t overl = 0; + + get_option_uint8(&overl, dhcp, DHO_OPTIONSOVERLOADED); + + if (!env) { + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + if (has_option_mask(options->nomask, opt->option)) + continue; + if (get_option_raw(dhcp, opt->option)) + e++; + } + if (dhcp->yiaddr) + e += 5; + if (*dhcp->bootfile && !(overl & 1)) + e++; + if (*dhcp->servername && !(overl & 2)) + e++; + return e; + } + + ep = env; + if (dhcp->yiaddr) { + /* Set some useful variables that we derive from the DHCP + * message but are not necessarily in the options */ + addr.s_addr = dhcp->yiaddr; + setvar(&ep, prefix, "ip_address", inet_ntoa(addr)); + if (get_option_addr(&net.s_addr, dhcp, DHO_SUBNETMASK) == -1) { + net.s_addr = get_netmask(addr.s_addr); + setvar(&ep, prefix, "subnet_mask", inet_ntoa(net)); + } + i = inet_ntocidr(net); + snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net)); + setvar(&ep, prefix, "subnet_cidr", cidr); + if (get_option_addr(&brd.s_addr, dhcp, DHO_BROADCAST) == -1) { + brd.s_addr = addr.s_addr | ~net.s_addr; + setvar(&ep, prefix, "broadcast_address", inet_ntoa(net)); + } + addr.s_addr = dhcp->yiaddr & net.s_addr; + setvar(&ep, prefix, "network_number", inet_ntoa(addr)); + } + + if (*dhcp->bootfile && !(overl & 1)) + setvar(&ep, prefix, "filename", (const char *)dhcp->bootfile); + if (*dhcp->servername && !(overl & 2)) + setvar(&ep, prefix, "server_name", (const char *)dhcp->servername); + + for (opt = dhcp_opts; opt->option; opt++) { + if (!opt->var) + continue; + if (has_option_mask(options->nomask, opt->option)) + continue; + val = NULL; + p = get_option(dhcp, opt->option, &pl, NULL); + if (!p) + continue; + /* We only want the FQDN name */ + if (opt->option == DHO_FQDN) { + p += 3; + pl -= 3; + } + len = print_option(NULL, 0, opt->type, pl, p); + if (len < 0) + return -1; + e = strlen(prefix) + strlen(opt->var) + len + 4; + v = val = *ep++ = xmalloc(e); + v += snprintf(val, e, "%s_%s=", prefix, opt->var); + if (len != 0) + print_option(v, len, opt->type, pl, p); + } + + return ep - env; +} -- cgit v1.1