summaryrefslogblamecommitdiffstats
path: root/src/customdhcpcd/dhcp.c
blob: 7c41ad268270a1b3c8b6245a70d6ef6e6a2617b3 (plain) (tree)
1
  





























































                                                                             



                      

                 










                                       



                                          



                                       
 
                


                                                                   
                                                                        
 
























































































































                                                                      





                                      














                                                       

              















































































                                                                      
                 

                                

                 
                                
      
                          
                  

                                
      
         
 


                                   

                              




                                                                       

      
                         
 



                                                                      
 






                                                                      


                                                         
                                                        



                                                                              























































                                                              





                                                                      





















































                                                                   



                             

















                                  



                                                              

                                                        
 






















                                                        




                                                                      












































                                                            







                                                                       






















                                                 



                                                                               



















                                                       



                                                                                
















                                                   



                                                                  




























                                                                    









                                                                     








































                                                                 

























                                                     



































                                                                   













                                               








                                   
                  


                                  

                 


                                   









                                                                            


                                     
                 


                                     

                 


                                     


              






















                                                

                  



                                                           

      




























                                                          




                  





                                                      
 

                  

      



















































                                                                
 
/*
 * 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 <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <net/if_arp.h>

#include <arpa/inet.h>

#include <errno.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"

#include "common.h"
#include "dhcpcd.h"
#include "dhcp.h"
#include "interface.h"
#include "logger.h"
#include "socket.h"

#ifndef STAILQ_CONCAT
#define	STAILQ_CONCAT(head1, head2) do {				\
	if (!STAILQ_EMPTY((head2))) {					\
		*(head1)->stqh_last = (head2)->stqh_first;		\
		(head1)->stqh_last = (head2)->stqh_last;		\
		STAILQ_INIT((head2));					\
	}								\
} while (0)
#endif

typedef struct message
{
  int value;
  const char *name;
} dhcp_message_t;

static dhcp_message_t dhcp_messages[] =
{
  { DHCP_DISCOVER, "DHCP_DISCOVER" },
  { DHCP_OFFER,    "DHCP_OFFER" },
  { DHCP_REQUEST,  "DHCP_REQUEST" },
  { DHCP_DECLINE,  "DHCP_DECLINE" },
  { DHCP_ACK,      "DHCP_ACK" },
  { DHCP_NAK,      "DHCP_NAK" },
  { DHCP_RELEASE,  "DHCP_RELEASE" },
  { DHCP_INFORM,   "DHCP_INFORM" },
  { -1, NULL }
};

static const char *dhcp_message (int type)
{
  dhcp_message_t *d;
  for (d = dhcp_messages; d->name; d++)
    if (d->value == type)
      return (d->name);

  return (NULL);
}

ssize_t send_message (const interface_t *iface, const dhcp_t *dhcp,
                      uint32_t xid, char type, const options_t *options)
{
  struct udp_dhcp_packet *packet;
  dhcpmessage_t *message;
  unsigned char *m;
  unsigned char *p;
  unsigned char *n_params = NULL;
  size_t l;
  struct in_addr from;
  struct in_addr to;
  time_t up = uptime() - iface->start_uptime;
  uint32_t ul;
  uint16_t sz;
  size_t message_length;
  ssize_t retval;

  if (!iface || !options || !dhcp)
    return -1;

  memset (&from, 0, sizeof (from));
  memset (&to, 0, sizeof (to));

  if (type == DHCP_RELEASE)
    to.s_addr = dhcp->serveraddress.s_addr;

  message = xzalloc (sizeof (*message));
  m = (unsigned char *) message;
  p = (unsigned char *) &message->options;

  if ((type == DHCP_INFORM ||
       type == DHCP_RELEASE ||
       type == DHCP_REQUEST) &&
      ! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)))
    {
      message->ciaddr = iface->previous_address.s_addr;
      from.s_addr = iface->previous_address.s_addr;

      /* Just incase we haven't actually configured the address yet */
      if (type == DHCP_INFORM && iface->previous_address.s_addr == 0)
        message->ciaddr = dhcp->address.s_addr;

      /* Zero the address if we're currently on a different subnet */
      if (type == DHCP_REQUEST &&
          iface->previous_netmask.s_addr != dhcp->netmask.s_addr)
        message->ciaddr = from.s_addr = 0;

      if (from.s_addr != 0)
        to.s_addr = dhcp->serveraddress.s_addr;
    }

  message->op = DHCP_BOOTREQUEST;
  message->hwtype = iface->family;
  switch (iface->family)
    {
    case ARPHRD_ETHER:
    case ARPHRD_IEEE802:
      message->hwlen = ETHER_ADDR_LEN;
      memcpy (&message->chaddr, &iface->hwaddr,
              ETHER_ADDR_LEN);
      break;
    case ARPHRD_IEEE1394:
    case ARPHRD_INFINIBAND:
      message->hwlen = 0;
      if (message->ciaddr == 0)
        message->flags = htons (BROADCAST_FLAG);
      break;
    default:
      logger (LOG_ERR, "dhcp: unknown hardware type %d",
              iface->family);
    }

  if (up < 0 || up > (time_t) UINT16_MAX)
    message->secs = htons ((uint16_t) UINT16_MAX);
  else
    message->secs = htons (up);
  message->xid = xid;
  message->cookie = htonl (MAGIC_COOKIE);

  *p++ = DHCP_MESSAGETYPE;
  *p++ = 1;
  *p++ = type;

  if (type == DHCP_REQUEST)
    {
      *p++ = DHCP_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;
    }

  *p++ = DHCP_CLIENTID;
  *p++ = iface->clientid_len;
  memcpy (p, iface->clientid, iface->clientid_len);
  p += iface->clientid_len;

  if (type != DHCP_DECLINE && type != DHCP_RELEASE)
    {
      if (options->userclass_len > 0)
        {
          *p++ = DHCP_USERCLASS;
          *p++ = options->userclass_len;
          memcpy (p, &options->userclass, options->userclass_len);
          p += options->userclass_len;
        }

      if (*options->classid > 0)
        {
          *p++ = DHCP_CLASSID;
          *p++ = l = strlen (options->classid);
          memcpy (p, options->classid, l);
          p += l;
        }
    }

  if (type == DHCP_DISCOVER || type == DHCP_REQUEST)
    {
#define PUTADDR(_type, _val) { \
	*p++ = _type; \
	*p++ = 4; \
	memcpy (p, &_val.s_addr, 4); \
	p += 4; \
}
      if (IN_LINKLOCAL (ntohl (dhcp->address.s_addr)))
        logger (LOG_ERR,
                "cannot request a link local address");
      else
        {
          if (dhcp->address.s_addr &&
              dhcp->address.s_addr !=
              iface->previous_address.s_addr)
            {
              PUTADDR (DHCP_ADDRESS, dhcp->address);
              if (dhcp->serveraddress.s_addr)
                PUTADDR (DHCP_SERVERIDENTIFIER,
                         dhcp->serveraddress);
            }
        }
