1/* dnsmasq is Copyright (c) 2000-2003 Simon Kelley
2
3   This program is free software; you can redistribute it and/or modify
4   it under the terms of the GNU General Public License as published by
5   the Free Software Foundation; version 2 dated June, 1991.
6
7   This program is distributed in the hope that it will be useful,
8   but WITHOUT ANY WARRANTY; without even the implied warranty of
9   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10   GNU General Public License for more details.
11*/
12
13/* Author's email: simon@thekelleys.org.uk */
14
15#include "dnsmasq.h"
16
17#define BOOTREQUEST              1
18#define BOOTREPLY                2
19#define DHCP_COOKIE              0x63825363
20
21/* The Linux in-kernel DHCP client silently ignores any packet
22   smaller than this. Sigh...........   */
23#define MIN_PACKETSZ             300
24
25#define OPTION_PAD               0
26#define OPTION_NETMASK           1
27#define OPTION_ROUTER            3
28#define OPTION_DNSSERVER         6
29#define OPTION_HOSTNAME          12
30#define OPTION_DOMAINNAME        15
31#define OPTION_BROADCAST         28
32#define OPTION_REQUESTED_IP      50
33#define OPTION_LEASE_TIME        51
34#define OPTION_OVERLOAD          52
35#define OPTION_MESSAGE_TYPE      53
36#define OPTION_SERVER_IDENTIFIER 54
37#define OPTION_REQUESTED_OPTIONS 55
38#define OPTION_MESSAGE           56
39#define OPTION_MAXMESSAGE        57
40#define OPTION_T1                58
41#define OPTION_T2                59
42#define OPTION_VENDOR_ID         60
43#define OPTION_CLIENT_ID         61
44#define OPTION_USER_CLASS        77
45#define OPTION_SUBNET_SELECT     118
46#define OPTION_END               255
47
48#define DHCPDISCOVER             1
49#define DHCPOFFER                2
50#define DHCPREQUEST              3
51#define DHCPDECLINE              4
52#define DHCPACK                  5
53#define DHCPNAK                  6
54#define DHCPRELEASE              7
55#define DHCPINFORM               8
56
57static unsigned char *option_put(unsigned char *p, unsigned char *end, int opt, int len, unsigned int val);
58static unsigned char *option_end(unsigned char *p, unsigned char *end, struct dhcp_packet *start);
59static unsigned char *option_put_string(unsigned char *p, unsigned char *end, int opt, char *string);
60static void bootp_option_put(struct dhcp_packet *mess, char *filename, char *sname);
61static int option_len(unsigned char *opt);
62static void *option_ptr(unsigned char *opt);
63static struct in_addr option_addr(unsigned char *opt);
64static unsigned int option_uint(unsigned char *opt, int size);
65static void log_packet(char *type, struct in_addr *addr, unsigned char *hwaddr, char *interface, char *string);
66static unsigned char *option_find(struct dhcp_packet *mess, int size, int opt_type);
67static unsigned char *do_req_options(struct dhcp_context *context,
68				     unsigned char *p, unsigned char *end,
69				     unsigned char *req_options,
70				     struct daemon *daemon,
71				     char *hostname,
72				     struct in_addr iface_addr,
73				     struct dhcp_netid *netid,
74				     struct in_addr subnet_addr);
75
76static int have_config(struct dhcp_config *config, unsigned int mask)
77{
78  return config && (config->flags & mask);
79}
80
81int dhcp_reply(struct daemon *daemon, struct in_addr iface_addr, char *iface_name, unsigned int sz, time_t now)
82{
83  struct dhcp_context *context, *context_tmp;
84  unsigned char *opt, *clid;
85  struct dhcp_lease *lease, *ltmp;
86  struct dhcp_vendor *vendor;
87  int clid_len;
88  struct dhcp_packet *mess = &daemon->dhcp_packet->data;
89  unsigned char *p = mess->options + sizeof(u32); /* skip cookie */
90  unsigned char *end = (unsigned char *)(daemon->dhcp_packet + 1);
91  char *hostname = NULL;
92  char *req_options = NULL;
93  char *message = NULL;
94  unsigned int renewal_time, expires_time, def_time;
95  struct dhcp_config *config;
96  struct dhcp_netid *netid = NULL;
97  struct in_addr addr, subnet_addr;
98  unsigned short fuzz = 0;
99  unsigned int mess_type = 0;
100
101  subnet_addr.s_addr = 0;
102
103  if (mess->op != BOOTREQUEST)
104    return 0;
105
106  /* Token ring is supported when we have packet sockets
107     to make the HW headers for us. We don't have the code to build
108     token ring headers when using BPF. We rely on the fact that
109     token ring hwaddrs are the same size as ethernet hwaddrs. */
110
111#ifdef HAVE_BPF
112  if (mess->htype != ARPHRD_ETHER)
113#else
114  if (mess->htype != ARPHRD_ETHER && mess->htype != ARPHRD_IEEE802)
115#endif
116    {
117      syslog(LOG_WARNING, "DHCP request for unsupported hardware type (%d) recieved on %s",
118	     mess->htype, iface_name);
119      return 0;
120    }
121
122  if (mess->hlen != ETHER_ADDR_LEN)
123    return 0;
124
125  /* check for DHCP rather than BOOTP */
126  if ((opt = option_find(mess, sz, OPTION_MESSAGE_TYPE)))
127    {
128      mess_type = option_uint(opt, 1);
129
130      /* only insist on a cookie for DHCP. */
131      if (*((u32 *)&mess->options) != htonl(DHCP_COOKIE))
132	return 0;
133
134      /* Some buggy clients set ciaddr when they shouldn't, so clear that here since
135	 it can affect the context-determination code. */
136      if ((option_find(mess, sz, OPTION_REQUESTED_IP) || mess_type == DHCPDISCOVER))
137	mess->ciaddr.s_addr = 0;
138
139      /* Check for RFC3011 subnet selector */
140      if ((opt = option_find(mess, sz, OPTION_SUBNET_SELECT)))
141	subnet_addr = option_addr(opt);
142    }
143
144  /* Determine network for this packet. If the machine has an address already, and we don't have
145     have a giaddr or explicit subnet selector, use the ciaddr. This is necessary because a
146     machine which got a lease via a relay won't use the relay to renew. */
147  addr =
148    subnet_addr.s_addr ? subnet_addr :
149    (mess->giaddr.s_addr ? mess->giaddr :
150     (mess->ciaddr.s_addr ? mess->ciaddr : iface_addr));
151
152  /* More than one context may match, we build a chain of them all on ->current
153     Note that if netmasks, netid or lease times don't match, odd things may happen. */
154
155  for (context = NULL, context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next)
156    if (context_tmp->netmask.s_addr  &&
157	is_same_net(addr, context_tmp->start, context_tmp->netmask) &&
158	is_same_net(addr, context_tmp->end, context_tmp->netmask))
159      {
160	context_tmp->current = context;
161	context = context_tmp;
162
163	/* start to build netid chain */
164	if (context_tmp->netid.net)
165	  {
166	    context_tmp->netid.next = netid;
167	    netid = &context_tmp->netid;
168	  }
169      }
170
171  if (!context)
172    {
173      syslog(LOG_WARNING, "no address range available for DHCP request %s %s",
174	     subnet_addr.s_addr ? "with subnet selector" : "via",
175	     subnet_addr.s_addr ? inet_ntoa(subnet_addr) : (mess->giaddr.s_addr ? inet_ntoa(mess->giaddr) : iface_name));
176      return 0;
177    }
178
179  mess->op = BOOTREPLY;
180
181  if (mess_type == 0)
182    {
183      /* BOOTP request */
184      config = find_config(daemon->dhcp_conf, context, NULL, 0, mess->chaddr, NULL);
185      if (have_config(config, CONFIG_ADDR) &&
186	  !have_config(config, CONFIG_DISABLE) &&
187	  !lease_find_by_addr(config->addr))
188	{
189	  struct dhcp_netid id;
190	  char save = mess->file[128];
191	  end = mess->options + 64; /* BOOTP vend area is only 64 bytes */
192	  mess->yiaddr = config->addr;
193	  mess->siaddr = daemon->dhcp_next_server.s_addr ? daemon->dhcp_next_server : iface_addr;
194	  if (have_config(config, CONFIG_NAME))
195	    hostname = config->hostname;
196	  if (have_config(config, CONFIG_NETID))
197	    {
198	      config->netid.next = netid;
199	      netid = &config->netid;
200	    }
201	  /* Match incoming filename field as a netid. */
202	  if (mess->file[0])
203	    {
204	      mess->file[128] = 0; /* ensure zero term. */
205	      id.net = mess->file;
206	      id.next = netid;
207	      netid = &id;
208	    }
209	  p = do_req_options(context, p, end, NULL, daemon,
210			     hostname, iface_addr, netid, subnet_addr);
211	  /* must do this after do_req_options since it overwrites filename field. */
212	  bootp_option_put(mess, daemon->dhcp_file, daemon->dhcp_sname);
213	  p = option_end(p, end, mess);
214	  log_packet(NULL, &config->addr, mess->chaddr, iface_name, NULL);
215	  mess->file[128] = save;
216	  return p - (unsigned char *)mess;
217	}
218      return 0;
219    }
220
221  /* If there is no client identifier option, use the hardware address */
222  if ((opt = option_find(mess, sz, OPTION_CLIENT_ID)))
223    {
224      clid = option_ptr(opt);
225      clid_len = option_len(opt);
226    }
227  else
228    {
229      clid =  mess->chaddr;
230      clid_len = 0;
231    }
232
233  config = find_config(daemon->dhcp_conf, context, clid, clid_len, mess->chaddr, NULL);
234
235  if (have_config(config, CONFIG_NAME))
236    hostname = config->hostname;
237  else if ((opt = option_find(mess, sz, OPTION_HOSTNAME)))
238    {
239      int len = option_len(opt);
240      hostname = daemon->dhcp_buff;
241      memcpy(hostname, option_ptr(opt), len);
242      /* May not be zero terminated */
243      hostname[len] = 0;
244      /* ensure there are no strange chars in there */
245      if (!canonicalise(hostname))
246	hostname = NULL;
247      else
248	{
249	  char *dot = strchr(hostname, '.');
250	  if (dot)
251	    {
252	      if (!daemon->domain_suffix || !hostname_isequal(dot+1, daemon->domain_suffix))
253		{
254		  syslog(LOG_WARNING, "Ignoring DHCP host name %s because it has an illegal domain part", hostname);
255		  hostname = NULL;
256		}
257	      else
258		{
259		  *dot = 0; /* truncate */
260		  if (strlen(hostname) == 0)
261		    hostname = NULL; /* nothing left */
262		}
263	    }
264
265	  /* Search again now we have a hostname.
266	     Only accept configs without CLID and HWADDR here, (they won't match)
267	     to avoid impersonation by name. */
268	  if (!config)
269	    {
270	      struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, mess->chaddr, hostname);
271	      if (!have_config(new, CONFIG_CLID) && !have_config(new, CONFIG_HWADDR))
272		config = new;
273	    }
274	}
275    }
276
277  if (have_config(config, CONFIG_NETID))
278    {
279      config->netid.next = netid;
280      netid = &config->netid;
281    }
282
283  /* Theres a chance that carefully chosen data could match the same
284     vendor/user option twice and make a loop in the netid chain. */
285  for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
286    vendor->used = 0;
287
288  if ((opt = option_find(mess, sz, OPTION_VENDOR_ID)))
289    for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
290      if (vendor->is_vendor && !vendor->used)
291	{
292	  int i;
293	  for (i = 0; i <= (option_len(opt) - vendor->len); i++)
294	    if (memcmp(vendor->data, option_ptr(opt)+i, vendor->len) == 0)
295	      {
296		vendor->used = 1;
297		vendor->netid.next = netid;
298		netid = &vendor->netid;
299		break;
300	      }
301	}
302
303  if ((opt = option_find(mess, sz, OPTION_USER_CLASS)))
304    {
305      unsigned char *ucp =  option_ptr(opt);
306      int j;
307      for (j = 0; j < option_len(opt); j += ucp[j] + 1)
308	for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
309	  if (!vendor->is_vendor && !vendor->used)
310	    {
311	      int i;
312	      for (i = 0; i <= (ucp[j] - vendor->len); i++)
313		if (memcmp(vendor->data, &ucp[j+i+1], vendor->len) == 0)
314		  {
315		    vendor->used = 1;
316		    vendor->netid.next = netid;
317		    netid = &vendor->netid;
318		    break;
319		  }
320	    }
321    }
322
323  /* Can have setting to ignore the client ID for a particular MAC address or hostname */
324  if (have_config(config, CONFIG_NOCLID))
325    {
326      clid =  mess->chaddr;
327      clid_len = 0;
328    }
329
330  /* do we have a lease in store? */
331  lease = lease_find_by_client(clid, clid_len);
332
333  def_time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
334
335  if ((opt = option_find(mess, sz, OPTION_LEASE_TIME)))
336    {
337      unsigned int req_time = option_uint(opt, 4);
338
339      if (def_time == 0xffffffff ||
340	  (req_time != 0xffffffff && req_time < def_time))
341	expires_time = renewal_time = req_time;
342      else
343	expires_time = renewal_time = def_time;
344    }
345  else
346    {
347      renewal_time = def_time;
348      if (lease)
349	expires_time = (unsigned int)difftime(lease->expires, now);
350      else
351	expires_time = def_time;
352    }
353
354  if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS)))
355    {
356      int len = option_len(opt);
357      req_options = daemon->dhcp_buff2;
358      memcpy(req_options, option_ptr(opt), len);
359      req_options[len] = OPTION_END;
360    }
361
362  switch (mess_type)
363    {
364    case DHCPDECLINE:
365      if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER)) ||
366	  (iface_addr.s_addr != option_addr(opt).s_addr))
367	return 0;
368
369      /* sanitise any message. Paranoid? Moi? */
370      if ((opt = option_find(mess, sz, OPTION_MESSAGE)))
371	{
372	  char *p = option_ptr(opt), *q = daemon->dhcp_buff;
373	  int i;
374
375	  for (i = option_len(opt); i > 0; i--)
376	    {
377	      char c = *p++;
378	      if (isprint(c))
379		*q++ = c;
380	    }
381	  *q++ = 0; /* add terminator */
382	  message = daemon->dhcp_buff;
383	}
384
385      if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP)))
386	return 0;
387
388      log_packet("DECLINE", option_ptr(opt), mess->chaddr, iface_name, message);
389
390      if (lease && lease->addr.s_addr == option_addr(opt).s_addr)
391	lease_prune(lease, now);
392
393      if (have_config(config, CONFIG_ADDR) &&
394	  config->addr.s_addr == option_addr(opt).s_addr)
395	{
396	  syslog(LOG_WARNING, "disabling DHCP static address %s", inet_ntoa(config->addr));
397	  config->flags &= ~CONFIG_ADDR ;
398	}
399      else
400	/* make sure this host gets a different address next time. */
401	for (; context; context = context->current)
402	  context->addr_epoch++;
403
404      return 0;
405
406    case DHCPRELEASE:
407      if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER)) ||
408	  (iface_addr.s_addr != option_addr(opt).s_addr))
409	return 0;
410
411      log_packet("RELEASE", &mess->ciaddr, mess->chaddr, iface_name, NULL);
412
413      if (lease && lease->addr.s_addr == mess->ciaddr.s_addr)
414	lease_prune(lease, now);
415
416      return 0;
417
418    case DHCPDISCOVER:
419      if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP)))
420	addr = option_addr(opt);
421      if (have_config(config, CONFIG_DISABLE))
422	message = "ignored";
423      else if (have_config(config, CONFIG_ADDR) &&
424               (!(ltmp = lease_find_by_addr(config->addr)) || ltmp == lease))
425	mess->yiaddr = config->addr;
426      else if (lease && address_available(context, lease->addr))
427	mess->yiaddr = lease->addr;
428      else if (opt && address_available(context, addr) && !lease_find_by_addr(addr) &&
429	       !config_find_by_address(daemon->dhcp_conf, addr))
430	mess->yiaddr = addr;
431      else if (!address_allocate(context, daemon, &mess->yiaddr, mess->chaddr))
432	message = "no address available";
433      log_packet("DISCOVER", opt ? &addr : NULL, mess->chaddr, iface_name, message);
434
435      if (message)
436	return 0;
437
438      bootp_option_put(mess, daemon->dhcp_file, daemon->dhcp_sname);
439      mess->siaddr = daemon->dhcp_next_server.s_addr ? daemon->dhcp_next_server : iface_addr;
440      p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPOFFER);
441      p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
442      p = option_put(p, end, OPTION_LEASE_TIME, 4, expires_time);
443      /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
444      if (expires_time != 0xffffffff)
445	{
446	  p = option_put(p, end, OPTION_T1, 4, (expires_time/2));
447	  p = option_put(p, end, OPTION_T2, 4, ((expires_time * 7)/8));
448	}
449      p = do_req_options(context, p, end, req_options, daemon,
450			 NULL, iface_addr, netid, subnet_addr);
451      p = option_end(p, end, mess);
452
453      log_packet("OFFER" , &mess->yiaddr, mess->chaddr, iface_name, NULL);
454      return p - (unsigned char *)mess;
455
456    case DHCPREQUEST:
457      if (have_config(config, CONFIG_DISABLE))
458	message = "disabled";
459      else if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP)))
460	{
461	  /* SELECTING  or INIT_REBOOT */
462	  mess->yiaddr = option_addr(opt);
463
464	  if ((opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER)))
465	    {
466	      /* SELECTING */
467	      if (iface_addr.s_addr != option_addr(opt).s_addr)
468		return 0;
469
470	      /* If a lease exists for this host and another address, squash it. */
471	      if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
472		{
473		  lease_prune(lease, now);
474		  lease = NULL;
475		}
476
477	      if (!lease)
478		{
479		  if (lease_find_by_addr(mess->yiaddr))
480		    message = "address in use";
481		  else if (!(lease = lease_allocate(clid, clid_len, mess->yiaddr)))
482		    message = "no leases left";
483		}
484	    }
485	  else
486	    {
487	      /* INIT-REBOOT */
488	      if (!lease)
489		return 0;
490
491	      if (lease->addr.s_addr != mess->yiaddr.s_addr)
492		message = "wrong address";
493	    }
494	}
495      else
496	{
497	  /* RENEWING or REBINDING */
498	  /* Must exist a lease for this address */
499	  if (!lease || mess->ciaddr.s_addr != lease->addr.s_addr)
500	    message = "lease not found";
501
502	  /* desynchronise renewals */
503	  fuzz = rand16();
504	  while (fuzz > (renewal_time/16))
505	    fuzz = fuzz/2;
506
507	  mess->yiaddr = mess->ciaddr;
508	}
509
510      if (!message)
511	{
512	  struct dhcp_config *addr_config;
513	  /* If a machine moves networks whilst it has a lease, we catch that here. */
514	  if (!is_same_net(mess->yiaddr, context->start, context->netmask))
515	    message = "wrong network";
516
517	  /* Check for renewal of a lease which is now outside the allowed range. */
518	  else if (!address_available(context, mess->yiaddr) &&
519		   (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr))
520	    message = "address no longer available";
521
522	  /* Check if a new static address has been configured. Be very sure that
523	     when the client does DISCOVER, it will get the static address, otherwise
524	     an endless protocol loop will ensue. */
525
526	  else if (have_config(config, CONFIG_ADDR) && !lease_find_by_addr(config->addr))
527	    message = "static lease available";
528
529	  /* Check to see if the address is reserved as a static address for another host */
530	  else if ((addr_config = config_find_by_address(daemon->dhcp_conf, mess->yiaddr)) && addr_config != config)
531	    message ="address reserved";
532	}
533
534      log_packet("REQUEST", &mess->yiaddr, mess->chaddr, iface_name, NULL);
535
536      if (message)
537	{
538	  log_packet("NAK", &mess->yiaddr, mess->chaddr, iface_name, message);
539
540	  mess->siaddr.s_addr = mess->yiaddr.s_addr = mess->ciaddr.s_addr = 0;
541	  bootp_option_put(mess, NULL, NULL);
542	  p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPNAK);
543	  p = option_put_string(p, end, OPTION_MESSAGE, message);
544	  p = option_end(p, end, mess);
545	  mess->flags |= htons(0x8000); /* broadcast */
546	  return p - (unsigned char *)mess;
547	}
548
549      log_packet("ACK", &mess->yiaddr, mess->chaddr, iface_name, hostname);
550
551      lease_set_hwaddr(lease, mess->chaddr);
552      if (hostname)
553	lease_set_hostname(lease, hostname, daemon->domain_suffix);
554      lease_set_expires(lease, renewal_time == 0xffffffff ? 0 : now + (time_t)renewal_time);
555
556      bootp_option_put(mess, daemon->dhcp_file, daemon->dhcp_sname);
557      mess->siaddr = daemon->dhcp_next_server.s_addr ? daemon->dhcp_next_server : iface_addr;
558      p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
559      p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
560      p = option_put(p, end, OPTION_LEASE_TIME, 4, renewal_time);
561      if (renewal_time != 0xffffffff)
562	{
563	  p = option_put(p, end, OPTION_T1, 4, (renewal_time/2) - fuzz);
564	  p = option_put(p, end, OPTION_T2, 4, ((renewal_time * 7)/8) - fuzz);
565	}
566      p = do_req_options(context, p, end, req_options, daemon,
567			 hostname, iface_addr, netid, subnet_addr);
568      p = option_end(p, end, mess);
569      return p - (unsigned char *)mess;
570
571    case DHCPINFORM:
572      if (have_config(config, CONFIG_DISABLE))
573	message = "ignored";
574
575      log_packet("INFORM", &mess->ciaddr, mess->chaddr, iface_name, message);
576
577      if (message || mess->ciaddr.s_addr == 0)
578	return 0;
579
580      p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
581      p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
582      p = do_req_options(context, p, end, req_options, daemon,
583			 hostname, iface_addr, netid, subnet_addr);
584      p = option_end(p, end, mess);
585
586      log_packet("ACK", &mess->ciaddr, mess->chaddr, iface_name, hostname);
587      return p - (unsigned char *)mess;
588    }
589
590  return 0;
591}
592
593static void log_packet(char *type, struct in_addr *addr, unsigned char *hwaddr, char *interface, char *string)
594{
595  syslog(LOG_INFO, "%s%s(%s)%s%s %.2x:%.2x:%.2x:%.2x:%.2x:%.2x%s%s",
596	 type ? "DHCP" : "BOOTP",
597	 type ? type : "",
598	 interface,
599	 addr ? " " : "",
600	 addr ? inet_ntoa(*addr) : "",
601	 hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5],
602	 string ? " " : "",
603	 string ? string : "");
604}
605
606static int option_len(unsigned char *opt)
607{
608  return opt[1];
609}
610
611static void *option_ptr(unsigned char *opt)
612{
613  return &opt[2];
614}
615
616static struct in_addr option_addr(unsigned char *opt)
617{
618  /* this worries about unaligned data in the option. */
619  /* struct in_addr is network byte order */
620  struct in_addr ret;
621
622  memcpy(&ret, option_ptr(opt), INADDRSZ);
623
624  return ret;
625}
626
627static unsigned int option_uint(unsigned char *opt, int size)
628{
629  /* this worries about unaligned data and byte order */
630  unsigned int ret = 0;
631  int i;
632  unsigned char *p = option_ptr(opt);
633
634  for (i = 0; i < size; i++)
635    ret = (ret << 8) | *p++;
636
637  return ret;
638}
639
640static void bootp_option_put(struct dhcp_packet *mess, char *filename, char *sname)
641{
642  memset(mess->sname, 0, sizeof(mess->sname));
643  memset(mess->file, 0, sizeof(mess->file));
644  if (sname)
645    strncpy(mess->sname, sname, sizeof(mess->sname)-1);
646  if (filename)
647    strncpy(mess->file, filename, sizeof(mess->file)-1);
648}
649
650static unsigned char *option_put(unsigned char *p, unsigned char *end, int opt, int len, unsigned int val)
651{
652  int i;
653
654  /* always keep one octet space for the END option. */
655  if (p + len + 3 < end)
656    {
657      *(p++) = opt;
658      *(p++) = len;
659
660      for (i = 0; i < len; i++)
661	*(p++) = val >> (8 * (len - (i + 1)));
662    }
663  return p;
664}
665
666static unsigned char *option_end(unsigned char *p, unsigned char *end, struct dhcp_packet *start)
667{
668  *(p++) = OPTION_END;
669  while ((p < end) && (p - ((unsigned char *)start) < MIN_PACKETSZ))
670    *p++ = 0;
671
672  return p;
673}
674
675static unsigned char *option_put_string(unsigned char *p, unsigned char *end, int opt, char *string)
676{
677  int len = strlen(string);
678
679  if (p + len + 3 < end)
680    {
681      *(p++) = opt;
682      *(p++) = len;
683      memcpy(p, string, len);
684      p += len;
685    }
686
687  return p;
688}
689
690static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int *overload)
691{
692  if (!p)
693    return NULL;
694
695  while (*p != OPTION_END)
696    {
697      if (end && (p >= end))
698	return 0; /* malformed packet */
699      else if (*p == OPTION_PAD)
700	p++;
701      else if (*p == OPTION_OVERLOAD)
702	{
703	  if (end && (p >= end - 3))
704	    return 0; /* malformed packet */
705	  if (overload)
706	    *overload = *(p+2);
707	  p += 3;
708	}
709      else
710	{
711	  int opt_len;;
712	  if (end && (p >= end - 2))
713	    return 0; /* malformed packet */
714	  opt_len = option_len(p);
715	  if (end && (p >= end - (2 + opt_len)))
716	    return 0; /* malformed packet */
717	  if (*p == opt)
718	    return p;
719	  p += opt_len + 2;
720	}
721    }
722
723  return NULL;
724}
725
726static unsigned char *option_find(struct dhcp_packet *mess, int size, int opt_type)
727{
728  int overload = 0;
729  unsigned char *ret;
730
731  /* skip over DHCP cookie; */
732  ret = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + size, opt_type, &overload);
733
734  if (!ret && (overload & 1))
735    ret = option_find1(&mess->file[0], &mess->file[128], opt_type, &overload);
736
737  if (!ret && (overload & 2))
738    ret = option_find1(&mess->sname[0], &mess->file[64], opt_type, &overload);
739
740  return ret;
741}
742
743static int in_list(unsigned char *list, int opt)
744{
745  int i;
746
747  /* If no requested options, send everything, not nothing. */
748  if (!list)
749    return 1;
750
751  for (i = 0; list[i] != OPTION_END; i++)
752    if (opt == list[i])
753      return 1;
754
755  return 0;
756}
757
758static struct dhcp_opt *option_find2(struct dhcp_netid *netid, struct dhcp_opt *opts, int opt)
759{
760  struct dhcp_opt *tmp;
761  struct dhcp_netid *tmp1;
762
763  for (tmp = opts; tmp; tmp = tmp->next)
764    if (tmp->opt == opt)
765      {
766	if (netid)
767	  {
768	    if (tmp->netid)
769	      for (tmp1 = netid; tmp1; tmp1 = tmp1->next)
770		if (strcmp(tmp->netid, tmp1->net) == 0)
771		  return tmp;
772	  }
773	else if (!tmp->netid)
774	  return tmp;
775      }
776
777  return netid ? option_find2(NULL, opts, opt) : NULL;
778}
779
780static unsigned char *do_req_options(struct dhcp_context *context,
781				     unsigned char *p, unsigned char *end,
782				     unsigned char *req_options,
783				     struct daemon *daemon,
784				     char *hostname,
785				     struct in_addr iface_addr,
786				     struct dhcp_netid *netid,
787				     struct in_addr subnet_addr)
788{
789  struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
790
791  if (in_list(req_options, OPTION_MAXMESSAGE))
792    p = option_put(p, end, OPTION_MAXMESSAGE, 2, end - (unsigned char *)daemon->dhcp_packet);
793
794  /* rfc3011 says this doesn't need to be in the requested options list. */
795  if (subnet_addr.s_addr)
796    p = option_put(p, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr));
797
798  if (in_list(req_options, OPTION_NETMASK) &&
799      !option_find2(netid, config_opts, OPTION_NETMASK))
800    p = option_put(p, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr));
801
802  /* May not have a "guessed" broadcast address if we got no packets via a relay
803     from this net yet (ie just unicast renewals after a restart */
804  if (context->broadcast.s_addr &&
805      in_list(req_options, OPTION_BROADCAST) &&
806      !option_find2(netid, config_opts, OPTION_BROADCAST))
807    p = option_put(p, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
808
809  /* Same comments as broadcast apply, and also may not be able to get a sensible
810     default when using subnet select.  User must configure by steam in that case. */
811  if (context->router.s_addr &&
812      in_list(req_options, OPTION_ROUTER) &&
813      !option_find2(netid, config_opts, OPTION_ROUTER))
814    p = option_put(p, end, OPTION_ROUTER, INADDRSZ, ntohl(context->router.s_addr));
815
816  if (in_list(req_options, OPTION_DNSSERVER) &&
817      !option_find2(netid, config_opts, OPTION_DNSSERVER))
818    p = option_put(p, end, OPTION_DNSSERVER, INADDRSZ, ntohl(iface_addr.s_addr));
819
820  if (daemon->domain_suffix && in_list(req_options, OPTION_DOMAINNAME) &&
821      !option_find2(netid, config_opts, OPTION_DOMAINNAME))
822    p = option_put_string(p, end, OPTION_DOMAINNAME, daemon->domain_suffix);
823
824  /* Note that we ignore attempts to set the hostname using
825     --dhcp-option=12,<name> */
826  if (hostname && in_list(req_options, OPTION_HOSTNAME))
827    p = option_put_string(p, end, OPTION_HOSTNAME, hostname);
828
829  for (opt=config_opts; opt; opt = opt->next)
830    {
831      if (opt->opt == OPTION_HOSTNAME ||
832	  opt->opt == OPTION_MAXMESSAGE ||
833	  !in_list(req_options, opt->opt) ||
834	  opt != option_find2(netid, config_opts, opt->opt) ||
835	  p + opt->len + 3 >= end)
836	continue;
837
838      /* For the options we have default values on
839	 dhc-option=<optionno> means "don't include this option"
840	 not "include a zero-length option" */
841      if (opt->len == 0 &&
842	  (opt->opt == OPTION_NETMASK ||
843	   opt->opt == OPTION_BROADCAST ||
844	   opt->opt == OPTION_ROUTER ||
845	   opt->opt == OPTION_DNSSERVER))
846	continue;
847
848      *(p++) = opt->opt;
849      *(p++) = opt->len;
850      if (opt->len == 0)
851	continue;
852
853      if (opt->is_addr)
854	{
855	  int j;
856	  struct in_addr *a = (struct in_addr *)opt->val;
857	  for (j = 0; j < opt->len; j+=INADDRSZ, a++)
858	    {
859	      /* zero means "self" */
860	      if (a->s_addr == 0)
861		memcpy(p, &iface_addr, INADDRSZ);
862	      else
863		memcpy(p, a, INADDRSZ);
864	      p += INADDRSZ;
865	    }
866	}
867      else
868	{
869	  memcpy(p, opt->val, opt->len);
870	  p += opt->len;
871	}
872    }
873  return p;
874}
875
876
877