/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2012 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 #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 RFC5969 (1 << 13) #define IPV4R IPV4 | REQUEST #define DAD "Duplicate address detected" /* 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 *opt_buffer; struct dhcp_opt { uint8_t option; int type; const char *var; }; static const struct dhcp_opt const dhcp_opts[] = { { 1, IPV4 | REQUEST, "subnet_mask" }, /* RFC 3442 states that the CSR has to come before all other * routes. For completeness, we also specify static routes, * then routers. */ { 121, RFC3442, "classless_static_routes" }, { 249, RFC3442, "ms_classless_static_routes" }, { 33, IPV4 | ARRAY | REQUEST, "static_routes" }, { 3, IPV4 | ARRAY | REQUEST, "routers" }, { 2, UINT32, "time_offset" }, { 4, IPV4 | ARRAY, "time_servers" }, { 5, IPV4 | ARRAY, "ien116_name_servers" }, /* Explicitly include DNS in the list of parameters requested in the DNS request. * Without this some DHCP servers may skip the DNS entries in the DHCP replies.*/ { 6, IPV4 | ARRAY | REQUEST, "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" }, /* Explicitly include DNS in the list of parameters requested in the DNS request. * Without this some DHCP servers may skip the DNS entries in the DHCP replies.*/ { 15, STRING | REQUEST, "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" }, { 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" }, { 120, STRING | RFC3361, "sip_server" }, { 212, RFC5969, "sixrd" }, { 0, 0, NULL } }; static const char *if_params[] = { "interface", "reason", "pid", "ifmetric", "ifwireless", "ifflags", "profile", "interface_order", NULL }; static const char *dhcp_params[] = { "ip_address", "subnet_cidr", "network_number", "ssid", "filename", "server_name", NULL }; void print_options(void) { const struct dhcp_opt *opt; const char **p; for (p = if_params; *p; p++) printf(" - %s\n", *p); for (p = dhcp_params; *p; p++) printf(" %s\n", *p); 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, const char *opts, int add) { char *token, *o, *p, *t; const struct dhcp_opt *opt; int match, n; o = p = xstrdup(opts); 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 == 2 && !(opt->type & IPV4)) { free(o); errno = EINVAL; return -1; } if (add == 1 || add == 2) add_option_mask(mask, opt->option); else del_option_mask(mask, opt->option); break; } } if (!opt->option) { free(o); errno = ENOENT; return -1; } } free(o); 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 | RFC3442 | RFC5969)) return 0; sz = 0; if (opt->type & (UINT32 | 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 | ARRAY)) return dl % sz; return (dl == sz ? 0 : -1); } /* unknown option, so let it pass */ return 0; } #ifdef DEBUG_MEMORY static void free_option_buffer(void) { free(opt_buffer); } #endif #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; /* DHCP Options are in TLV format with T and L each being a single * byte. In general, here we have p -> T, ol=p+1 -> L, op -> V. * We must make sure there is enough room to read both T and L. */ while (p + 1 < e) { o = *p++; if (o == opt) { if (op) { if (!opt_buffer) { opt_buffer = xmalloc(sizeof(*dhcp)); #ifdef DEBUG_MEMORY atexit(free_option_buffer); #endif } if (!bp) bp = opt_buffer; memcpy(bp, op, ol); bp += ol; } ol = (p + *p < e) ? *p : e - (p + 1); 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 *)opt_buffer; } if (op) return op; errno = ENOENT; return NULL; } int get_option_addr(struct in_addr *a, const struct dhcp_message *dhcp, uint8_t option) { const uint8_t *p = get_option_raw(dhcp, option); if (!p) return -1; memcpy(&a->s_addr, p, sizeof(a->s_addr)); return 0; } int get_option_uint32(uint32_t *i, const struct dhcp_message *dhcp, uint8_t option) { const uint8_t *p = get_option_raw(dhcp, option); uint32_t d; if (!p) return -1; memcpy(&d, p, sizeof(d)); *i = ntohl(d); 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; if (i) *i = *(p); return 0; } /* Decode an RFC3397 DNS search order option into a space * separated string. Returns length of string (including * terminating zero) or zero on error. out may be NULL * to just determine output length. */ 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 b, bytes = 0, ocets; uint8_t cidr; 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, 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; size_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, ocets); p += ocets; rt->net.s_addr = htonl(~0U << (32 - cidr)); } /* 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 (dl != 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)); dl -= sizeof(addr.s_addr); } *--p = '\0'; break; default: errno = EINVAL; return 0; } return sip; } /* Decode an RFC5969 6rd order option into a space * separated string. Returns length of string (including * terminating zero) or zero on error. */ static ssize_t decode_rfc5969(char *out, ssize_t len, int pl, const uint8_t *p) { uint8_t ipv4masklen, ipv6prefixlen; uint8_t ipv6prefix[16]; uint8_t br[4]; int i; ssize_t b, bytes = 0; if (pl < 22) { errno = EINVAL; return 0; } ipv4masklen = *p++; pl--; ipv6prefixlen = *p++; pl--; for (i = 0; i < 16; i++) { ipv6prefix[i] = *p++; pl--; } if (out) { b= snprintf(out, len, "%d %d " "%02x%02x:%02x%02x:" "%02x%02x:%02x%02x:" "%02x%02x:%02x%02x:" "%02x%02x:%02x%02x", ipv4masklen, ipv6prefixlen, ipv6prefix[0], ipv6prefix[1], ipv6prefix[2], ipv6prefix[3], ipv6prefix[4], ipv6prefix[5], ipv6prefix[6], ipv6prefix[7], ipv6prefix[8], ipv6prefix[9], ipv6prefix[10],ipv6prefix[11], ipv6prefix[12],ipv6prefix[13],ipv6prefix[14], ipv6prefix[15] ); len -= b; out += b; bytes += b; } else { bytes += 16 * 2 + 8 + 2 + 1 + 2; } while (pl >= 4) { br[0] = *p++; br[1] = *p++; br[2] = *p++; br[3] = *p++; pl -= 4; if (out) { b= snprintf(out, len, " %d.%d.%d.%d", br[0], br[1], br[2], br[3]); len -= b; out += b; bytes += b; } else { bytes += (4 * 4); } } return bytes; } 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 char *ifname, unsigned long long *opts) { 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) { if (!(*opts & DHCPCD_CSR_WARNED)) { syslog(LOG_DEBUG, "%s: using Classless Static Routes", ifname); *opts |= DHCPCD_CSR_WARNED; } 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) { uint8_t *p = dst; uint8_t *lp = p++; if (*src == '\0') return 0; for (; *src; src++) { if (*src == '\0') break; if (*src == '.') { /* Skip the trailing . */ if (src[1] == '\0') break; *lp = p - lp - 1; if (*lp == '\0') return p - dst; lp = p++; } else *p++ = (uint8_t)*src; } *lp = p - lp - 1; *p++ = '\0'; return p - dst; } #define PUTADDR(_type, _val) \ { \ *p++ = _type; \ *p++ = 4; \ memcpy(p, &_val.s_addr, 4); \ p += 4; \ } int dhcp_message_add_addr(struct dhcp_message *dhcp, uint8_t type, struct in_addr addr) { uint8_t *p; size_t len; p = dhcp->options; while (*p != DHO_END) { p++; p += *p + 1; } len = p - (uint8_t *)dhcp; if (len + 6 > sizeof(*dhcp)) { errno = ENOMEM; return -1; } PUTADDR(type, addr); *p = DHO_END; return 0; } ssize_t make_message(struct dhcp_message **message, const struct interface *iface, uint8_t type) { 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; size_t len; const char *hp; const struct dhcp_opt *opt; const struct if_options *ifo = iface->state->options; const struct dhcp_lease *lease = &iface->state->lease; dhcp = xzalloc(sizeof (*dhcp)); m = (uint8_t *)dhcp; p = dhcp->options; if ((type == DHCP_INFORM || type == DHCP_RELEASE || (type == DHCP_REQUEST && iface->net.s_addr == lease->net.s_addr && (iface->state->new == NULL || iface->state->new->cookie == htonl(MAGIC_COOKIE))))) { dhcp->ciaddr = iface->addr.s_addr; /* In-case we haven't actually configured the address yet */ if (type == DHCP_INFORM && iface->addr.s_addr == 0) dhcp->ciaddr = lease->addr.s_addr; } dhcp->op = DHCP_BOOTREQUEST; dhcp->hwtype = iface->family; switch (iface->family) { case ARPHRD_ETHER: case ARPHRD_IEEE802: dhcp->hwlen = iface->hwlen; memcpy(&dhcp->chaddr, &iface->hwaddr, iface->hwlen); break; } if (ifo->options & DHCPCD_BROADCAST && dhcp->ciaddr == 0 && type != DHCP_DECLINE && type != DHCP_RELEASE) dhcp->flags = htons(BROADCAST_FLAG); if (type != DHCP_DECLINE && type != DHCP_RELEASE) { if (up < 0 || up > (time_t)UINT16_MAX) dhcp->secs = htons((uint16_t)UINT16_MAX); else dhcp->secs = htons(up); } dhcp->xid = iface->state->xid; dhcp->cookie = htonl(MAGIC_COOKIE); *p++ = DHO_MESSAGETYPE; *p++ = 1; *p++ = type; if (iface->clientid) { *p++ = DHO_CLIENTID; memcpy(p, iface->clientid, iface->clientid[0] + 1); p += iface->clientid[0] + 1; } if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) { if (type == DHCP_DECLINE || (type == DHCP_REQUEST && lease->addr.s_addr != iface->addr.s_addr)) { PUTADDR(DHO_IPADDRESS, lease->addr); if (lease->server.s_addr) PUTADDR(DHO_SERVERID, lease->server); } if (type == DHCP_RELEASE) { if (lease->server.s_addr) PUTADDR(DHO_SERVERID, lease->server); } } if (type == DHCP_DECLINE) { *p++ = DHO_MESSAGE; len = strlen(DAD); *p++ = len; memcpy(p, DAD, len); p += len; } if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST) PUTADDR(DHO_IPADDRESS, ifo->req_addr); if (type == DHCP_DISCOVER || type == DHCP_INFORM || 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; } else if (sz > MTU_MAX) { /* Even though our MTU could be greater than * MTU_MAX (1500) dhcpcd does not presently * handle DHCP packets any bigger. */ sz = MTU_MAX; } sz = htons(sz); memcpy(p, &sz, 2); p += 2; if (ifo->userclass[0]) { *p++ = DHO_USERCLASS; memcpy(p, ifo->userclass, ifo->userclass[0] + 1); p += ifo->userclass[0] + 1; } if (ifo->vendorclassid[0]) { *p++ = DHO_VENDORCLASSID; memcpy(p, ifo->vendorclassid, ifo->vendorclassid[0] + 1); p += ifo->vendorclassid[0] + 1; } if (type != DHCP_INFORM) { if (ifo->leasetime != 0) { *p++ = DHO_LEASETIME; *p++ = 4; ul = htonl(ifo->leasetime); memcpy(p, &ul, 4); p += 4; } } /* Regardless of RFC2132, we should always send a hostname * upto the first dot (the short hostname) as otherwise * confuses some DHCP servers when updating DNS. * The FQDN option should be used if a FQDN is required. */ if (ifo->options & DHCPCD_HOSTNAME && ifo->hostname[0]) { *p++ = DHO_HOSTNAME; hp = strchr(ifo->hostname, '.'); if (hp) len = hp - ifo->hostname; else len = strlen(ifo->hostname); *p++ = len; memcpy(p, ifo->hostname, len); p += len; } if (ifo->fqdn != FQDN_DISABLE && ifo->hostname[0]) { /* 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++ = (ifo->fqdn & 0x09) | 0x04; *p++ = 0; /* from server for PTR RR */ *p++ = 0; /* from server for A RR if S=1 */ ul = encode_rfc1035(ifo->hostname, p); *lp += ul; p += ul; } /* vendor is already encoded correctly, so just add it */ if (ifo->vendor[0]) { *p++ = DHO_VENDOR; memcpy(p, ifo->vendor, ifo->vendor[0] + 1); p += ifo->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(ifo->requestmask, opt->option))) continue; if (type == DHCP_INFORM && (opt->option == DHO_RENEWALTIME || opt->option == DHO_REBINDTIME)) continue; *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; /* We don't write BOOTP leases */ if (is_bootp(dhcp)) { unlink(iface->leasefile); return 0; } syslog(LOG_DEBUG, "%s: writing lease `%s'", iface->name, iface->leasefile); fd = open(iface->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0444); #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, 0444); } #endif if (fd == -1) { syslog(LOG_ERR, "%s: open: %m", iface->name); 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) { if (errno != ENOENT) syslog(LOG_ERR, "%s: open `%s': %m", iface->name, iface->leasefile); return NULL; } syslog(LOG_DEBUG, "%s: reading lease `%s'", iface->name, iface->leasefile); 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, *p; ssize_t bytes = 0; ssize_t r; e = data + dl; while (data < e) { c = *data++; if (c == '\0') { /* If rest is all NULL, skip it. */ for (p = data; p < e; p++) if (*p != '\0') break; if (p == e) break; } 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 */ case '|': /* FALLTHROUGH */ case '&': 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 & RFC3361) { if ((tmp = decode_rfc3361(dl, data)) == NULL) return -1; l = strlen(tmp); 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 & RFC5969) return decode_rfc5969(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; dl /= 2; } else if (type & SINT16) { l = 6; dl /= 2; } else if (type & UINT32) { l = 10; dl /= 4; } else if (type & SINT32) { l = 11; dl /= 4; } else if (type & IPV4) { l = 16; dl /= 4; } 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; if (len <= l) { bytes += len; break; } len -= l; bytes += l; s += l; } return bytes; } ssize_t configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, const struct if_options *ifo) { 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(ifo->nomask, opt->option)) continue; if (get_option_raw(dhcp, opt->option)) e++; } if (dhcp->yiaddr || dhcp->ciaddr) e += 5; if (*dhcp->bootfile && !(overl & 1)) e++; if (*dhcp->servername && !(overl & 2)) e++; return e; } ep = env; if (dhcp->yiaddr || dhcp->ciaddr) { /* Set some useful variables that we derive from the DHCP * message but are not necessarily in the options */ addr.s_addr = dhcp->yiaddr ? dhcp->yiaddr : dhcp->ciaddr; setvar(&ep, prefix, "ip_address", inet_ntoa(addr)); if (get_option_addr(&net, 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, dhcp, DHO_BROADCAST) == -1) { brd.s_addr = addr.s_addr | ~net.s_addr; setvar(&ep, prefix, "broadcast_address", inet_ntoa(brd)); } 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(ifo->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; } void get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp) { struct timeval now; lease->cookie = dhcp->cookie; /* BOOTP does not set yiaddr for replies when ciaddr is set. */ if (dhcp->yiaddr) lease->addr.s_addr = dhcp->yiaddr; else lease->addr.s_addr = dhcp->ciaddr; if (get_option_addr(&lease->net, dhcp, DHO_SUBNETMASK) == -1) lease->net.s_addr = get_netmask(lease->addr.s_addr); if (get_option_addr(&lease->brd, dhcp, DHO_BROADCAST) == -1) lease->brd.s_addr = lease->addr.s_addr | ~lease->net.s_addr; if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) == 0) { /* Ensure that we can use the lease */ get_monotonic(&now); if (now.tv_sec + (time_t)lease->leasetime < now.tv_sec) lease->leasetime = ~0U; /* Infinite lease */ } else lease->leasetime = ~0U; /* Default to infinite lease */ if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0) lease->renewaltime = 0; if (get_option_uint32(&lease->rebindtime, dhcp, DHO_REBINDTIME) != 0) lease->rebindtime = 0; if (get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0) lease->server.s_addr = INADDR_ANY; }