aboutsummaryrefslogtreecommitdiffstats
path: root/dhcp.c
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:22 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:22 -0800
commitf7c5421560640d23fc10803b9d59a9ff1d83e467 (patch)
treed6ae69d0d3f4a4d760a3254ec326bca4a8afacfe /dhcp.c
parentd97c47cad830d00c9da685cc4ea157d6185f6c97 (diff)
downloadexternal_dhcpcd-f7c5421560640d23fc10803b9d59a9ff1d83e467.zip
external_dhcpcd-f7c5421560640d23fc10803b9d59a9ff1d83e467.tar.gz
external_dhcpcd-f7c5421560640d23fc10803b9d59a9ff1d83e467.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'dhcp.c')
-rw-r--r--dhcp.c1251
1 files changed, 1251 insertions, 0 deletions
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 <roy@marples.name>
+ * 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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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;
+}