#undef PUTADDR

      if (options->leasetime != 0)
        {
          *p++ = DHCP_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])
        {
          if (options->fqdn == FQDN_DISABLE)
            {
              *p++ = DHCP_HOSTNAME;
              *p++ = l = strlen (options->hostname);
              memcpy (p, options->hostname, l);
              p += l;
            }
          else
            {
              /* Draft IETF DHC-FQDN option (81) */
              *p++ = DHCP_FQDN;
              *p++ = (l = strlen (options->hostname)) + 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 & 0x9;
              *p++ = 0; /* from server for PTR RR */
              *p++ = 0; /* from server for A RR if S=1 */
              memcpy (p, options->hostname, l);
              p += l;
            }
        }

      *p++ = DHCP_PARAMETERREQUESTLIST;
      n_params = p;
      *p++ = 0;
      /* Only request DNSSERVER in discover to keep the packets small.
       * RFC2131 Section 3.5 states that the REQUEST must include the
       * list from the DISCOVER message, so I think this is ok. */

      if (type == DHCP_DISCOVER && ! options->test)
        *p++ = DHCP_DNSSERVER;
      else
        {
          if (type != DHCP_INFORM)
            {
              *p++ = DHCP_RENEWALTIME;
              *p++ = DHCP_REBINDTIME;
            }
          *p++ = DHCP_NETMASK;
          *p++ = DHCP_BROADCAST;

          /* -S means request CSR and MSCSR
           * -SS means only request MSCSR incase DHCP message
           *  is too big */
          if (options->domscsr < 2)
            *p++ = DHCP_CSR;
          if (options->domscsr > 0)
            *p++ = DHCP_MSCSR;
          /* RFC 3442 states classless static routes should be
           * before routers and static routes as classless static
           * routes override them both */
          *p++ = DHCP_STATICROUTE;
          *p++ = DHCP_ROUTERS;
          *p++ = DHCP_HOSTNAME;
          *p++ = DHCP_DNSSEARCH;
          *p++ = DHCP_DNSDOMAIN;
          *p++ = DHCP_DNSSERVER;
#ifdef ENABLE_NIS
          *p++ = DHCP_NISDOMAIN;
          *p++ = DHCP_NISSERVER;
#endif
#ifdef ENABLE_NTP
          *p++ = DHCP_NTPSERVER;
#endif
          *p++ = DHCP_MTU;
#ifdef ENABLE_INFO
          *p++ = DHCP_ROOTPATH;
          *p++ = DHCP_SIPSERVER;
#endif
        }

      *n_params = p - n_params - 1;
    }
  *p++ = DHCP_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++ = DHCP_PAD;
