blob: c47a63dfdfc5ea3a6afbea00d2d0cb7f0b012b61 [file] [log] [blame]
Simon Kelleyf6b7dc42005-01-23 12:06:08 +00001/* dnsmasq is Copyright (c) 2000-2005 Simon Kelley
Simon Kelley9e4abcb2004-01-22 19:47:41 +00002
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
Simon Kelleya2226412004-05-13 20:27:08 +010021/* The Linux in-kernel DHCP client silently ignores any packet
22 smaller than this. Sigh........... */
23#define MIN_PACKETSZ 300
24
Simon Kelley9e4abcb2004-01-22 19:47:41 +000025#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
Simon Kelley91dccd02005-03-31 17:48:32 +010032#define OPTION_VENDOR_CLASS_OPT 43
Simon Kelley9e4abcb2004-01-22 19:47:41 +000033#define OPTION_REQUESTED_IP 50
34#define OPTION_LEASE_TIME 51
35#define OPTION_OVERLOAD 52
36#define OPTION_MESSAGE_TYPE 53
37#define OPTION_SERVER_IDENTIFIER 54
38#define OPTION_REQUESTED_OPTIONS 55
Simon Kelley44a2a312004-03-10 20:04:35 +000039#define OPTION_MESSAGE 56
Simon Kelley9e4abcb2004-01-22 19:47:41 +000040#define OPTION_MAXMESSAGE 57
Simon Kelley44a2a312004-03-10 20:04:35 +000041#define OPTION_T1 58
42#define OPTION_T2 59
Simon Kelleya84fa1d2004-04-23 22:21:21 +010043#define OPTION_VENDOR_ID 60
Simon Kelley44a2a312004-03-10 20:04:35 +000044#define OPTION_CLIENT_ID 61
Simon Kelleya2226412004-05-13 20:27:08 +010045#define OPTION_USER_CLASS 77
Simon Kelley3d8df262005-08-29 12:19:27 +010046#define OPTION_CLIENT_FQDN 81
Simon Kelley3be34542004-09-11 19:12:13 +010047#define OPTION_SUBNET_SELECT 118
Simon Kelley9e4abcb2004-01-22 19:47:41 +000048#define OPTION_END 255
49
50#define DHCPDISCOVER 1
51#define DHCPOFFER 2
52#define DHCPREQUEST 3
53#define DHCPDECLINE 4
54#define DHCPACK 5
55#define DHCPNAK 6
56#define DHCPRELEASE 7
57#define DHCPINFORM 8
58
Simon Kelley0a852542005-03-23 20:28:59 +000059#define have_config(config, mask) ((config) && ((config)->flags & (mask)))
60
Simon Kelley9e4abcb2004-01-22 19:47:41 +000061static unsigned char *option_put(unsigned char *p, unsigned char *end, int opt, int len, unsigned int val);
Simon Kelleya2226412004-05-13 20:27:08 +010062static unsigned char *option_end(unsigned char *p, unsigned char *end, struct dhcp_packet *start);
Simon Kelley44a2a312004-03-10 20:04:35 +000063static unsigned char *option_put_string(unsigned char *p, unsigned char *end, int opt, char *string);
Simon Kelley26128d22004-11-14 16:43:54 +000064static void bootp_option_put(struct dhcp_packet *mess,
65 struct dhcp_boot *boot_opts, struct dhcp_netid *netids);
Simon Kelleyf6b7dc42005-01-23 12:06:08 +000066static int option_len(unsigned char *opt);
Simon Kelley9e4abcb2004-01-22 19:47:41 +000067static void *option_ptr(unsigned char *opt);
68static struct in_addr option_addr(unsigned char *opt);
Simon Kelley44a2a312004-03-10 20:04:35 +000069static unsigned int option_uint(unsigned char *opt, int size);
70static void log_packet(char *type, struct in_addr *addr, unsigned char *hwaddr, char *interface, char *string);
Simon Kelleyf6b7dc42005-01-23 12:06:08 +000071static unsigned char *option_find(struct dhcp_packet *mess, int size, int opt_type, int minsize);
Simon Kelley9e4abcb2004-01-22 19:47:41 +000072static unsigned char *do_req_options(struct dhcp_context *context,
73 unsigned char *p, unsigned char *end,
74 unsigned char *req_options,
Simon Kelley3be34542004-09-11 19:12:13 +010075 struct daemon *daemon,
76 char *hostname,
Simon Kelley3be34542004-09-11 19:12:13 +010077 struct dhcp_netid *netid,
Simon Kelley3d8df262005-08-29 12:19:27 +010078 struct in_addr subnet_addr,
79 unsigned char fqdn_flags);
Simon Kelley9e4abcb2004-01-22 19:47:41 +000080
Simon Kelleyb8187c82005-11-26 21:46:27 +000081int dhcp_reply(struct daemon *daemon, struct dhcp_context *context, char *iface_name,
82 unsigned int sz, time_t now, int unicast_dest)
Simon Kelley33820b72004-04-03 21:10:00 +010083{
Simon Kelley26128d22004-11-14 16:43:54 +000084 unsigned char *opt, *clid = NULL;
Simon Kelley0a852542005-03-23 20:28:59 +000085 struct dhcp_lease *ltmp, *lease = NULL;
Simon Kelleya2226412004-05-13 20:27:08 +010086 struct dhcp_vendor *vendor;
Simon Kelley26128d22004-11-14 16:43:54 +000087 struct dhcp_netid_list *id_list;
88 int clid_len = 0, ignore = 0;
Simon Kelley3be34542004-09-11 19:12:13 +010089 struct dhcp_packet *mess = &daemon->dhcp_packet->data;
90 unsigned char *p = mess->options + sizeof(u32); /* skip cookie */
91 unsigned char *end = (unsigned char *)(daemon->dhcp_packet + 1);
Simon Kelley3d8df262005-08-29 12:19:27 +010092 char *hostname = NULL, *offer_hostname = NULL, *client_hostname = NULL;
Simon Kelleyb8187c82005-11-26 21:46:27 +000093 int hostname_auth = 0;
Simon Kelley3d8df262005-08-29 12:19:27 +010094 unsigned char *req_options = NULL;
Simon Kelley44a2a312004-03-10 20:04:35 +000095 char *message = NULL;
Simon Kelley59353a62004-11-21 19:34:28 +000096 unsigned int time;
Simon Kelley9e4abcb2004-01-22 19:47:41 +000097 struct dhcp_config *config;
Simon Kelleya2226412004-05-13 20:27:08 +010098 struct dhcp_netid *netid = NULL;
Simon Kelley3d8df262005-08-29 12:19:27 +010099 struct in_addr subnet_addr;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100100 unsigned short fuzz = 0;
Simon Kelley3be34542004-09-11 19:12:13 +0100101 unsigned int mess_type = 0;
Simon Kelley3d8df262005-08-29 12:19:27 +0100102 u8 *chaddr;
103 unsigned char fqdn_flags = 0;
Simon Kelley3be34542004-09-11 19:12:13 +0100104 subnet_addr.s_addr = 0;
105
106 if (mess->op != BOOTREQUEST)
Simon Kelley33820b72004-04-03 21:10:00 +0100107 return 0;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000108
Simon Kelley3be34542004-09-11 19:12:13 +0100109 /* check for DHCP rather than BOOTP */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000110 if ((opt = option_find(mess, sz, OPTION_MESSAGE_TYPE, 1)))
Simon Kelley44a2a312004-03-10 20:04:35 +0000111 {
Simon Kelley3be34542004-09-11 19:12:13 +0100112 mess_type = option_uint(opt, 1);
Simon Kelley44a2a312004-03-10 20:04:35 +0000113
Simon Kelley3be34542004-09-11 19:12:13 +0100114 /* only insist on a cookie for DHCP. */
115 if (*((u32 *)&mess->options) != htonl(DHCP_COOKIE))
116 return 0;
117
118 /* Some buggy clients set ciaddr when they shouldn't, so clear that here since
119 it can affect the context-determination code. */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000120 if ((option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ) || mess_type == DHCPDISCOVER))
Simon Kelley3be34542004-09-11 19:12:13 +0100121 mess->ciaddr.s_addr = 0;
122
123 /* Check for RFC3011 subnet selector */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000124 if ((opt = option_find(mess, sz, OPTION_SUBNET_SELECT, INADDRSZ)))
Simon Kelley3be34542004-09-11 19:12:13 +0100125 subnet_addr = option_addr(opt);
Simon Kelley26128d22004-11-14 16:43:54 +0000126
127 /* If there is no client identifier option, use the hardware address */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000128 if ((opt = option_find(mess, sz, OPTION_CLIENT_ID, 1)))
Simon Kelley26128d22004-11-14 16:43:54 +0000129 {
Simon Kelley26128d22004-11-14 16:43:54 +0000130 clid_len = option_len(opt);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000131 clid = option_ptr(opt);
Simon Kelley26128d22004-11-14 16:43:54 +0000132 }
Simon Kelley0a852542005-03-23 20:28:59 +0000133
134 /* do we have a lease in store? */
Simon Kelley3d8df262005-08-29 12:19:27 +0100135 if (mess->htype != 0)
136 lease = lease_find_by_client(mess->chaddr, clid, clid_len);
Simon Kelley0a852542005-03-23 20:28:59 +0000137
138 /* If this request is missing a clid, but we've seen one before,
139 use it again for option matching etc. */
140 if (lease && !clid && lease->clid)
141 {
142 clid_len = lease->clid_len;
143 clid = lease->clid;
144 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000145 }
Simon Kelley3be34542004-09-11 19:12:13 +0100146
Simon Kelley3d8df262005-08-29 12:19:27 +0100147 /* htype == 0 is only allowed in DHCPINFORM, this seems to be a
148 microsoftism. chaddr == NULL in that case */
149
150 if (mess->htype == 0 && mess_type == DHCPINFORM)
151 {
152 chaddr = NULL;
153 if (mess->hlen != 0)
154 return 0;
155 }
156 else
157 {
158 chaddr = mess->chaddr;
159
160 /* Token ring is supported when we have packet sockets
161 to make the HW headers for us. We don't have the code to build
162 token ring headers when using BPF. We rely on the fact that
163 token ring hwaddrs are the same size as ethernet hwaddrs. */
164
165#ifdef HAVE_BPF
166 if (mess->htype != ARPHRD_ETHER)
167#else
168 if (mess->htype != ARPHRD_ETHER && mess->htype != ARPHRD_IEEE802)
169#endif
170 {
Simon Kelleyb8187c82005-11-26 21:46:27 +0000171 syslog(LOG_WARNING, _("DHCP request for unsupported hardware type (%d) recieved on %s"),
Simon Kelley3d8df262005-08-29 12:19:27 +0100172 mess->htype, iface_name);
173 return 0;
174 }
175
176 if (mess->hlen != ETHER_ADDR_LEN)
177 return 0;
178 }
179
Simon Kelley0a852542005-03-23 20:28:59 +0000180 /* Determine network for this packet. Our caller will have already linked all the
181 contexts which match the addresses of the receiving interface but if the
182 machine has an address already, or came via a relay, or we have a subnet selector,
183 we search again. If we don't have have a giaddr or explicit subnet selector,
184 use the ciaddr. This is necessary because a machine which got a lease via a
Simon Kelley3d8df262005-08-29 12:19:27 +0100185 relay won't use the relay to renew. If matching a ciaddr fails but we have a context
186 from the physical network, continue using that to allow correct DHCPNAK generation later. */
Simon Kelley0a852542005-03-23 20:28:59 +0000187 if (mess->giaddr.s_addr || subnet_addr.s_addr || mess->ciaddr.s_addr)
188 {
Simon Kelley3d8df262005-08-29 12:19:27 +0100189 struct dhcp_context *context_tmp, *context_new = NULL;
190 struct in_addr addr = mess->ciaddr;
191 int force = 0;
Simon Kelley0a852542005-03-23 20:28:59 +0000192
Simon Kelley3d8df262005-08-29 12:19:27 +0100193 if (subnet_addr.s_addr)
194 {
195 addr = subnet_addr;
196 force = 1;
197 }
198 else if (mess->giaddr.s_addr)
199 {
200 addr = mess->giaddr;
201 force = 1;
202 }
203
204 for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next)
Simon Kelley0a852542005-03-23 20:28:59 +0000205 if (context_tmp->netmask.s_addr &&
206 is_same_net(addr, context_tmp->start, context_tmp->netmask) &&
207 is_same_net(addr, context_tmp->end, context_tmp->netmask))
208 {
Simon Kelley3d8df262005-08-29 12:19:27 +0100209 context_tmp->current = context_new;
210 context_new = context_tmp;
Simon Kelley0a852542005-03-23 20:28:59 +0000211 }
Simon Kelley3d8df262005-08-29 12:19:27 +0100212
213 if (context_new || force)
214 context = context_new;
215
Simon Kelley0a852542005-03-23 20:28:59 +0000216 }
Simon Kelley3be34542004-09-11 19:12:13 +0100217
218 if (!context)
219 {
Simon Kelleyb8187c82005-11-26 21:46:27 +0000220 syslog(LOG_WARNING, _("no address range available for DHCP request %s %s"),
221 subnet_addr.s_addr ? _("with subnet selector") : _("via"),
Simon Kelley0a852542005-03-23 20:28:59 +0000222 subnet_addr.s_addr ? inet_ntoa(subnet_addr) : (mess->giaddr.s_addr ? inet_ntoa(mess->giaddr) : iface_name));
Simon Kelley3be34542004-09-11 19:12:13 +0100223 return 0;
224 }
225
226 mess->op = BOOTREPLY;
Simon Kelley36717ee2004-09-20 19:20:58 +0100227
Simon Kelley3d8df262005-08-29 12:19:27 +0100228 config = find_config(daemon->dhcp_conf, context, clid, clid_len, chaddr, NULL);
Simon Kelley26128d22004-11-14 16:43:54 +0000229
Simon Kelley3be34542004-09-11 19:12:13 +0100230 if (mess_type == 0)
231 {
232 /* BOOTP request */
Simon Kelley26128d22004-11-14 16:43:54 +0000233 struct dhcp_netid id;
234 char save = mess->file[128];
235 struct in_addr *logaddr = NULL;
236
Simon Kelley26128d22004-11-14 16:43:54 +0000237 if (have_config(config, CONFIG_DISABLE))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000238 message = _("disabled");
Simon Kelley26128d22004-11-14 16:43:54 +0000239
240 end = mess->options + 64; /* BOOTP vend area is only 64 bytes */
241
242 if (have_config(config, CONFIG_NAME))
243 hostname = config->hostname;
244
245 if (have_config(config, CONFIG_NETID))
246 {
247 config->netid.next = netid;
248 netid = &config->netid;
249 }
250
251 /* Match incoming filename field as a netid. */
252 if (mess->file[0])
253 {
254 mess->file[128] = 0; /* ensure zero term. */
Simon Kelley3d8df262005-08-29 12:19:27 +0100255 id.net = (char *)mess->file;
Simon Kelley26128d22004-11-14 16:43:54 +0000256 id.next = netid;
257 netid = &id;
258 }
259
260 for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
261 if (match_netid(id_list->list, netid))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000262 message = _("disabled");
Simon Kelley26128d22004-11-14 16:43:54 +0000263
Simon Kelley3d8df262005-08-29 12:19:27 +0100264 if (!message)
265 {
266 if (have_config(config, CONFIG_ADDR))
267 {
268 logaddr = &config->addr;
269 mess->yiaddr = config->addr;
270 if ((lease = lease_find_by_addr(config->addr)) &&
271 memcmp(lease->hwaddr, chaddr, ETHER_ADDR_LEN) != 0)
Simon Kelleyb8187c82005-11-26 21:46:27 +0000272 message = _("address in use");
Simon Kelley3d8df262005-08-29 12:19:27 +0100273 }
274 else if (!(daemon->options & OPT_BOOTP_DYNAMIC))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000275 message = _("no address configured");
Simon Kelley3d8df262005-08-29 12:19:27 +0100276 else
277 {
Simon Kelleye17fb622006-01-14 20:33:46 +0000278 if (!(lease = lease_find_by_client(mess->chaddr, NULL, 0)) ||
279 !address_available(context, lease->addr))
280 {
281 if (lease)
282 {
283 /* lease exists, wrong network. */
284 lease_prune(lease, now);
285 lease = NULL;
286 }
287 if (!address_allocate(context, daemon, &mess->yiaddr, chaddr, netid, now))
288 message = _("no address available");
289 }
290 else
Simon Kelley3d8df262005-08-29 12:19:27 +0100291 mess->yiaddr = lease->addr;
Simon Kelley3d8df262005-08-29 12:19:27 +0100292 }
293
294 if (!message && !lease && (!(lease = lease_allocate(chaddr, NULL, 0, mess->yiaddr))))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000295 message = _("no leases left");
Simon Kelley3d8df262005-08-29 12:19:27 +0100296
Simon Kelleye17fb622006-01-14 20:33:46 +0000297 if (!message && !(context = narrow_context(context, mess->yiaddr)))
298 message = _("wrong network");
299
Simon Kelley3d8df262005-08-29 12:19:27 +0100300 if (!message)
301 {
302 logaddr = &mess->yiaddr;
Simon Kelley3d8df262005-08-29 12:19:27 +0100303
304 if (context->netid.net && !(context->flags & CONTEXT_FILTER))
305 {
306 context->netid.next = netid;
307 netid = &context->netid;
308 }
309
310 lease_set_hwaddr(lease, chaddr, NULL, 0);
311 if (hostname)
Simon Kelleyb8187c82005-11-26 21:46:27 +0000312 lease_set_hostname(lease, hostname, daemon->domain_suffix, 1);
Simon Kelley3d8df262005-08-29 12:19:27 +0100313 lease_set_expires(lease, 0); /* infinite lease */
314
315 p = do_req_options(context, p, end, NULL, daemon,
316 hostname, netid, subnet_addr, fqdn_flags);
317 /* must do this after do_req_options since it overwrites filename field. */
318 mess->siaddr = context->local;
319 bootp_option_put(mess, daemon->boot_config, netid);
320 p = option_end(p, end, mess);
321 }
322 }
323
324 log_packet(NULL, logaddr, chaddr, iface_name, message);
Simon Kelley26128d22004-11-14 16:43:54 +0000325 mess->file[128] = save;
326
327 if (message)
328 return 0;
329 else
330 return p - (unsigned char *)mess;
Simon Kelley3be34542004-09-11 19:12:13 +0100331 }
332
Simon Kelley3d8df262005-08-29 12:19:27 +0100333 if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 4)))
334 {
335 /* http://tools.ietf.org/wg/dhc/draft-ietf-dhc-fqdn-option/draft-ietf-dhc-fqdn-option-10.txt */
336 int len = option_len(opt);
337 char *pq = daemon->dhcp_buff;
338 unsigned char *pp, *op = option_ptr(opt);
339
340 fqdn_flags = *op;
341 len -= 3;
342 op += 3;
343 pp = op;
344
345 /* Always force update, since the client has no way to do it itself. */
346 if (fqdn_flags & 0x01)
347 fqdn_flags |= 0x02;
348
349 fqdn_flags &= ~0x08;
350 fqdn_flags |= 0x01;
351
352 if (fqdn_flags & 0x04)
353 while (*op != 0 && ((op + (*op) + 1) - pp) < len)
354 {
355 memcpy(pq, op+1, *op);
356 pq += *op;
357 op += (*op)+1;
358 *(pq++) = '.';
359 }
360 else
361 {
362 memcpy(pq, op, len);
363 pq += len + 1;
364 }
365
366 if (pq != daemon->dhcp_buff)
367 pq--;
368
369 *pq = 0;
370
371 if (canonicalise(daemon->dhcp_buff))
372 offer_hostname = client_hostname = daemon->dhcp_buff;
373 }
Simon Kelleybb01cb92004-12-13 20:56:23 +0000374 else if ((opt = option_find(mess, sz, OPTION_HOSTNAME, 1)))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000375 {
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000376 int len = option_len(opt);
Simon Kelley3d8df262005-08-29 12:19:27 +0100377 memcpy(daemon->dhcp_buff, option_ptr(opt), len);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000378 /* May not be zero terminated */
Simon Kelley3d8df262005-08-29 12:19:27 +0100379 daemon->dhcp_buff[len] = 0;
380 if (canonicalise(daemon->dhcp_buff))
381 client_hostname = daemon->dhcp_buff;
382 }
383
384 if (have_config(config, CONFIG_NAME))
385 {
386 hostname = config->hostname;
Simon Kelleyb8187c82005-11-26 21:46:27 +0000387 hostname_auth = 1;
Simon Kelley3d8df262005-08-29 12:19:27 +0100388 /* be careful not to send an OFFER with a hostname not
389 matching the DISCOVER. */
390 if (fqdn_flags != 0 || !client_hostname || hostname_isequal(hostname, client_hostname))
391 offer_hostname = hostname;
392 }
393 else if (client_hostname && (hostname = strip_hostname(daemon, client_hostname)) && !config)
394 {
395 /* Search again now we have a hostname.
396 Only accept configs without CLID and HWADDR here, (they won't match)
397 to avoid impersonation by name. */
398 struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, chaddr, hostname);
399 if (!have_config(new, CONFIG_CLID) && !have_config(new, CONFIG_HWADDR))
400 config = new;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000401 }
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100402
Simon Kelleya2226412004-05-13 20:27:08 +0100403 if (have_config(config, CONFIG_NETID))
404 {
405 config->netid.next = netid;
406 netid = &config->netid;
407 }
Simon Kelley36717ee2004-09-20 19:20:58 +0100408
Simon Kelley26128d22004-11-14 16:43:54 +0000409 /* user-class options are, according to RFC3004, supposed to contain
410 a set of counted strings. Here we check that this is so (by seeing
411 if the counts are consistent with the overall option length) and if
412 so zero the counts so that we don't get spurious matches between
413 the vendor string and the counts. If the lengths don't add up, we
414 assume that the option is a single string and non RFC3004 compliant
415 and just do the substring match. dhclient provides these broken options. */
Simon Kelleya2226412004-05-13 20:27:08 +0100416
Simon Kelleybb01cb92004-12-13 20:56:23 +0000417 if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
Simon Kelleya2226412004-05-13 20:27:08 +0100418 {
Simon Kelley26128d22004-11-14 16:43:54 +0000419 unsigned char *ucp = option_ptr(opt);
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000420 int tmp, j;
Simon Kelley26128d22004-11-14 16:43:54 +0000421 for (j = 0; j < option_len(opt); j += ucp[j] + 1);
422 if (j == option_len(opt))
423 for (j = 0; j < option_len(opt); j = tmp)
424 {
425 tmp = j + ucp[j] + 1;
426 ucp[j] = 0;
427 }
Simon Kelleya2226412004-05-13 20:27:08 +0100428 }
Simon Kelley26128d22004-11-14 16:43:54 +0000429
430 for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
Simon Kelleybb01cb92004-12-13 20:56:23 +0000431 if ((opt = option_find(mess, sz, vendor->is_vendor ? OPTION_VENDOR_ID : OPTION_USER_CLASS, 1)))
Simon Kelley26128d22004-11-14 16:43:54 +0000432 {
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000433 int i;
Simon Kelley26128d22004-11-14 16:43:54 +0000434 for (i = 0; i <= (option_len(opt) - vendor->len); i++)
435 if (memcmp(vendor->data, option_ptr(opt)+i, vendor->len) == 0)
436 {
437 vendor->netid.next = netid;
438 netid = &vendor->netid;
439 break;
440 }
441 }
442
443 /* if all the netids in the ignore list are present, ignore this client */
444 for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
445 if (match_netid(id_list->list, netid))
446 ignore = 1;
447
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100448 /* Can have setting to ignore the client ID for a particular MAC address or hostname */
449 if (have_config(config, CONFIG_NOCLID))
Simon Kelley0a852542005-03-23 20:28:59 +0000450 clid = NULL;
451
Simon Kelleybb01cb92004-12-13 20:56:23 +0000452 if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 0)))
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100453 {
Simon Kelley3d8df262005-08-29 12:19:27 +0100454 req_options = (unsigned char *)daemon->dhcp_buff2;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000455 memcpy(req_options, option_ptr(opt), option_len(opt));
456 req_options[option_len(opt)] = OPTION_END;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100457 }
458
Simon Kelley3be34542004-09-11 19:12:13 +0100459 switch (mess_type)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000460 {
Simon Kelley44a2a312004-03-10 20:04:35 +0000461 case DHCPDECLINE:
Simon Kelleybb01cb92004-12-13 20:56:23 +0000462 if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
Simon Kelley0a852542005-03-23 20:28:59 +0000463 (context->local.s_addr != option_addr(opt).s_addr))
Simon Kelley44a2a312004-03-10 20:04:35 +0000464 return 0;
465
466 /* sanitise any message. Paranoid? Moi? */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000467 if ((opt = option_find(mess, sz, OPTION_MESSAGE, 1)))
Simon Kelley44a2a312004-03-10 20:04:35 +0000468 {
Simon Kelley3be34542004-09-11 19:12:13 +0100469 char *p = option_ptr(opt), *q = daemon->dhcp_buff;
Simon Kelley44a2a312004-03-10 20:04:35 +0000470 int i;
471
472 for (i = option_len(opt); i > 0; i--)
473 {
474 char c = *p++;
475 if (isprint(c))
476 *q++ = c;
477 }
478 *q++ = 0; /* add terminator */
Simon Kelley3be34542004-09-11 19:12:13 +0100479 message = daemon->dhcp_buff;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000480 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000481
Simon Kelleybb01cb92004-12-13 20:56:23 +0000482 if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
Simon Kelley44a2a312004-03-10 20:04:35 +0000483 return 0;
484
Simon Kelley3d8df262005-08-29 12:19:27 +0100485 log_packet("DECLINE", option_ptr(opt), chaddr, iface_name, message);
Simon Kelley44a2a312004-03-10 20:04:35 +0000486
487 if (lease && lease->addr.s_addr == option_addr(opt).s_addr)
488 lease_prune(lease, now);
489
Simon Kelley33820b72004-04-03 21:10:00 +0100490 if (have_config(config, CONFIG_ADDR) &&
Simon Kelley44a2a312004-03-10 20:04:35 +0000491 config->addr.s_addr == option_addr(opt).s_addr)
492 {
Simon Kelleyb8187c82005-11-26 21:46:27 +0000493 syslog(LOG_WARNING, _("disabling DHCP static address %s"), inet_ntoa(config->addr));
Simon Kelley33820b72004-04-03 21:10:00 +0100494 config->flags &= ~CONFIG_ADDR ;
Simon Kelley44a2a312004-03-10 20:04:35 +0000495 }
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100496 else
497 /* make sure this host gets a different address next time. */
Simon Kelley36717ee2004-09-20 19:20:58 +0100498 for (; context; context = context->current)
499 context->addr_epoch++;
Simon Kelley44a2a312004-03-10 20:04:35 +0000500
501 return 0;
502
503 case DHCPRELEASE:
Simon Kelleybb01cb92004-12-13 20:56:23 +0000504 if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
Simon Kelley0a852542005-03-23 20:28:59 +0000505 (context->local.s_addr != option_addr(opt).s_addr))
Simon Kelley44a2a312004-03-10 20:04:35 +0000506 return 0;
507
Simon Kelley44a2a312004-03-10 20:04:35 +0000508 if (lease && lease->addr.s_addr == mess->ciaddr.s_addr)
509 lease_prune(lease, now);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100510 else
Simon Kelleyb8187c82005-11-26 21:46:27 +0000511 message = _("unknown lease");
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100512
Simon Kelley3d8df262005-08-29 12:19:27 +0100513 log_packet("RELEASE", &mess->ciaddr, chaddr, iface_name, message);
Simon Kelley44a2a312004-03-10 20:04:35 +0000514
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000515 return 0;
516
517 case DHCPDISCOVER:
Simon Kelley3d8df262005-08-29 12:19:27 +0100518 {
519 struct in_addr addr;
520
521 if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
522 addr = option_addr(opt);
523 if (ignore || have_config(config, CONFIG_DISABLE))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000524 message = _("ignored");
Simon Kelley3d8df262005-08-29 12:19:27 +0100525 else if (have_config(config, CONFIG_ADDR) &&
526 (!(ltmp = lease_find_by_addr(config->addr)) || ltmp == lease))
527 mess->yiaddr = config->addr;
528 else if (lease && address_available(context, lease->addr))
529 mess->yiaddr = lease->addr;
530 else if (opt && address_available(context, addr) && !lease_find_by_addr(addr) &&
531 !config_find_by_address(daemon->dhcp_conf, addr))
532 mess->yiaddr = addr;
533 else if (!address_allocate(context, daemon, &mess->yiaddr, chaddr, netid, now))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000534 message = _("no address available");
Simon Kelley3d8df262005-08-29 12:19:27 +0100535 log_packet("DISCOVER", opt ? &addr : NULL, chaddr, iface_name, message);
536 }
537
Simon Kelleye17fb622006-01-14 20:33:46 +0000538 if (message || !(context = narrow_context(context, mess->yiaddr)))
Simon Kelley33820b72004-04-03 21:10:00 +0100539 return 0;
Simon Kelleye17fb622006-01-14 20:33:46 +0000540
Simon Kelley0a852542005-03-23 20:28:59 +0000541 if (context->netid.net && !(context->flags & CONTEXT_FILTER))
Simon Kelley59353a62004-11-21 19:34:28 +0000542 {
543 context->netid.next = netid;
544 netid = &context->netid;
545 }
546
547 time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000548 if ((opt = option_find(mess, sz, OPTION_LEASE_TIME, 4)))
Simon Kelley59353a62004-11-21 19:34:28 +0000549 {
550 unsigned int req_time = option_uint(opt, 4);
551 if (time == 0xffffffff || (req_time != 0xffffffff && req_time < time))
552 time = req_time;
553 }
554 else if (lease && lease->expires != 0)
555 time = (unsigned int)difftime(lease->expires, now);
556
Simon Kelley0a852542005-03-23 20:28:59 +0000557 mess->siaddr = context->local;
Simon Kelley26128d22004-11-14 16:43:54 +0000558 bootp_option_put(mess, daemon->boot_config, netid);
Simon Kelley44a2a312004-03-10 20:04:35 +0000559 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPOFFER);
Simon Kelley0a852542005-03-23 20:28:59 +0000560 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelley59353a62004-11-21 19:34:28 +0000561 p = option_put(p, end, OPTION_LEASE_TIME, 4, time);
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100562 /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
Simon Kelley59353a62004-11-21 19:34:28 +0000563 if (time != 0xffffffff)
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100564 {
Simon Kelley59353a62004-11-21 19:34:28 +0000565 p = option_put(p, end, OPTION_T1, 4, (time/2));
566 p = option_put(p, end, OPTION_T2, 4, (time*7)/8);
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100567 }
Simon Kelley3be34542004-09-11 19:12:13 +0100568 p = do_req_options(context, p, end, req_options, daemon,
Simon Kelley3d8df262005-08-29 12:19:27 +0100569 offer_hostname, netid, subnet_addr, fqdn_flags);
Simon Kelleya2226412004-05-13 20:27:08 +0100570 p = option_end(p, end, mess);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000571
Simon Kelley3d8df262005-08-29 12:19:27 +0100572 log_packet("OFFER" , &mess->yiaddr, chaddr, iface_name, NULL);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000573 return p - (unsigned char *)mess;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000574
575 case DHCPREQUEST:
Simon Kelley26128d22004-11-14 16:43:54 +0000576 if (ignore || have_config(config, CONFIG_DISABLE))
577 return 0;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000578 if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000579 {
580 /* SELECTING or INIT_REBOOT */
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000581 mess->yiaddr = option_addr(opt);
Simon Kelley44a2a312004-03-10 20:04:35 +0000582
Simon Kelleybb01cb92004-12-13 20:56:23 +0000583 if ((opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000584 {
Simon Kelley3be34542004-09-11 19:12:13 +0100585 /* SELECTING */
Simon Kelleye17fb622006-01-14 20:33:46 +0000586 for (; context; context = context->current)
587 if (context->local.s_addr == option_addr(opt).s_addr)
588 break;
589
590 if (!context)
Simon Kelley3be34542004-09-11 19:12:13 +0100591 return 0;
592
593 /* If a lease exists for this host and another address, squash it. */
594 if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
595 {
596 lease_prune(lease, now);
597 lease = NULL;
598 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000599 }
Simon Kelley3be34542004-09-11 19:12:13 +0100600 else
601 {
602 /* INIT-REBOOT */
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100603 if (!lease && !(daemon->options & OPT_AUTHORITATIVE))
Simon Kelley3be34542004-09-11 19:12:13 +0100604 return 0;
605
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100606 if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
Simon Kelleyb8187c82005-11-26 21:46:27 +0000607 message = _("wrong address");
Simon Kelley3be34542004-09-11 19:12:13 +0100608 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000609 }
610 else
611 {
612 /* RENEWING or REBINDING */
613 /* Must exist a lease for this address */
Simon Kelley44a2a312004-03-10 20:04:35 +0000614 if (!lease || mess->ciaddr.s_addr != lease->addr.s_addr)
Simon Kelleyb8187c82005-11-26 21:46:27 +0000615 {
616 message = _("lease not found");
617 /* ensure we broadcast NAK */
618 unicast_dest = 0;
619 }
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100620 /* desynchronise renewals */
621 fuzz = rand16();
Simon Kelley3be34542004-09-11 19:12:13 +0100622 mess->yiaddr = mess->ciaddr;
Simon Kelley44a2a312004-03-10 20:04:35 +0000623 }
624
Simon Kelley3d8df262005-08-29 12:19:27 +0100625 log_packet("REQUEST", &mess->yiaddr, chaddr, iface_name, NULL);
Simon Kelleye17fb622006-01-14 20:33:46 +0000626
Simon Kelleydfa666f2004-08-02 18:27:27 +0100627 if (!message)
628 {
629 struct dhcp_config *addr_config;
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100630
Simon Kelleye17fb622006-01-14 20:33:46 +0000631 if (!(context = narrow_context(context, mess->yiaddr)))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000632 {
Simon Kelleye17fb622006-01-14 20:33:46 +0000633 /* If a machine moves networks whilst it has a lease, we catch that here. */
Simon Kelleyb8187c82005-11-26 21:46:27 +0000634 message = _("wrong network");
635 /* ensure we broadcast NAK */
636 unicast_dest = 0;
637 }
638
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100639 /* Check for renewal of a lease which is outside the allowed range. */
Simon Kelleydfa666f2004-08-02 18:27:27 +0100640 else if (!address_available(context, mess->yiaddr) &&
641 (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000642 message = _("address not available");
Simon Kelleye17fb622006-01-14 20:33:46 +0000643
Simon Kelleydfa666f2004-08-02 18:27:27 +0100644 /* Check if a new static address has been configured. Be very sure that
645 when the client does DISCOVER, it will get the static address, otherwise
646 an endless protocol loop will ensue. */
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100647
648 else if (have_config(config, CONFIG_ADDR) &&
649 config->addr.s_addr != mess->yiaddr.s_addr &&
650 (!(ltmp = lease_find_by_addr(config->addr)) || ltmp == lease))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000651 message = _("static lease available");
Simon Kelleydfa666f2004-08-02 18:27:27 +0100652
653 /* Check to see if the address is reserved as a static address for another host */
Simon Kelley3be34542004-09-11 19:12:13 +0100654 else if ((addr_config = config_find_by_address(daemon->dhcp_conf, mess->yiaddr)) && addr_config != config)
Simon Kelleyb8187c82005-11-26 21:46:27 +0000655 message = _("address reserved");
Simon Kelleydfa666f2004-08-02 18:27:27 +0100656
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100657 else if ((ltmp = lease_find_by_addr(mess->yiaddr)) && ltmp != lease)
Simon Kelleyb8187c82005-11-26 21:46:27 +0000658 message = _("address in use");
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100659
Simon Kelley3d8df262005-08-29 12:19:27 +0100660 else if (!lease && !(lease = lease_allocate(chaddr, clid, clid_len, mess->yiaddr)))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000661 message = _("no leases left");
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100662 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000663
664 if (message)
665 {
Simon Kelley3d8df262005-08-29 12:19:27 +0100666 log_packet("NAK", &mess->yiaddr, chaddr, iface_name, message);
Simon Kelley44a2a312004-03-10 20:04:35 +0000667
Simon Kelleyb8187c82005-11-26 21:46:27 +0000668 mess->siaddr.s_addr = mess->yiaddr.s_addr = 0;
Simon Kelley44a2a312004-03-10 20:04:35 +0000669 bootp_option_put(mess, NULL, NULL);
670 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPNAK);
Simon Kelley3d8df262005-08-29 12:19:27 +0100671 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelley44a2a312004-03-10 20:04:35 +0000672 p = option_put_string(p, end, OPTION_MESSAGE, message);
Simon Kelleyb8187c82005-11-26 21:46:27 +0000673 /* This fixes a problem with the DHCP spec, broadcasting a NAK to a host on
674 a distant subnet which unicast a REQ to us won't work. */
675 if (!unicast_dest || mess->giaddr.s_addr != 0 ||
676 mess->ciaddr.s_addr == 0 || is_same_net(context->local, mess->ciaddr, context->netmask))
677 {
678 mess->flags |= htons(0x8000); /* broadcast */
679 mess->ciaddr.s_addr = 0;
680 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000681 }
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100682 else
Simon Kelley44a2a312004-03-10 20:04:35 +0000683 {
Simon Kelleyb8187c82005-11-26 21:46:27 +0000684 if (!hostname_auth && (client_hostname = host_from_dns(daemon, mess->yiaddr)))
685 {
686 hostname = client_hostname;
687 hostname_auth = 1;
688 }
689
Simon Kelley3d8df262005-08-29 12:19:27 +0100690 log_packet("ACK", &mess->yiaddr, chaddr, iface_name, hostname);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100691
Simon Kelley0a852542005-03-23 20:28:59 +0000692 if (context->netid.net && !(context->flags & CONTEXT_FILTER))
Simon Kelley59353a62004-11-21 19:34:28 +0000693 {
694 context->netid.next = netid;
695 netid = &context->netid;
696 }
697
698 time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000699 if ((opt = option_find(mess, sz, OPTION_LEASE_TIME, 4)))
Simon Kelley59353a62004-11-21 19:34:28 +0000700 {
701 unsigned int req_time = option_uint(opt, 4);
702 if (time == 0xffffffff || (req_time != 0xffffffff && req_time < time))
703 time = req_time;
704 }
705
Simon Kelley3d8df262005-08-29 12:19:27 +0100706 lease_set_hwaddr(lease, chaddr, clid, clid_len);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100707 if (hostname)
Simon Kelleyb8187c82005-11-26 21:46:27 +0000708 lease_set_hostname(lease, hostname, daemon->domain_suffix, hostname_auth);
Simon Kelley59353a62004-11-21 19:34:28 +0000709 lease_set_expires(lease, time == 0xffffffff ? 0 : now + (time_t)time);
Simon Kelley0a852542005-03-23 20:28:59 +0000710
711 mess->siaddr = context->local;
Simon Kelley26128d22004-11-14 16:43:54 +0000712 bootp_option_put(mess, daemon->boot_config, netid);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100713 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
Simon Kelley0a852542005-03-23 20:28:59 +0000714 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelley59353a62004-11-21 19:34:28 +0000715 p = option_put(p, end, OPTION_LEASE_TIME, 4, time);
716 if (time != 0xffffffff)
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100717 {
Simon Kelley59353a62004-11-21 19:34:28 +0000718 while (fuzz > (time/16))
719 fuzz = fuzz/2;
720 p = option_put(p, end, OPTION_T1, 4, (time/2) - fuzz);
721 p = option_put(p, end, OPTION_T2, 4, ((time * 7)/8) - fuzz);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100722 }
723 p = do_req_options(context, p, end, req_options, daemon,
Simon Kelley3d8df262005-08-29 12:19:27 +0100724 hostname, netid, subnet_addr, fqdn_flags);
Simon Kelley44a2a312004-03-10 20:04:35 +0000725 }
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100726
Simon Kelleya2226412004-05-13 20:27:08 +0100727 p = option_end(p, end, mess);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000728 return p - (unsigned char *)mess;
729
730 case DHCPINFORM:
Simon Kelley26128d22004-11-14 16:43:54 +0000731 if (ignore || have_config(config, CONFIG_DISABLE))
Simon Kelleyb8187c82005-11-26 21:46:27 +0000732 message = _("ignored");
Simon Kelley33820b72004-04-03 21:10:00 +0100733
Simon Kelley3d8df262005-08-29 12:19:27 +0100734 log_packet("INFORM", &mess->ciaddr, chaddr, iface_name, message);
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100735
Simon Kelleye17fb622006-01-14 20:33:46 +0000736 if (message || mess->ciaddr.s_addr == 0 ||
737 !(context = narrow_context(context, mess->ciaddr)))
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100738 return 0;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000739
Simon Kelley59353a62004-11-21 19:34:28 +0000740 if (context->netid.net)
741 {
742 context->netid.next = netid;
743 netid = &context->netid;
744 }
745
Simon Kelley0a852542005-03-23 20:28:59 +0000746 mess->siaddr = context->local;
Simon Kelley26128d22004-11-14 16:43:54 +0000747 bootp_option_put(mess, daemon->boot_config, netid);
Simon Kelley44a2a312004-03-10 20:04:35 +0000748 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
Simon Kelley0a852542005-03-23 20:28:59 +0000749 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelleybb01cb92004-12-13 20:56:23 +0000750 if (!hostname)
751 hostname = host_from_dns(daemon, mess->yiaddr);
Simon Kelley3be34542004-09-11 19:12:13 +0100752 p = do_req_options(context, p, end, req_options, daemon,
Simon Kelley3d8df262005-08-29 12:19:27 +0100753 hostname, netid, subnet_addr, fqdn_flags);
Simon Kelleya2226412004-05-13 20:27:08 +0100754 p = option_end(p, end, mess);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000755
Simon Kelley3d8df262005-08-29 12:19:27 +0100756 log_packet("ACK", &mess->ciaddr, chaddr, iface_name, hostname);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000757 return p - (unsigned char *)mess;
758 }
759
760 return 0;
761}
762
Simon Kelley44a2a312004-03-10 20:04:35 +0000763static void log_packet(char *type, struct in_addr *addr, unsigned char *hwaddr, char *interface, char *string)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000764{
Simon Kelley3d8df262005-08-29 12:19:27 +0100765 u8 empty[] = { 0, 0, 0, 0, 0, 0};
766
767 if (!hwaddr)
768 hwaddr = empty;
769
Simon Kelley3be34542004-09-11 19:12:13 +0100770 syslog(LOG_INFO, "%s%s(%s)%s%s %.2x:%.2x:%.2x:%.2x:%.2x:%.2x%s%s",
771 type ? "DHCP" : "BOOTP",
772 type ? type : "",
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000773 interface,
774 addr ? " " : "",
775 addr ? inet_ntoa(*addr) : "",
Simon Kelley44a2a312004-03-10 20:04:35 +0000776 hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5],
777 string ? " " : "",
778 string ? string : "");
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000779}
780
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000781static int option_len(unsigned char *opt)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000782{
783 return opt[1];
784}
785
786static void *option_ptr(unsigned char *opt)
787{
788 return &opt[2];
789}
790
791static struct in_addr option_addr(unsigned char *opt)
792{
793 /* this worries about unaligned data in the option. */
794 /* struct in_addr is network byte order */
795 struct in_addr ret;
796
797 memcpy(&ret, option_ptr(opt), INADDRSZ);
798
799 return ret;
800}
801
Simon Kelley44a2a312004-03-10 20:04:35 +0000802static unsigned int option_uint(unsigned char *opt, int size)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000803{
804 /* this worries about unaligned data and byte order */
Simon Kelley44a2a312004-03-10 20:04:35 +0000805 unsigned int ret = 0;
806 int i;
807 unsigned char *p = option_ptr(opt);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000808
Simon Kelley44a2a312004-03-10 20:04:35 +0000809 for (i = 0; i < size; i++)
810 ret = (ret << 8) | *p++;
811
812 return ret;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000813}
814
Simon Kelley26128d22004-11-14 16:43:54 +0000815static void bootp_option_put(struct dhcp_packet *mess,
816 struct dhcp_boot *boot_opts, struct dhcp_netid *netids)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000817{
Simon Kelley26128d22004-11-14 16:43:54 +0000818 struct dhcp_boot *tmp;
819
820 for (tmp = boot_opts; tmp; tmp = tmp->next)
821 if (match_netid(tmp->netid, netids))
822 break;
823 if (!tmp)
824 /* No match, look for one without a netid */
825 for (tmp = boot_opts; tmp; tmp = tmp->next)
826 if (!tmp->netid)
827 break;
828
829 /* Do this _after_ the matching above, since in
830 BOOTP mode, one if the things we match is the filename. */
831
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000832 memset(mess->sname, 0, sizeof(mess->sname));
833 memset(mess->file, 0, sizeof(mess->file));
Simon Kelley26128d22004-11-14 16:43:54 +0000834
835 if (tmp)
836 {
837 if (tmp->sname)
Simon Kelley3d8df262005-08-29 12:19:27 +0100838 strncpy((char *)mess->sname, tmp->sname, sizeof(mess->sname)-1);
Simon Kelley26128d22004-11-14 16:43:54 +0000839 if (tmp->file)
Simon Kelley3d8df262005-08-29 12:19:27 +0100840 strncpy((char *)mess->file, tmp->file, sizeof(mess->file)-1);
Simon Kelley26128d22004-11-14 16:43:54 +0000841 if (tmp->next_server.s_addr)
842 mess->siaddr = tmp->next_server;
843 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000844}
845
Simon Kelleyb8187c82005-11-26 21:46:27 +0000846static int check_space(unsigned char *p, unsigned char *end, int len, int opt)
847{
848 /* always keep one octet space for the END option. */
849 if (p + len + 3 >= end)
850 {
851 syslog(LOG_WARNING, _("cannot send DHCP option %d: no space left in packet"), opt);
852 return 0;
853 }
854
855 return 1;
856}
857
858
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000859static unsigned char *option_put(unsigned char *p, unsigned char *end, int opt, int len, unsigned int val)
860{
861 int i;
Simon Kelley44a2a312004-03-10 20:04:35 +0000862
Simon Kelleyb8187c82005-11-26 21:46:27 +0000863 if (check_space(p, end, len, opt))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000864 {
865 *(p++) = opt;
Simon Kelleya2226412004-05-13 20:27:08 +0100866 *(p++) = len;
867
868 for (i = 0; i < len; i++)
869 *(p++) = val >> (8 * (len - (i + 1)));
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000870 }
871 return p;
872}
873
Simon Kelleya2226412004-05-13 20:27:08 +0100874static unsigned char *option_end(unsigned char *p, unsigned char *end, struct dhcp_packet *start)
875{
876 *(p++) = OPTION_END;
877 while ((p < end) && (p - ((unsigned char *)start) < MIN_PACKETSZ))
878 *p++ = 0;
879
880 return p;
881}
882
Simon Kelley44a2a312004-03-10 20:04:35 +0000883static unsigned char *option_put_string(unsigned char *p, unsigned char *end, int opt, char *string)
884{
Simon Kelley0a852542005-03-23 20:28:59 +0000885 size_t len = strlen(string);
Simon Kelley3be34542004-09-11 19:12:13 +0100886
Simon Kelleyb8187c82005-11-26 21:46:27 +0000887 if (check_space(p, end, len, opt))
Simon Kelley44a2a312004-03-10 20:04:35 +0000888 {
889 *(p++) = opt;
Simon Kelley3be34542004-09-11 19:12:13 +0100890 *(p++) = len;
891 memcpy(p, string, len);
892 p += len;
Simon Kelley44a2a312004-03-10 20:04:35 +0000893 }
Simon Kelley3be34542004-09-11 19:12:13 +0100894
Simon Kelley44a2a312004-03-10 20:04:35 +0000895 return p;
896}
897
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000898static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int *overload)
899{
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000900 if (!p)
901 return NULL;
902
903 while (*p != OPTION_END)
904 {
Simon Kelleybb01cb92004-12-13 20:56:23 +0000905 if (p >= end)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000906 return 0; /* malformed packet */
907 else if (*p == OPTION_PAD)
908 p++;
909 else if (*p == OPTION_OVERLOAD)
910 {
Simon Kelleybb01cb92004-12-13 20:56:23 +0000911 if (p >= end - 3)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000912 return 0; /* malformed packet */
913 if (overload)
914 *overload = *(p+2);
915 p += 3;
916 }
917 else
918 {
919 int opt_len;;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000920 if (p >= end - 2)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000921 return 0; /* malformed packet */
922 opt_len = option_len(p);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000923 if (p >= end - (2 + opt_len))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000924 return 0; /* malformed packet */
925 if (*p == opt)
926 return p;
927 p += opt_len + 2;
928 }
929 }
930
931 return NULL;
932}
933
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000934static unsigned char *option_find(struct dhcp_packet *mess, int size, int opt_type, int minsize)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000935{
936 int overload = 0;
937 unsigned char *ret;
938
Simon Kelley3be34542004-09-11 19:12:13 +0100939 /* skip over DHCP cookie; */
940 ret = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + size, opt_type, &overload);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000941
942 if (!ret && (overload & 1))
943 ret = option_find1(&mess->file[0], &mess->file[128], opt_type, &overload);
944
945 if (!ret && (overload & 2))
946 ret = option_find1(&mess->sname[0], &mess->file[64], opt_type, &overload);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000947
948 /* Check the option field is big enough */
949 if (ret && (option_len(ret) < minsize))
950 ret = NULL;
951
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000952 return ret;
953}
954
955static int in_list(unsigned char *list, int opt)
956{
957 int i;
958
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100959 /* If no requested options, send everything, not nothing. */
960 if (!list)
961 return 1;
962
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000963 for (i = 0; list[i] != OPTION_END; i++)
964 if (opt == list[i])
965 return 1;
966
967 return 0;
968}
969
Simon Kelleya2226412004-05-13 20:27:08 +0100970static struct dhcp_opt *option_find2(struct dhcp_netid *netid, struct dhcp_opt *opts, int opt)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000971{
Simon Kelley91dccd02005-03-31 17:48:32 +0100972 struct dhcp_opt *tmp;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100973 for (tmp = opts; tmp; tmp = tmp->next)
Simon Kelleya2226412004-05-13 20:27:08 +0100974 if (tmp->opt == opt)
975 {
976 if (netid)
977 {
Simon Kelley26128d22004-11-14 16:43:54 +0000978 if (match_netid(tmp->netid, netid))
979 return tmp;
Simon Kelleya2226412004-05-13 20:27:08 +0100980 }
981 else if (!tmp->netid)
982 return tmp;
983 }
984
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100985 return netid ? option_find2(NULL, opts, opt) : NULL;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000986}
987
Simon Kelley91dccd02005-03-31 17:48:32 +0100988static unsigned char *do_opt(struct dhcp_opt *opt, unsigned char *p, unsigned char *end, struct in_addr local)
989{
Simon Kelleyb8187c82005-11-26 21:46:27 +0000990 if (!check_space(p, end, opt->len, opt->opt))
Simon Kelley91dccd02005-03-31 17:48:32 +0100991 return p;
Simon Kelleyb8187c82005-11-26 21:46:27 +0000992
Simon Kelley91dccd02005-03-31 17:48:32 +0100993 *(p++) = opt->opt;
994 *(p++) = opt->len;
995
996 if (opt->len == 0)
997 return p;
998
999 if (opt->is_addr && !opt->vendor_class)
1000 {
1001 int j;
1002 struct in_addr *a = (struct in_addr *)opt->val;
1003 for (j = 0; j < opt->len; j+=INADDRSZ, a++)
1004 {
1005 /* zero means "self" (but not in vendorclass options.) */
1006 if (a->s_addr == 0)
1007 memcpy(p, &local, INADDRSZ);
1008 else
1009 memcpy(p, a, INADDRSZ);
1010 p += INADDRSZ;
1011 }
1012 }
1013 else
1014 {
1015 memcpy(p, opt->val, opt->len);
1016 p += opt->len;
1017 }
1018
1019 return p;
1020}
1021
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001022static unsigned char *do_req_options(struct dhcp_context *context,
1023 unsigned char *p, unsigned char *end,
1024 unsigned char *req_options,
Simon Kelley3be34542004-09-11 19:12:13 +01001025 struct daemon *daemon,
1026 char *hostname,
Simon Kelley3be34542004-09-11 19:12:13 +01001027 struct dhcp_netid *netid,
Simon Kelley3d8df262005-08-29 12:19:27 +01001028 struct in_addr subnet_addr,
1029 unsigned char fqdn_flags)
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001030{
Simon Kelley3be34542004-09-11 19:12:13 +01001031 struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
Simon Kelley91dccd02005-03-31 17:48:32 +01001032 char *vendor_class = NULL;
Simon Kelley3be34542004-09-11 19:12:13 +01001033
Simon Kelley1ab84e22004-01-29 16:48:35 +00001034 if (in_list(req_options, OPTION_MAXMESSAGE))
Simon Kelley3be34542004-09-11 19:12:13 +01001035 p = option_put(p, end, OPTION_MAXMESSAGE, 2, end - (unsigned char *)daemon->dhcp_packet);
Simon Kelley1ab84e22004-01-29 16:48:35 +00001036
Simon Kelley3be34542004-09-11 19:12:13 +01001037 /* rfc3011 says this doesn't need to be in the requested options list. */
1038 if (subnet_addr.s_addr)
1039 p = option_put(p, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr));
1040
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001041 if (in_list(req_options, OPTION_NETMASK) &&
Simon Kelley33820b72004-04-03 21:10:00 +01001042 !option_find2(netid, config_opts, OPTION_NETMASK))
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001043 p = option_put(p, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr));
1044
Simon Kelley3be34542004-09-11 19:12:13 +01001045 /* May not have a "guessed" broadcast address if we got no packets via a relay
1046 from this net yet (ie just unicast renewals after a restart */
1047 if (context->broadcast.s_addr &&
1048 in_list(req_options, OPTION_BROADCAST) &&
Simon Kelley33820b72004-04-03 21:10:00 +01001049 !option_find2(netid, config_opts, OPTION_BROADCAST))
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001050 p = option_put(p, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
1051
Simon Kelley3be34542004-09-11 19:12:13 +01001052 /* Same comments as broadcast apply, and also may not be able to get a sensible
1053 default when using subnet select. User must configure by steam in that case. */
1054 if (context->router.s_addr &&
1055 in_list(req_options, OPTION_ROUTER) &&
Simon Kelley33820b72004-04-03 21:10:00 +01001056 !option_find2(netid, config_opts, OPTION_ROUTER))
Simon Kelley3be34542004-09-11 19:12:13 +01001057 p = option_put(p, end, OPTION_ROUTER, INADDRSZ, ntohl(context->router.s_addr));
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001058
1059 if (in_list(req_options, OPTION_DNSSERVER) &&
Simon Kelley33820b72004-04-03 21:10:00 +01001060 !option_find2(netid, config_opts, OPTION_DNSSERVER))
Simon Kelley0a852542005-03-23 20:28:59 +00001061 p = option_put(p, end, OPTION_DNSSERVER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001062
Simon Kelley3be34542004-09-11 19:12:13 +01001063 if (daemon->domain_suffix && in_list(req_options, OPTION_DOMAINNAME) &&
Simon Kelley33820b72004-04-03 21:10:00 +01001064 !option_find2(netid, config_opts, OPTION_DOMAINNAME))
Simon Kelley3be34542004-09-11 19:12:13 +01001065 p = option_put_string(p, end, OPTION_DOMAINNAME, daemon->domain_suffix);
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001066
1067 /* Note that we ignore attempts to set the hostname using
Simon Kelley3d8df262005-08-29 12:19:27 +01001068 --dhcp-option=12,<name> and the fqdn using
1069 --dhc-option=81,<name> */
1070 if (hostname)
1071 {
1072 if (in_list(req_options, OPTION_HOSTNAME))
1073 p = option_put_string(p, end, OPTION_HOSTNAME, hostname);
1074
1075 if (fqdn_flags != 0)
1076 {
1077 int len = strlen(hostname) + 3;
1078 if (fqdn_flags & 0x04)
1079 len += 2;
1080
1081 if (daemon->domain_suffix)
1082 len += strlen(daemon->domain_suffix) + 1;
1083
1084 if (p + len + 1 < end)
1085 {
1086 *(p++) = OPTION_CLIENT_FQDN;
1087 *(p++) = len;
1088 *(p++) = fqdn_flags;
1089 *(p++) = 255;
1090 *(p++) = 255;
1091
1092 if (fqdn_flags & 0x04)
1093 {
1094 p = do_rfc1035_name(p, hostname);
1095 if (daemon->domain_suffix)
1096 p = do_rfc1035_name(p, daemon->domain_suffix);
1097 *p++ = 0;
1098 }
1099 else
1100 {
1101 memcpy(p, hostname, strlen(hostname));
1102 p += strlen(hostname);
1103 if (daemon->domain_suffix)
1104 {
1105 *(p++) = '.';
1106 memcpy(p, daemon->domain_suffix, strlen(daemon->domain_suffix));
1107 p += strlen(daemon->domain_suffix);
1108 }
1109 }
1110 }
1111 }
1112 }
1113
Simon Kelleya84fa1d2004-04-23 22:21:21 +01001114 for (opt=config_opts; opt; opt = opt->next)
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001115 {
Simon Kelleya84fa1d2004-04-23 22:21:21 +01001116 if (opt->opt == OPTION_HOSTNAME ||
Simon Kelley3d8df262005-08-29 12:19:27 +01001117 opt->opt == OPTION_CLIENT_FQDN ||
Simon Kelleya84fa1d2004-04-23 22:21:21 +01001118 opt->opt == OPTION_MAXMESSAGE ||
1119 !in_list(req_options, opt->opt) ||
Simon Kelley91dccd02005-03-31 17:48:32 +01001120 opt != option_find2(netid, config_opts, opt->opt))
Simon Kelley33820b72004-04-03 21:10:00 +01001121 continue;
1122
1123 /* For the options we have default values on
1124 dhc-option=<optionno> means "don't include this option"
1125 not "include a zero-length option" */
1126 if (opt->len == 0 &&
1127 (opt->opt == OPTION_NETMASK ||
1128 opt->opt == OPTION_BROADCAST ||
1129 opt->opt == OPTION_ROUTER ||
1130 opt->opt == OPTION_DNSSERVER))
1131 continue;
Simon Kelley91dccd02005-03-31 17:48:32 +01001132
1133 /* opt->val has terminating zero */
1134 if (opt->opt == OPTION_VENDOR_ID)
Simon Kelley3d8df262005-08-29 12:19:27 +01001135 vendor_class = (char *)opt->val;
Simon Kelley91dccd02005-03-31 17:48:32 +01001136 else
1137 p = do_opt(opt, p, end, context->local);
1138 }
1139
1140 if (in_list(req_options, OPTION_VENDOR_ID))
1141 {
1142 for (opt = daemon->vendor_opts; opt; opt = opt->next)
1143 if (!opt->netid || match_netid(opt->netid, netid))
1144 {
Simon Kelley3d8df262005-08-29 12:19:27 +01001145 if (vendor_class && strcmp(vendor_class, (char *)opt->vendor_class) != 0)
Simon Kelleyb8187c82005-11-26 21:46:27 +00001146 syslog(LOG_WARNING, _("More than one vendor class matches, using %s"), vendor_class);
Simon Kelley91dccd02005-03-31 17:48:32 +01001147 else
Simon Kelley3d8df262005-08-29 12:19:27 +01001148 vendor_class = (char *)opt->vendor_class;
Simon Kelley91dccd02005-03-31 17:48:32 +01001149 }
1150
1151 if (vendor_class)
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001152 {
Simon Kelley91dccd02005-03-31 17:48:32 +01001153 p = option_put_string(p, end, OPTION_VENDOR_ID, vendor_class);
1154
1155 if (in_list(req_options, OPTION_VENDOR_CLASS_OPT))
Simon Kelley1ab84e22004-01-29 16:48:35 +00001156 {
Simon Kelley91dccd02005-03-31 17:48:32 +01001157 unsigned char *plen, *oend = end;
1158
1159 /* encapsulated options can only be 256 bytes,
1160 even of the packet is larger */
1161 if (p + 256 < end)
1162 oend = p + 256;
1163
1164 if (p + 3 >= oend)
1165 return p;
1166
1167 *(p++) = OPTION_VENDOR_CLASS_OPT;
1168 plen = p++; /* fill in later */
1169
1170 for (opt = daemon->vendor_opts; opt; opt = opt->next)
1171 if ((!opt->netid || match_netid(opt->netid, netid)) &&
Simon Kelley3d8df262005-08-29 12:19:27 +01001172 strcmp(vendor_class, (char *)opt->vendor_class) == 0)
Simon Kelley91dccd02005-03-31 17:48:32 +01001173 p = do_opt(opt, p, oend, context->local);
1174
1175 *plen = p - plen - 1;
Simon Kelley1ab84e22004-01-29 16:48:35 +00001176 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001177 }
Simon Kelley91dccd02005-03-31 17:48:32 +01001178 }
1179
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001180 return p;
1181}
1182
1183