/*
* dhcpcd - DHCP client daemon
* Copyright (c) 2006-2010 Roy Marples <[email protected]>
* 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 "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

#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 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" },
       { 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" },
       { 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" },
       { 0, 0, NULL }
};

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;
}

#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;

       while (p < 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;
                       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;
}

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;
}


/* 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, int *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 && !(*opts & DHCPCD_CSR_WARNED)) {
                       *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_LENGTH_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_LENGTH_MIN)
               *p++ = DHO_PAD;
#endif

       *message = dhcp;
       return p - m;
}

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;
}