#endif

  message_length = p - m;

  packet = xzalloc (sizeof (*packet));
  make_dhcp_packet (packet, (unsigned char *) message, message_length,
                    from, to);
  free (message);

  logger (LOG_DEBUG, "sending %s with xid 0x%x",
          dhcp_message (type), xid);
  retval = send_packet (iface, ETHERTYPE_IP, (unsigned char *) packet,
                        message_length +
                        sizeof (packet->ip) + sizeof (packet->udp));
  free (packet);
  return (retval);
}

/* 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 unsigned int decode_search (const unsigned char *p, int len, char *out)
{
  const unsigned char *r, *q = p;
  unsigned int count = 0, l, hops;

  while (q - p < len)
    {
      r = NULL;
      hops = 0;
      while ((l = *q++))
        {
          unsigned int label_type = l & 0xc0;
          if (label_type == 0x80 || label_type == 0x40)
            return 0;
          else if (label_type == 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 >= len)
                return 0;
            }
          else
            {
              /* straightforward name segment, add with '.' */
              count += l + 1;
              if (out)
                {
                  memcpy (out, q, l);
                  out += l;
                  *out++ = '.';
                }
              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;
}

/* Add our classless static routes to the routes variable
 * and return the last route set */
static struct route_head *decode_CSR (const unsigned char *p, int len)
{
  const unsigned char *q = p;
  unsigned int cidr;
  unsigned int ocets;
  struct route_head *routes = NULL;
  route_t *route;

  /* Minimum is 5 -first is CIDR and a router length of 4 */
  if (len < 5)
    return NULL;

  while (q - p < len)
    {
      if (! routes)
        {
          routes = xmalloc (sizeof (*routes));
          STAILQ_INIT (routes);
        }

      route = xzalloc (sizeof (*route));

      cidr = *q++;
      if (cidr > 32)
        {
          logger (LOG_ERR,
                  "invalid CIDR of %d in classless static route",
                  cidr);
          free_route (routes);
          return (NULL);
        }
      ocets = (cidr + 7) / 8;

      if (ocets > 0)
        {
          memcpy (&route->destination.s_addr, q, (size_t) ocets);
          q += ocets;
        }

      /* Now enter the netmask */
      if (ocets > 0)
        {
          memset (&route->netmask.s_addr, 255, (size_t) ocets - 1);
          memset ((unsigned char *) &route->netmask.s_addr +
                  (ocets - 1),
                  (256 - (1 << (32 - cidr) % 8)), 1);
        }

      /* Finally, snag the router */
      memcpy (&route->gateway.s_addr, q, 4);
      q += 4;

      STAILQ_INSERT_TAIL (routes, route, entries);
    }

  return (routes);
}

void free_dhcp (dhcp_t *dhcp)
{
  if (! dhcp)
    return;

  free_route (dhcp->routes);
  free (dhcp->hostname);
  free_address (dhcp->dnsservers);
  free (dhcp->dnsdomain);
  free (dhcp->dnssearch);
  free_address (dhcp->ntpservers);
  free (dhcp->nisdomain);
  free_address (dhcp->nisservers);
  free (dhcp->rootpath);
  free (dhcp->sipservers);
  if (dhcp->fqdn)
    {
      free (dhcp->fqdn->name);
      free (dhcp->fqdn);
    }
}


static bool dhcp_add_address (struct address_head **addresses,
                              const unsigned char *data,
                              int length)
{
  int i;
  address_t *address;

  for (i = 0; i < length; i += 4)
    {
      /* Sanity check */
      if (i + 4 > length)
        {
          logger (LOG_ERR, "invalid address length");
          return (false);
        }

      if (*addresses == NULL)
        {
          *addresses = xmalloc (sizeof (**addresses));
          STAILQ_INIT (*addresses);
        }
      address = xzalloc (sizeof (*address));
      memcpy (&address->address.s_addr, data + i, 4);
      STAILQ_INSERT_TAIL (*addresses, address, entries);
    }

  return (true);
}

#ifdef ENABLE_INFO
static char *decode_sipservers (const unsigned char *data, int length)
{
  char *sip = NULL;
  char *p;
  const char encoding = *data++;
  struct in_addr addr;
  size_t len;

  length--;

  switch (encoding)
    {
    case 0:
      if ((len = decode_search (data, length, NULL)) > 0)
        {
          sip = xmalloc (len);
          decode_search (data, length, sip);
        }
      break;

    case 1:
      if (length == 0 || length % 4 != 0)
        {
          logger (LOG_ERR,
                  "invalid length %d for option 120",
                  length + 1);
          break;
        }
      len = ((length / 4) * (4 * 4)) + 1;
      sip = p = xmalloc (len);
      while (length != 0)
        {
          memcpy (&addr.s_addr, data, 4);
          data += 4;
          p += snprintf (p, len - (p - sip),
                         "%s ", inet_ntoa (addr));
          length -= 4;
        }
      *--p = '\0';
      break;

    default:
      logger (LOG_ERR, "unknown sip encoding %d", encoding);
      break;
    }

  return (sip);
}
#endif

/* 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));
}

static struct route_head *decode_routes (const unsigned char *data, int length)
{
  int i;
  struct route_head *head = NULL;
  route_t *route;

  for (i = 0; i < length; i += 8)
    {
      if (! head)
        {
          head = xmalloc (sizeof (*head));
          STAILQ_INIT (head);
        }
      route = xzalloc (sizeof (*route));
      memcpy (&route->destination.s_addr, data + i, 4);
      memcpy (&route->gateway.s_addr, data + i + 4, 4);
      route->netmask.s_addr =
        route_netmask (route->destination.s_addr);
      STAILQ_INSERT_TAIL (head, route, entries);
    }

  return (head);
}

static struct route_head *decode_routers (const unsigned char *data, int length)
{
  int i;
  struct route_head *head = NULL;
  route_t *route = NULL;

  for (i = 0; i < length; i += 4)
    {
      if (! head)
        {
          head = xmalloc (sizeof (*head));
          STAILQ_INIT (head);
        }
      route = xzalloc (sizeof (*route));
      memcpy (&route->gateway.s_addr, data + i, 4);
      STAILQ_INSERT_TAIL (head, route, entries);
    }

  return (head);
}

int parse_dhcpmessage (dhcp_t *dhcp, const dhcpmessage_t *message)
{
  const unsigned char *p = message->options;
  const unsigned char *end = p; /* Add size later for gcc-3 issue */
  unsigned char option;
  unsigned char length;
  unsigned int len = 0;
  int retval = -1;
  struct timeval tv;
  struct route_head *routers = NULL;
  struct route_head *routes = NULL;
  struct route_head *csr = NULL;
  struct route_head *mscsr = NULL;
  bool in_overload = false;
  bool parse_sname = false;
  bool parse_file = false;

  end += sizeof (message->options);

  if (gettimeofday (&tv, NULL) == -1)
    {
      logger (LOG_ERR, "gettimeofday: %s", strerror (errno));
      return (-1);
    }

  dhcp->address.s_addr = message->yiaddr;
  dhcp->leasedfrom = tv.tv_sec;
  dhcp->frominfo = false;
  dhcp->address.s_addr = message->yiaddr;
  strlcpy (dhcp->servername, (char *) message->servername,
           sizeof (dhcp->servername));

#define LEN_ERR \
	{ \
		logger (LOG_ERR, "invalid length %d for option %d", \
			length, option); \
		p += length; \
		continue; \
	}

parse_start:
  while (p < end)
    {
      option = *p++;
      if (! option)
        continue;

      if (option == DHCP_END)
        goto eexit;

      length = *p++;

      if (option != DHCP_PAD && length == 0)
        {
          logger (LOG_ERR, "option %d has zero length", option);
          retval = -1;
          goto eexit;
        }

      if (p + length >= end)
        {
          logger (LOG_ERR, "dhcp option exceeds message length");
          retval = -1;
          goto eexit;
        }

      switch (option)
        {
        case DHCP_MESSAGETYPE:
          retval = (int) * p;
          p += length;
          continue;

        default:
          if (length == 0)
            {
              logger (LOG_DEBUG,
                      "option %d has zero length, skipping",
                      option);
              continue;
            }
        }

#define LENGTH(_length) \
		if (length != _length) \
		LEN_ERR;
#define MIN_LENGTH(_length) \
		if (length < _length) \
		LEN_ERR;
#define MULT_LENGTH(_mult) \
		if (length % _mult != 0) \
		LEN_ERR;
#define GET_UINT8(_val) \
		LENGTH (sizeof (uint8_t)); \
		memcpy (&_val, p, sizeof (uint8_t));
#define GET_UINT16(_val) \
		LENGTH (sizeof (uint16_t)); \
		memcpy (&_val, p, sizeof (uint16_t));
#define GET_UINT32(_val) \
		LENGTH (sizeof (uint32_t)); \
		memcpy (&_val, p, sizeof (uint32_t));
#define GET_UINT16_H(_val) \
		GET_UINT16 (_val); \
		_val = ntohs (_val);
#define GET_UINT32_H(_val) \
		GET_UINT32 (_val); \
		_val = ntohl (_val);

      switch (option)
        {
        case DHCP_ADDRESS:
          GET_UINT32 (dhcp->address.s_addr);
          break;
        case DHCP_NETMASK:
          GET_UINT32 (dhcp->netmask.s_addr);
          break;
        case DHCP_BROADCAST:
          GET_UINT32 (dhcp->broadcast.s_addr);
          break;
        case DHCP_SERVERIDENTIFIER:
          GET_UINT32 (dhcp->serveraddress.s_addr);
          break;
        case DHCP_LEASETIME:
          GET_UINT32_H (dhcp->leasetime);
          break;
        case DHCP_RENEWALTIME:
          GET_UINT32_H (dhcp->renewaltime);
          break;
        case DHCP_REBINDTIME:
          GET_UINT32_H (dhcp->rebindtime);
          break;
        case DHCP_MTU:
          GET_UINT16_H (dhcp->mtu);
          /* Minimum legal mtu is 68 accoridng to
           * RFC 2132. In practise it's 576 which is the
           * minimum maximum message size. */
          if (dhcp->mtu < MTU_MIN)
            {
              logger (LOG_DEBUG,
                      "MTU %d is too low, minimum is %d; ignoring",
                      dhcp->mtu, MTU_MIN);
              dhcp->mtu = 0;
            }
          break;

#undef GET_UINT32_H
#undef GET_UINT32
#undef GET_UINT16_H
#undef GET_UINT16
#undef GET_UINT8

#define GETSTR(_var) { \
	MIN_LENGTH (sizeof (char)); \
	if (_var) free (_var); \
	_var = xmalloc ((size_t) length + 1); \
	memcpy (_var, p, (size_t) length); \
	memset (_var + length, 0, 1); \
}
        case DHCP_HOSTNAME:
          GETSTR (dhcp->hostname);
          break;
        case DHCP_DNSDOMAIN:
          GETSTR (dhcp->dnsdomain);
          break;
        case DHCP_MESSAGE:
          GETSTR (dhcp->message);
          break;
#ifdef ENABLE_INFO
        case DHCP_ROOTPATH:
          GETSTR (dhcp->rootpath);
          break;
#endif
#ifdef ENABLE_NIS
        case DHCP_NISDOMAIN:
          GETSTR (dhcp->nisdomain);
          break;
#endif
#undef GETSTR

#define GETADDR(_var) \
				MULT_LENGTH (4); \
				if (! dhcp_add_address (&_var, p, length)) \
				{ \
					retval = -1; \
					goto eexit; \
				}
        case DHCP_DNSSERVER:
          GETADDR (dhcp->dnsservers);
          break;
#ifdef ENABLE_NTP
        case DHCP_NTPSERVER:
          GETADDR (dhcp->ntpservers);
          break;
#endif
#ifdef ENABLE_NIS
        case DHCP_NISSERVER:
          GETADDR (dhcp->nisservers);
          break;
#endif
#undef GETADDR

        case DHCP_DNSSEARCH:
          MIN_LENGTH (1);
          free (dhcp->dnssearch);
          len = decode_search (p, length, NULL);
          if (len > 0)
            {
              dhcp->dnssearch = xmalloc (len);
              decode_search (p, length,
                             dhcp->dnssearch);
            }
          break;

        case DHCP_CSR:
          MIN_LENGTH (5);
          free_route (csr);
          csr = decode_CSR (p, length);
          break;

        case DHCP_MSCSR:
          MIN_LENGTH (5);
          free_route (mscsr);
          mscsr = decode_CSR (p, length);
          break;

#ifdef ENABLE_INFO
        case DHCP_SIPSERVER:
          free (dhcp->sipservers);
          dhcp->sipservers = decode_sipservers (p, length);
          break;
#endif

        case DHCP_STATICROUTE:
          MULT_LENGTH (8);
          free_route (routes);
          routes = decode_routes (p, length);
          break;

        case DHCP_ROUTERS:
          MULT_LENGTH (4);
          free_route (routers);
          routers = decode_routers (p, length);
          break;

        case DHCP_OPTIONSOVERLOADED:
          LENGTH (1);
          /* The overloaded option in an overloaded option
           * should be ignored, overwise we may get an
           * infinite loop */
          if (! in_overload)
            {
              if (*p & 1)
                parse_file = true;
              if (*p & 2)
                parse_sname = true;
            }
          break;

        case DHCP_FQDN:
          /* We ignore replies about FQDN */
          break;

#undef LENGTH
#undef MIN_LENGTH
#undef MULT_LENGTH

        default:
          logger (LOG_DEBUG,
                  "no facility to parse DHCP code %u",
                  option);
          break;
        }

      p += length;
    }

eexit:
  /* We may have options overloaded, so go back and grab them */
  if (parse_file)
    {
      parse_file = false;
      p = message->bootfile;
      end = p + sizeof (message->bootfile);
      in_overload = true;
      goto parse_start;
    }
  else if (parse_sname)
    {
      parse_sname = false;
      p = message->servername;
      end = p + sizeof (message->servername);
      memset (dhcp->servername, 0, sizeof (dhcp->servername));
      in_overload = true;
      goto parse_start;
    }

  /* Fill in any missing fields */
  if (! dhcp->netmask.s_addr)
    dhcp->netmask.s_addr = get_netmask (dhcp->address.s_addr);
  if (! dhcp->broadcast.s_addr)
    dhcp->broadcast.s_addr = dhcp->address.s_addr |
                             ~dhcp->netmask.s_addr;

  /* If we have classess static routes then we discard
   * static routes and routers according to RFC 3442 */
  if (csr)
    {
      dhcp->routes = csr;
      free_route (mscsr);
      free_route (routers);
      free_route (routes);
    }
  else if (mscsr)
    {
      dhcp->routes = mscsr;
      free_route (routers);
      free_route (routes);
    }
  else
    {
      /* Ensure that we apply static routes before routers */
      if (! routes)
        routes = routers;
      else if (routers)
        STAILQ_CONCAT (routes, routers);
      dhcp->routes = routes;
    }

  return (retval);
}