blob: eaf492e3912feaff4f7fb1a62ee362b8f5b0dd07 [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 Kelley0a852542005-03-23 20:28:59 +000081int dhcp_reply(struct daemon *daemon, struct dhcp_context *context, char *iface_name, unsigned int sz, time_t now)
Simon Kelley33820b72004-04-03 21:10:00 +010082{
Simon Kelley26128d22004-11-14 16:43:54 +000083 unsigned char *opt, *clid = NULL;
Simon Kelley0a852542005-03-23 20:28:59 +000084 struct dhcp_lease *ltmp, *lease = NULL;
Simon Kelleya2226412004-05-13 20:27:08 +010085 struct dhcp_vendor *vendor;
Simon Kelley26128d22004-11-14 16:43:54 +000086 struct dhcp_netid_list *id_list;
87 int clid_len = 0, ignore = 0;
Simon Kelley3be34542004-09-11 19:12:13 +010088 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);
Simon Kelley3d8df262005-08-29 12:19:27 +010091 char *hostname = NULL, *offer_hostname = NULL, *client_hostname = NULL;
92 unsigned char *req_options = NULL;
Simon Kelley44a2a312004-03-10 20:04:35 +000093 char *message = NULL;
Simon Kelley59353a62004-11-21 19:34:28 +000094 unsigned int time;
Simon Kelley9e4abcb2004-01-22 19:47:41 +000095 struct dhcp_config *config;
Simon Kelleya2226412004-05-13 20:27:08 +010096 struct dhcp_netid *netid = NULL;
Simon Kelley3d8df262005-08-29 12:19:27 +010097 struct in_addr subnet_addr;
Simon Kelleya84fa1d2004-04-23 22:21:21 +010098 unsigned short fuzz = 0;
Simon Kelley3be34542004-09-11 19:12:13 +010099 unsigned int mess_type = 0;
Simon Kelley3d8df262005-08-29 12:19:27 +0100100 u8 *chaddr;
101 unsigned char fqdn_flags = 0;
Simon Kelley3be34542004-09-11 19:12:13 +0100102 subnet_addr.s_addr = 0;
103
104 if (mess->op != BOOTREQUEST)
Simon Kelley33820b72004-04-03 21:10:00 +0100105 return 0;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000106
Simon Kelley3be34542004-09-11 19:12:13 +0100107 /* check for DHCP rather than BOOTP */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000108 if ((opt = option_find(mess, sz, OPTION_MESSAGE_TYPE, 1)))
Simon Kelley44a2a312004-03-10 20:04:35 +0000109 {
Simon Kelley3be34542004-09-11 19:12:13 +0100110 mess_type = option_uint(opt, 1);
Simon Kelley44a2a312004-03-10 20:04:35 +0000111
Simon Kelley3be34542004-09-11 19:12:13 +0100112 /* only insist on a cookie for DHCP. */
113 if (*((u32 *)&mess->options) != htonl(DHCP_COOKIE))
114 return 0;
115
116 /* Some buggy clients set ciaddr when they shouldn't, so clear that here since
117 it can affect the context-determination code. */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000118 if ((option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ) || mess_type == DHCPDISCOVER))
Simon Kelley3be34542004-09-11 19:12:13 +0100119 mess->ciaddr.s_addr = 0;
120
121 /* Check for RFC3011 subnet selector */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000122 if ((opt = option_find(mess, sz, OPTION_SUBNET_SELECT, INADDRSZ)))
Simon Kelley3be34542004-09-11 19:12:13 +0100123 subnet_addr = option_addr(opt);
Simon Kelley26128d22004-11-14 16:43:54 +0000124
125 /* If there is no client identifier option, use the hardware address */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000126 if ((opt = option_find(mess, sz, OPTION_CLIENT_ID, 1)))
Simon Kelley26128d22004-11-14 16:43:54 +0000127 {
Simon Kelley26128d22004-11-14 16:43:54 +0000128 clid_len = option_len(opt);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000129 clid = option_ptr(opt);
Simon Kelley26128d22004-11-14 16:43:54 +0000130 }
Simon Kelley0a852542005-03-23 20:28:59 +0000131
132 /* do we have a lease in store? */
Simon Kelley3d8df262005-08-29 12:19:27 +0100133 if (mess->htype != 0)
134 lease = lease_find_by_client(mess->chaddr, clid, clid_len);
Simon Kelley0a852542005-03-23 20:28:59 +0000135
136 /* If this request is missing a clid, but we've seen one before,
137 use it again for option matching etc. */
138 if (lease && !clid && lease->clid)
139 {
140 clid_len = lease->clid_len;
141 clid = lease->clid;
142 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000143 }
Simon Kelley3be34542004-09-11 19:12:13 +0100144
Simon Kelley3d8df262005-08-29 12:19:27 +0100145 /* htype == 0 is only allowed in DHCPINFORM, this seems to be a
146 microsoftism. chaddr == NULL in that case */
147
148 if (mess->htype == 0 && mess_type == DHCPINFORM)
149 {
150 chaddr = NULL;
151 if (mess->hlen != 0)
152 return 0;
153 }
154 else
155 {
156 chaddr = mess->chaddr;
157
158 /* Token ring is supported when we have packet sockets
159 to make the HW headers for us. We don't have the code to build
160 token ring headers when using BPF. We rely on the fact that
161 token ring hwaddrs are the same size as ethernet hwaddrs. */
162
163#ifdef HAVE_BPF
164 if (mess->htype != ARPHRD_ETHER)
165#else
166 if (mess->htype != ARPHRD_ETHER && mess->htype != ARPHRD_IEEE802)
167#endif
168 {
169 syslog(LOG_WARNING, "DHCP request for unsupported hardware type (%d) recieved on %s",
170 mess->htype, iface_name);
171 return 0;
172 }
173
174 if (mess->hlen != ETHER_ADDR_LEN)
175 return 0;
176 }
177
Simon Kelley0a852542005-03-23 20:28:59 +0000178 /* Determine network for this packet. Our caller will have already linked all the
179 contexts which match the addresses of the receiving interface but if the
180 machine has an address already, or came via a relay, or we have a subnet selector,
181 we search again. If we don't have have a giaddr or explicit subnet selector,
182 use the ciaddr. This is necessary because a machine which got a lease via a
Simon Kelley3d8df262005-08-29 12:19:27 +0100183 relay won't use the relay to renew. If matching a ciaddr fails but we have a context
184 from the physical network, continue using that to allow correct DHCPNAK generation later. */
Simon Kelley0a852542005-03-23 20:28:59 +0000185 if (mess->giaddr.s_addr || subnet_addr.s_addr || mess->ciaddr.s_addr)
186 {
Simon Kelley3d8df262005-08-29 12:19:27 +0100187 struct dhcp_context *context_tmp, *context_new = NULL;
188 struct in_addr addr = mess->ciaddr;
189 int force = 0;
Simon Kelley0a852542005-03-23 20:28:59 +0000190
Simon Kelley3d8df262005-08-29 12:19:27 +0100191 if (subnet_addr.s_addr)
192 {
193 addr = subnet_addr;
194 force = 1;
195 }
196 else if (mess->giaddr.s_addr)
197 {
198 addr = mess->giaddr;
199 force = 1;
200 }
201
202 for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next)
Simon Kelley0a852542005-03-23 20:28:59 +0000203 if (context_tmp->netmask.s_addr &&
204 is_same_net(addr, context_tmp->start, context_tmp->netmask) &&
205 is_same_net(addr, context_tmp->end, context_tmp->netmask))
206 {
Simon Kelley3d8df262005-08-29 12:19:27 +0100207 context_tmp->current = context_new;
208 context_new = context_tmp;
Simon Kelley0a852542005-03-23 20:28:59 +0000209 }
Simon Kelley3d8df262005-08-29 12:19:27 +0100210
211 if (context_new || force)
212 context = context_new;
213
Simon Kelley0a852542005-03-23 20:28:59 +0000214 }
Simon Kelley3be34542004-09-11 19:12:13 +0100215
216 if (!context)
217 {
218 syslog(LOG_WARNING, "no address range available for DHCP request %s %s",
Simon Kelley0a852542005-03-23 20:28:59 +0000219 subnet_addr.s_addr ? "with subnet selector" : "via",
220 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 +0100221 return 0;
222 }
223
224 mess->op = BOOTREPLY;
Simon Kelley36717ee2004-09-20 19:20:58 +0100225
Simon Kelley3d8df262005-08-29 12:19:27 +0100226 config = find_config(daemon->dhcp_conf, context, clid, clid_len, chaddr, NULL);
Simon Kelley26128d22004-11-14 16:43:54 +0000227
Simon Kelley3be34542004-09-11 19:12:13 +0100228 if (mess_type == 0)
229 {
230 /* BOOTP request */
Simon Kelley26128d22004-11-14 16:43:54 +0000231 struct dhcp_netid id;
232 char save = mess->file[128];
233 struct in_addr *logaddr = NULL;
234
Simon Kelley26128d22004-11-14 16:43:54 +0000235 if (have_config(config, CONFIG_DISABLE))
236 message = "disabled";
237
238 end = mess->options + 64; /* BOOTP vend area is only 64 bytes */
239
240 if (have_config(config, CONFIG_NAME))
241 hostname = config->hostname;
242
243 if (have_config(config, CONFIG_NETID))
244 {
245 config->netid.next = netid;
246 netid = &config->netid;
247 }
248
249 /* Match incoming filename field as a netid. */
250 if (mess->file[0])
251 {
252 mess->file[128] = 0; /* ensure zero term. */
Simon Kelley3d8df262005-08-29 12:19:27 +0100253 id.net = (char *)mess->file;
Simon Kelley26128d22004-11-14 16:43:54 +0000254 id.next = netid;
255 netid = &id;
256 }
257
258 for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
259 if (match_netid(id_list->list, netid))
260 message = "disabled";
261
Simon Kelley3d8df262005-08-29 12:19:27 +0100262 if (!message)
263 {
264 if (have_config(config, CONFIG_ADDR))
265 {
266 logaddr = &config->addr;
267 mess->yiaddr = config->addr;
268 if ((lease = lease_find_by_addr(config->addr)) &&
269 memcmp(lease->hwaddr, chaddr, ETHER_ADDR_LEN) != 0)
270 message = "address in use";
271 }
272 else if (!(daemon->options & OPT_BOOTP_DYNAMIC))
273 message = "no address configured";
274 else
275 {
276 if ((lease = lease_find_by_client(mess->chaddr, NULL, 0)))
277 mess->yiaddr = lease->addr;
278 else if (!address_allocate(context, daemon, &mess->yiaddr, chaddr, netid, now))
279 message = "no address available";
280 }
281
282 if (!message && !lease && (!(lease = lease_allocate(chaddr, NULL, 0, mess->yiaddr))))
283 message = "no leases left";
284
285 if (!message)
286 {
287 logaddr = &mess->yiaddr;
288 context = narrow_context(context, mess->yiaddr);
289
290 if (context->netid.net && !(context->flags & CONTEXT_FILTER))
291 {
292 context->netid.next = netid;
293 netid = &context->netid;
294 }
295
296 lease_set_hwaddr(lease, chaddr, NULL, 0);
297 if (hostname)
298 lease_set_hostname(lease, hostname, daemon->domain_suffix);
299 lease_set_expires(lease, 0); /* infinite lease */
300
301 p = do_req_options(context, p, end, NULL, daemon,
302 hostname, netid, subnet_addr, fqdn_flags);
303 /* must do this after do_req_options since it overwrites filename field. */
304 mess->siaddr = context->local;
305 bootp_option_put(mess, daemon->boot_config, netid);
306 p = option_end(p, end, mess);
307 }
308 }
309
310 log_packet(NULL, logaddr, chaddr, iface_name, message);
Simon Kelley26128d22004-11-14 16:43:54 +0000311 mess->file[128] = save;
312
313 if (message)
314 return 0;
315 else
316 return p - (unsigned char *)mess;
Simon Kelley3be34542004-09-11 19:12:13 +0100317 }
318
Simon Kelley3d8df262005-08-29 12:19:27 +0100319 if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 4)))
320 {
321 /* http://tools.ietf.org/wg/dhc/draft-ietf-dhc-fqdn-option/draft-ietf-dhc-fqdn-option-10.txt */
322 int len = option_len(opt);
323 char *pq = daemon->dhcp_buff;
324 unsigned char *pp, *op = option_ptr(opt);
325
326 fqdn_flags = *op;
327 len -= 3;
328 op += 3;
329 pp = op;
330
331 /* Always force update, since the client has no way to do it itself. */
332 if (fqdn_flags & 0x01)
333 fqdn_flags |= 0x02;
334
335 fqdn_flags &= ~0x08;
336 fqdn_flags |= 0x01;
337
338 if (fqdn_flags & 0x04)
339 while (*op != 0 && ((op + (*op) + 1) - pp) < len)
340 {
341 memcpy(pq, op+1, *op);
342 pq += *op;
343 op += (*op)+1;
344 *(pq++) = '.';
345 }
346 else
347 {
348 memcpy(pq, op, len);
349 pq += len + 1;
350 }
351
352 if (pq != daemon->dhcp_buff)
353 pq--;
354
355 *pq = 0;
356
357 if (canonicalise(daemon->dhcp_buff))
358 offer_hostname = client_hostname = daemon->dhcp_buff;
359 }
Simon Kelleybb01cb92004-12-13 20:56:23 +0000360 else if ((opt = option_find(mess, sz, OPTION_HOSTNAME, 1)))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000361 {
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000362 int len = option_len(opt);
Simon Kelley3d8df262005-08-29 12:19:27 +0100363 memcpy(daemon->dhcp_buff, option_ptr(opt), len);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000364 /* May not be zero terminated */
Simon Kelley3d8df262005-08-29 12:19:27 +0100365 daemon->dhcp_buff[len] = 0;
366 if (canonicalise(daemon->dhcp_buff))
367 client_hostname = daemon->dhcp_buff;
368 }
369
370 if (have_config(config, CONFIG_NAME))
371 {
372 hostname = config->hostname;
373 /* be careful not to send an OFFER with a hostname not
374 matching the DISCOVER. */
375 if (fqdn_flags != 0 || !client_hostname || hostname_isequal(hostname, client_hostname))
376 offer_hostname = hostname;
377 }
378 else if (client_hostname && (hostname = strip_hostname(daemon, client_hostname)) && !config)
379 {
380 /* Search again now we have a hostname.
381 Only accept configs without CLID and HWADDR here, (they won't match)
382 to avoid impersonation by name. */
383 struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, chaddr, hostname);
384 if (!have_config(new, CONFIG_CLID) && !have_config(new, CONFIG_HWADDR))
385 config = new;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000386 }
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100387
Simon Kelleya2226412004-05-13 20:27:08 +0100388 if (have_config(config, CONFIG_NETID))
389 {
390 config->netid.next = netid;
391 netid = &config->netid;
392 }
Simon Kelley36717ee2004-09-20 19:20:58 +0100393
Simon Kelley26128d22004-11-14 16:43:54 +0000394 /* user-class options are, according to RFC3004, supposed to contain
395 a set of counted strings. Here we check that this is so (by seeing
396 if the counts are consistent with the overall option length) and if
397 so zero the counts so that we don't get spurious matches between
398 the vendor string and the counts. If the lengths don't add up, we
399 assume that the option is a single string and non RFC3004 compliant
400 and just do the substring match. dhclient provides these broken options. */
Simon Kelleya2226412004-05-13 20:27:08 +0100401
Simon Kelleybb01cb92004-12-13 20:56:23 +0000402 if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
Simon Kelleya2226412004-05-13 20:27:08 +0100403 {
Simon Kelley26128d22004-11-14 16:43:54 +0000404 unsigned char *ucp = option_ptr(opt);
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000405 int tmp, j;
Simon Kelley26128d22004-11-14 16:43:54 +0000406 for (j = 0; j < option_len(opt); j += ucp[j] + 1);
407 if (j == option_len(opt))
408 for (j = 0; j < option_len(opt); j = tmp)
409 {
410 tmp = j + ucp[j] + 1;
411 ucp[j] = 0;
412 }
Simon Kelleya2226412004-05-13 20:27:08 +0100413 }
Simon Kelley26128d22004-11-14 16:43:54 +0000414
415 for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
Simon Kelleybb01cb92004-12-13 20:56:23 +0000416 if ((opt = option_find(mess, sz, vendor->is_vendor ? OPTION_VENDOR_ID : OPTION_USER_CLASS, 1)))
Simon Kelley26128d22004-11-14 16:43:54 +0000417 {
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000418 int i;
Simon Kelley26128d22004-11-14 16:43:54 +0000419 for (i = 0; i <= (option_len(opt) - vendor->len); i++)
420 if (memcmp(vendor->data, option_ptr(opt)+i, vendor->len) == 0)
421 {
422 vendor->netid.next = netid;
423 netid = &vendor->netid;
424 break;
425 }
426 }
427
428 /* if all the netids in the ignore list are present, ignore this client */
429 for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
430 if (match_netid(id_list->list, netid))
431 ignore = 1;
432
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100433 /* Can have setting to ignore the client ID for a particular MAC address or hostname */
434 if (have_config(config, CONFIG_NOCLID))
Simon Kelley0a852542005-03-23 20:28:59 +0000435 clid = NULL;
436
Simon Kelleybb01cb92004-12-13 20:56:23 +0000437 if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 0)))
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100438 {
Simon Kelley3d8df262005-08-29 12:19:27 +0100439 req_options = (unsigned char *)daemon->dhcp_buff2;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000440 memcpy(req_options, option_ptr(opt), option_len(opt));
441 req_options[option_len(opt)] = OPTION_END;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100442 }
443
Simon Kelley3be34542004-09-11 19:12:13 +0100444 switch (mess_type)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000445 {
Simon Kelley44a2a312004-03-10 20:04:35 +0000446 case DHCPDECLINE:
Simon Kelleybb01cb92004-12-13 20:56:23 +0000447 if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
Simon Kelley0a852542005-03-23 20:28:59 +0000448 (context->local.s_addr != option_addr(opt).s_addr))
Simon Kelley44a2a312004-03-10 20:04:35 +0000449 return 0;
450
451 /* sanitise any message. Paranoid? Moi? */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000452 if ((opt = option_find(mess, sz, OPTION_MESSAGE, 1)))
Simon Kelley44a2a312004-03-10 20:04:35 +0000453 {
Simon Kelley3be34542004-09-11 19:12:13 +0100454 char *p = option_ptr(opt), *q = daemon->dhcp_buff;
Simon Kelley44a2a312004-03-10 20:04:35 +0000455 int i;
456
457 for (i = option_len(opt); i > 0; i--)
458 {
459 char c = *p++;
460 if (isprint(c))
461 *q++ = c;
462 }
463 *q++ = 0; /* add terminator */
Simon Kelley3be34542004-09-11 19:12:13 +0100464 message = daemon->dhcp_buff;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000465 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000466
Simon Kelleybb01cb92004-12-13 20:56:23 +0000467 if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
Simon Kelley44a2a312004-03-10 20:04:35 +0000468 return 0;
469
Simon Kelley3d8df262005-08-29 12:19:27 +0100470 log_packet("DECLINE", option_ptr(opt), chaddr, iface_name, message);
Simon Kelley44a2a312004-03-10 20:04:35 +0000471
472 if (lease && lease->addr.s_addr == option_addr(opt).s_addr)
473 lease_prune(lease, now);
474
Simon Kelley33820b72004-04-03 21:10:00 +0100475 if (have_config(config, CONFIG_ADDR) &&
Simon Kelley44a2a312004-03-10 20:04:35 +0000476 config->addr.s_addr == option_addr(opt).s_addr)
477 {
478 syslog(LOG_WARNING, "disabling DHCP static address %s", inet_ntoa(config->addr));
Simon Kelley33820b72004-04-03 21:10:00 +0100479 config->flags &= ~CONFIG_ADDR ;
Simon Kelley44a2a312004-03-10 20:04:35 +0000480 }
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100481 else
482 /* make sure this host gets a different address next time. */
Simon Kelley36717ee2004-09-20 19:20:58 +0100483 for (; context; context = context->current)
484 context->addr_epoch++;
Simon Kelley44a2a312004-03-10 20:04:35 +0000485
486 return 0;
487
488 case DHCPRELEASE:
Simon Kelleybb01cb92004-12-13 20:56:23 +0000489 if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
Simon Kelley0a852542005-03-23 20:28:59 +0000490 (context->local.s_addr != option_addr(opt).s_addr))
Simon Kelley44a2a312004-03-10 20:04:35 +0000491 return 0;
492
Simon Kelley44a2a312004-03-10 20:04:35 +0000493 if (lease && lease->addr.s_addr == mess->ciaddr.s_addr)
494 lease_prune(lease, now);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100495 else
496 message = "unknown lease";
497
Simon Kelley3d8df262005-08-29 12:19:27 +0100498 log_packet("RELEASE", &mess->ciaddr, chaddr, iface_name, message);
Simon Kelley44a2a312004-03-10 20:04:35 +0000499
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000500 return 0;
501
502 case DHCPDISCOVER:
Simon Kelley3d8df262005-08-29 12:19:27 +0100503 {
504 struct in_addr addr;
505
506 if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
507 addr = option_addr(opt);
508 if (ignore || have_config(config, CONFIG_DISABLE))
509 message = "ignored";
510 else if (have_config(config, CONFIG_ADDR) &&
511 (!(ltmp = lease_find_by_addr(config->addr)) || ltmp == lease))
512 mess->yiaddr = config->addr;
513 else if (lease && address_available(context, lease->addr))
514 mess->yiaddr = lease->addr;
515 else if (opt && address_available(context, addr) && !lease_find_by_addr(addr) &&
516 !config_find_by_address(daemon->dhcp_conf, addr))
517 mess->yiaddr = addr;
518 else if (!address_allocate(context, daemon, &mess->yiaddr, chaddr, netid, now))
519 message = "no address available";
520 log_packet("DISCOVER", opt ? &addr : NULL, chaddr, iface_name, message);
521 }
522
Simon Kelley33820b72004-04-03 21:10:00 +0100523 if (message)
524 return 0;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100525
Simon Kelley59353a62004-11-21 19:34:28 +0000526 context = narrow_context(context, mess->yiaddr);
Simon Kelley0a852542005-03-23 20:28:59 +0000527 if (context->netid.net && !(context->flags & CONTEXT_FILTER))
Simon Kelley59353a62004-11-21 19:34:28 +0000528 {
529 context->netid.next = netid;
530 netid = &context->netid;
531 }
532
533 time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000534 if ((opt = option_find(mess, sz, OPTION_LEASE_TIME, 4)))
Simon Kelley59353a62004-11-21 19:34:28 +0000535 {
536 unsigned int req_time = option_uint(opt, 4);
537 if (time == 0xffffffff || (req_time != 0xffffffff && req_time < time))
538 time = req_time;
539 }
540 else if (lease && lease->expires != 0)
541 time = (unsigned int)difftime(lease->expires, now);
542
Simon Kelley0a852542005-03-23 20:28:59 +0000543 mess->siaddr = context->local;
Simon Kelley26128d22004-11-14 16:43:54 +0000544 bootp_option_put(mess, daemon->boot_config, netid);
Simon Kelley44a2a312004-03-10 20:04:35 +0000545 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPOFFER);
Simon Kelley0a852542005-03-23 20:28:59 +0000546 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelley59353a62004-11-21 19:34:28 +0000547 p = option_put(p, end, OPTION_LEASE_TIME, 4, time);
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100548 /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
Simon Kelley59353a62004-11-21 19:34:28 +0000549 if (time != 0xffffffff)
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100550 {
Simon Kelley59353a62004-11-21 19:34:28 +0000551 p = option_put(p, end, OPTION_T1, 4, (time/2));
552 p = option_put(p, end, OPTION_T2, 4, (time*7)/8);
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100553 }
Simon Kelley3be34542004-09-11 19:12:13 +0100554 p = do_req_options(context, p, end, req_options, daemon,
Simon Kelley3d8df262005-08-29 12:19:27 +0100555 offer_hostname, netid, subnet_addr, fqdn_flags);
Simon Kelleya2226412004-05-13 20:27:08 +0100556 p = option_end(p, end, mess);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000557
Simon Kelley3d8df262005-08-29 12:19:27 +0100558 log_packet("OFFER" , &mess->yiaddr, chaddr, iface_name, NULL);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000559 return p - (unsigned char *)mess;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000560
561 case DHCPREQUEST:
Simon Kelley26128d22004-11-14 16:43:54 +0000562 if (ignore || have_config(config, CONFIG_DISABLE))
563 return 0;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000564 if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000565 {
566 /* SELECTING or INIT_REBOOT */
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000567 mess->yiaddr = option_addr(opt);
Simon Kelley44a2a312004-03-10 20:04:35 +0000568
Simon Kelleybb01cb92004-12-13 20:56:23 +0000569 if ((opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000570 {
Simon Kelley3be34542004-09-11 19:12:13 +0100571 /* SELECTING */
Simon Kelley0a852542005-03-23 20:28:59 +0000572 if (context->local.s_addr != option_addr(opt).s_addr)
Simon Kelley3be34542004-09-11 19:12:13 +0100573 return 0;
574
575 /* If a lease exists for this host and another address, squash it. */
576 if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
577 {
578 lease_prune(lease, now);
579 lease = NULL;
580 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000581 }
Simon Kelley3be34542004-09-11 19:12:13 +0100582 else
583 {
584 /* INIT-REBOOT */
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100585 if (!lease && !(daemon->options & OPT_AUTHORITATIVE))
Simon Kelley3be34542004-09-11 19:12:13 +0100586 return 0;
587
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100588 if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
Simon Kelley3be34542004-09-11 19:12:13 +0100589 message = "wrong address";
590 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000591 }
592 else
593 {
594 /* RENEWING or REBINDING */
595 /* Must exist a lease for this address */
Simon Kelley44a2a312004-03-10 20:04:35 +0000596 if (!lease || mess->ciaddr.s_addr != lease->addr.s_addr)
597 message = "lease not found";
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100598
599 /* desynchronise renewals */
600 fuzz = rand16();
Simon Kelley3be34542004-09-11 19:12:13 +0100601 mess->yiaddr = mess->ciaddr;
Simon Kelley44a2a312004-03-10 20:04:35 +0000602 }
603
Simon Kelley3d8df262005-08-29 12:19:27 +0100604 log_packet("REQUEST", &mess->yiaddr, chaddr, iface_name, NULL);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100605
Simon Kelleydfa666f2004-08-02 18:27:27 +0100606 if (!message)
607 {
608 struct dhcp_config *addr_config;
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100609
Simon Kelleydfa666f2004-08-02 18:27:27 +0100610 /* If a machine moves networks whilst it has a lease, we catch that here. */
611 if (!is_same_net(mess->yiaddr, context->start, context->netmask))
612 message = "wrong network";
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100613
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100614 /* Check for renewal of a lease which is outside the allowed range. */
Simon Kelleydfa666f2004-08-02 18:27:27 +0100615 else if (!address_available(context, mess->yiaddr) &&
616 (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr))
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100617 message = "address not available";
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100618
Simon Kelleydfa666f2004-08-02 18:27:27 +0100619 /* Check if a new static address has been configured. Be very sure that
620 when the client does DISCOVER, it will get the static address, otherwise
621 an endless protocol loop will ensue. */
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100622
623 else if (have_config(config, CONFIG_ADDR) &&
624 config->addr.s_addr != mess->yiaddr.s_addr &&
625 (!(ltmp = lease_find_by_addr(config->addr)) || ltmp == lease))
Simon Kelleydfa666f2004-08-02 18:27:27 +0100626 message = "static lease available";
627
628 /* Check to see if the address is reserved as a static address for another host */
Simon Kelley3be34542004-09-11 19:12:13 +0100629 else if ((addr_config = config_find_by_address(daemon->dhcp_conf, mess->yiaddr)) && addr_config != config)
Simon Kelley0a852542005-03-23 20:28:59 +0000630 message = "address reserved";
Simon Kelleydfa666f2004-08-02 18:27:27 +0100631
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100632 else if ((ltmp = lease_find_by_addr(mess->yiaddr)) && ltmp != lease)
633 message = "address in use";
634
Simon Kelley3d8df262005-08-29 12:19:27 +0100635 else if (!lease && !(lease = lease_allocate(chaddr, clid, clid_len, mess->yiaddr)))
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100636 message = "no leases left";
637 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000638
639 if (message)
640 {
Simon Kelley3d8df262005-08-29 12:19:27 +0100641 log_packet("NAK", &mess->yiaddr, chaddr, iface_name, message);
Simon Kelley44a2a312004-03-10 20:04:35 +0000642
643 mess->siaddr.s_addr = mess->yiaddr.s_addr = mess->ciaddr.s_addr = 0;
644 bootp_option_put(mess, NULL, NULL);
645 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPNAK);
Simon Kelley3d8df262005-08-29 12:19:27 +0100646 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelley44a2a312004-03-10 20:04:35 +0000647 p = option_put_string(p, end, OPTION_MESSAGE, message);
Simon Kelley44a2a312004-03-10 20:04:35 +0000648 mess->flags |= htons(0x8000); /* broadcast */
Simon Kelley44a2a312004-03-10 20:04:35 +0000649 }
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100650 else
Simon Kelley44a2a312004-03-10 20:04:35 +0000651 {
Simon Kelley3d8df262005-08-29 12:19:27 +0100652 if (!hostname)
653 hostname = host_from_dns(daemon, mess->yiaddr);
654
655 log_packet("ACK", &mess->yiaddr, chaddr, iface_name, hostname);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100656
Simon Kelley59353a62004-11-21 19:34:28 +0000657 context = narrow_context(context, mess->yiaddr);
Simon Kelley0a852542005-03-23 20:28:59 +0000658 if (context->netid.net && !(context->flags & CONTEXT_FILTER))
Simon Kelley59353a62004-11-21 19:34:28 +0000659 {
660 context->netid.next = netid;
661 netid = &context->netid;
662 }
663
664 time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000665 if ((opt = option_find(mess, sz, OPTION_LEASE_TIME, 4)))
Simon Kelley59353a62004-11-21 19:34:28 +0000666 {
667 unsigned int req_time = option_uint(opt, 4);
668 if (time == 0xffffffff || (req_time != 0xffffffff && req_time < time))
669 time = req_time;
670 }
671
Simon Kelley3d8df262005-08-29 12:19:27 +0100672 lease_set_hwaddr(lease, chaddr, clid, clid_len);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100673 if (hostname)
674 lease_set_hostname(lease, hostname, daemon->domain_suffix);
Simon Kelley59353a62004-11-21 19:34:28 +0000675 lease_set_expires(lease, time == 0xffffffff ? 0 : now + (time_t)time);
Simon Kelley0a852542005-03-23 20:28:59 +0000676
677 mess->siaddr = context->local;
Simon Kelley26128d22004-11-14 16:43:54 +0000678 bootp_option_put(mess, daemon->boot_config, netid);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100679 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
Simon Kelley0a852542005-03-23 20:28:59 +0000680 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelley59353a62004-11-21 19:34:28 +0000681 p = option_put(p, end, OPTION_LEASE_TIME, 4, time);
682 if (time != 0xffffffff)
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100683 {
Simon Kelley59353a62004-11-21 19:34:28 +0000684 while (fuzz > (time/16))
685 fuzz = fuzz/2;
686 p = option_put(p, end, OPTION_T1, 4, (time/2) - fuzz);
687 p = option_put(p, end, OPTION_T2, 4, ((time * 7)/8) - fuzz);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100688 }
689 p = do_req_options(context, p, end, req_options, daemon,
Simon Kelley3d8df262005-08-29 12:19:27 +0100690 hostname, netid, subnet_addr, fqdn_flags);
Simon Kelley44a2a312004-03-10 20:04:35 +0000691 }
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100692
Simon Kelleya2226412004-05-13 20:27:08 +0100693 p = option_end(p, end, mess);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000694 return p - (unsigned char *)mess;
695
696 case DHCPINFORM:
Simon Kelley26128d22004-11-14 16:43:54 +0000697 if (ignore || have_config(config, CONFIG_DISABLE))
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100698 message = "ignored";
Simon Kelley33820b72004-04-03 21:10:00 +0100699
Simon Kelley3d8df262005-08-29 12:19:27 +0100700 log_packet("INFORM", &mess->ciaddr, chaddr, iface_name, message);
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100701
702 if (message || mess->ciaddr.s_addr == 0)
703 return 0;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000704
Simon Kelley59353a62004-11-21 19:34:28 +0000705 context = narrow_context(context, mess->ciaddr);
706 if (context->netid.net)
707 {
708 context->netid.next = netid;
709 netid = &context->netid;
710 }
711
Simon Kelley0a852542005-03-23 20:28:59 +0000712 mess->siaddr = context->local;
Simon Kelley26128d22004-11-14 16:43:54 +0000713 bootp_option_put(mess, daemon->boot_config, netid);
Simon Kelley44a2a312004-03-10 20:04:35 +0000714 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
Simon Kelley0a852542005-03-23 20:28:59 +0000715 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelleybb01cb92004-12-13 20:56:23 +0000716 if (!hostname)
717 hostname = host_from_dns(daemon, mess->yiaddr);
Simon Kelley3be34542004-09-11 19:12:13 +0100718 p = do_req_options(context, p, end, req_options, daemon,
Simon Kelley3d8df262005-08-29 12:19:27 +0100719 hostname, netid, subnet_addr, fqdn_flags);
Simon Kelleya2226412004-05-13 20:27:08 +0100720 p = option_end(p, end, mess);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000721
Simon Kelley3d8df262005-08-29 12:19:27 +0100722 log_packet("ACK", &mess->ciaddr, chaddr, iface_name, hostname);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000723 return p - (unsigned char *)mess;
724 }
725
726 return 0;
727}
728
Simon Kelley44a2a312004-03-10 20:04:35 +0000729static void log_packet(char *type, struct in_addr *addr, unsigned char *hwaddr, char *interface, char *string)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000730{
Simon Kelley3d8df262005-08-29 12:19:27 +0100731 u8 empty[] = { 0, 0, 0, 0, 0, 0};
732
733 if (!hwaddr)
734 hwaddr = empty;
735
Simon Kelley3be34542004-09-11 19:12:13 +0100736 syslog(LOG_INFO, "%s%s(%s)%s%s %.2x:%.2x:%.2x:%.2x:%.2x:%.2x%s%s",
737 type ? "DHCP" : "BOOTP",
738 type ? type : "",
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000739 interface,
740 addr ? " " : "",
741 addr ? inet_ntoa(*addr) : "",
Simon Kelley44a2a312004-03-10 20:04:35 +0000742 hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5],
743 string ? " " : "",
744 string ? string : "");
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000745}
746
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000747static int option_len(unsigned char *opt)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000748{
749 return opt[1];
750}
751
752static void *option_ptr(unsigned char *opt)
753{
754 return &opt[2];
755}
756
757static struct in_addr option_addr(unsigned char *opt)
758{
759 /* this worries about unaligned data in the option. */
760 /* struct in_addr is network byte order */
761 struct in_addr ret;
762
763 memcpy(&ret, option_ptr(opt), INADDRSZ);
764
765 return ret;
766}
767
Simon Kelley44a2a312004-03-10 20:04:35 +0000768static unsigned int option_uint(unsigned char *opt, int size)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000769{
770 /* this worries about unaligned data and byte order */
Simon Kelley44a2a312004-03-10 20:04:35 +0000771 unsigned int ret = 0;
772 int i;
773 unsigned char *p = option_ptr(opt);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000774
Simon Kelley44a2a312004-03-10 20:04:35 +0000775 for (i = 0; i < size; i++)
776 ret = (ret << 8) | *p++;
777
778 return ret;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000779}
780
Simon Kelley26128d22004-11-14 16:43:54 +0000781static void bootp_option_put(struct dhcp_packet *mess,
782 struct dhcp_boot *boot_opts, struct dhcp_netid *netids)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000783{
Simon Kelley26128d22004-11-14 16:43:54 +0000784 struct dhcp_boot *tmp;
785
786 for (tmp = boot_opts; tmp; tmp = tmp->next)
787 if (match_netid(tmp->netid, netids))
788 break;
789 if (!tmp)
790 /* No match, look for one without a netid */
791 for (tmp = boot_opts; tmp; tmp = tmp->next)
792 if (!tmp->netid)
793 break;
794
795 /* Do this _after_ the matching above, since in
796 BOOTP mode, one if the things we match is the filename. */
797
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000798 memset(mess->sname, 0, sizeof(mess->sname));
799 memset(mess->file, 0, sizeof(mess->file));
Simon Kelley26128d22004-11-14 16:43:54 +0000800
801 if (tmp)
802 {
803 if (tmp->sname)
Simon Kelley3d8df262005-08-29 12:19:27 +0100804 strncpy((char *)mess->sname, tmp->sname, sizeof(mess->sname)-1);
Simon Kelley26128d22004-11-14 16:43:54 +0000805 if (tmp->file)
Simon Kelley3d8df262005-08-29 12:19:27 +0100806 strncpy((char *)mess->file, tmp->file, sizeof(mess->file)-1);
Simon Kelley26128d22004-11-14 16:43:54 +0000807 if (tmp->next_server.s_addr)
808 mess->siaddr = tmp->next_server;
809 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000810}
811
812static unsigned char *option_put(unsigned char *p, unsigned char *end, int opt, int len, unsigned int val)
813{
814 int i;
Simon Kelley44a2a312004-03-10 20:04:35 +0000815
816 /* always keep one octet space for the END option. */
Simon Kelleya2226412004-05-13 20:27:08 +0100817 if (p + len + 3 < end)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000818 {
819 *(p++) = opt;
Simon Kelleya2226412004-05-13 20:27:08 +0100820 *(p++) = len;
821
822 for (i = 0; i < len; i++)
823 *(p++) = val >> (8 * (len - (i + 1)));
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000824 }
825 return p;
826}
827
Simon Kelleya2226412004-05-13 20:27:08 +0100828static unsigned char *option_end(unsigned char *p, unsigned char *end, struct dhcp_packet *start)
829{
830 *(p++) = OPTION_END;
831 while ((p < end) && (p - ((unsigned char *)start) < MIN_PACKETSZ))
832 *p++ = 0;
833
834 return p;
835}
836
Simon Kelley44a2a312004-03-10 20:04:35 +0000837static unsigned char *option_put_string(unsigned char *p, unsigned char *end, int opt, char *string)
838{
Simon Kelley0a852542005-03-23 20:28:59 +0000839 size_t len = strlen(string);
Simon Kelley3be34542004-09-11 19:12:13 +0100840
841 if (p + len + 3 < end)
Simon Kelley44a2a312004-03-10 20:04:35 +0000842 {
843 *(p++) = opt;
Simon Kelley3be34542004-09-11 19:12:13 +0100844 *(p++) = len;
845 memcpy(p, string, len);
846 p += len;
Simon Kelley44a2a312004-03-10 20:04:35 +0000847 }
Simon Kelley3be34542004-09-11 19:12:13 +0100848
Simon Kelley44a2a312004-03-10 20:04:35 +0000849 return p;
850}
851
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000852static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int *overload)
853{
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000854 if (!p)
855 return NULL;
856
857 while (*p != OPTION_END)
858 {
Simon Kelleybb01cb92004-12-13 20:56:23 +0000859 if (p >= end)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000860 return 0; /* malformed packet */
861 else if (*p == OPTION_PAD)
862 p++;
863 else if (*p == OPTION_OVERLOAD)
864 {
Simon Kelleybb01cb92004-12-13 20:56:23 +0000865 if (p >= end - 3)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000866 return 0; /* malformed packet */
867 if (overload)
868 *overload = *(p+2);
869 p += 3;
870 }
871 else
872 {
873 int opt_len;;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000874 if (p >= end - 2)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000875 return 0; /* malformed packet */
876 opt_len = option_len(p);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000877 if (p >= end - (2 + opt_len))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000878 return 0; /* malformed packet */
879 if (*p == opt)
880 return p;
881 p += opt_len + 2;
882 }
883 }
884
885 return NULL;
886}
887
Simon Kelleyf6b7dc42005-01-23 12:06:08 +0000888static unsigned char *option_find(struct dhcp_packet *mess, int size, int opt_type, int minsize)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000889{
890 int overload = 0;
891 unsigned char *ret;
892
Simon Kelley3be34542004-09-11 19:12:13 +0100893 /* skip over DHCP cookie; */
894 ret = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + size, opt_type, &overload);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000895
896 if (!ret && (overload & 1))
897 ret = option_find1(&mess->file[0], &mess->file[128], opt_type, &overload);
898
899 if (!ret && (overload & 2))
900 ret = option_find1(&mess->sname[0], &mess->file[64], opt_type, &overload);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000901
902 /* Check the option field is big enough */
903 if (ret && (option_len(ret) < minsize))
904 ret = NULL;
905
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000906 return ret;
907}
908
909static int in_list(unsigned char *list, int opt)
910{
911 int i;
912
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100913 /* If no requested options, send everything, not nothing. */
914 if (!list)
915 return 1;
916
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000917 for (i = 0; list[i] != OPTION_END; i++)
918 if (opt == list[i])
919 return 1;
920
921 return 0;
922}
923
Simon Kelleya2226412004-05-13 20:27:08 +0100924static struct dhcp_opt *option_find2(struct dhcp_netid *netid, struct dhcp_opt *opts, int opt)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000925{
Simon Kelley91dccd02005-03-31 17:48:32 +0100926 struct dhcp_opt *tmp;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100927 for (tmp = opts; tmp; tmp = tmp->next)
Simon Kelleya2226412004-05-13 20:27:08 +0100928 if (tmp->opt == opt)
929 {
930 if (netid)
931 {
Simon Kelley26128d22004-11-14 16:43:54 +0000932 if (match_netid(tmp->netid, netid))
933 return tmp;
Simon Kelleya2226412004-05-13 20:27:08 +0100934 }
935 else if (!tmp->netid)
936 return tmp;
937 }
938
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100939 return netid ? option_find2(NULL, opts, opt) : NULL;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000940}
941
Simon Kelley91dccd02005-03-31 17:48:32 +0100942static unsigned char *do_opt(struct dhcp_opt *opt, unsigned char *p, unsigned char *end, struct in_addr local)
943{
944 if (p + opt->len + 3 >= end)
945 return p;
946
947 *(p++) = opt->opt;
948 *(p++) = opt->len;
949
950 if (opt->len == 0)
951 return p;
952
953 if (opt->is_addr && !opt->vendor_class)
954 {
955 int j;
956 struct in_addr *a = (struct in_addr *)opt->val;
957 for (j = 0; j < opt->len; j+=INADDRSZ, a++)
958 {
959 /* zero means "self" (but not in vendorclass options.) */
960 if (a->s_addr == 0)
961 memcpy(p, &local, INADDRSZ);
962 else
963 memcpy(p, a, INADDRSZ);
964 p += INADDRSZ;
965 }
966 }
967 else
968 {
969 memcpy(p, opt->val, opt->len);
970 p += opt->len;
971 }
972
973 return p;
974}
975
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000976static unsigned char *do_req_options(struct dhcp_context *context,
977 unsigned char *p, unsigned char *end,
978 unsigned char *req_options,
Simon Kelley3be34542004-09-11 19:12:13 +0100979 struct daemon *daemon,
980 char *hostname,
Simon Kelley3be34542004-09-11 19:12:13 +0100981 struct dhcp_netid *netid,
Simon Kelley3d8df262005-08-29 12:19:27 +0100982 struct in_addr subnet_addr,
983 unsigned char fqdn_flags)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000984{
Simon Kelley3be34542004-09-11 19:12:13 +0100985 struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
Simon Kelley91dccd02005-03-31 17:48:32 +0100986 char *vendor_class = NULL;
Simon Kelley3be34542004-09-11 19:12:13 +0100987
Simon Kelley1ab84e22004-01-29 16:48:35 +0000988 if (in_list(req_options, OPTION_MAXMESSAGE))
Simon Kelley3be34542004-09-11 19:12:13 +0100989 p = option_put(p, end, OPTION_MAXMESSAGE, 2, end - (unsigned char *)daemon->dhcp_packet);
Simon Kelley1ab84e22004-01-29 16:48:35 +0000990
Simon Kelley3be34542004-09-11 19:12:13 +0100991 /* rfc3011 says this doesn't need to be in the requested options list. */
992 if (subnet_addr.s_addr)
993 p = option_put(p, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr));
994
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000995 if (in_list(req_options, OPTION_NETMASK) &&
Simon Kelley33820b72004-04-03 21:10:00 +0100996 !option_find2(netid, config_opts, OPTION_NETMASK))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000997 p = option_put(p, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr));
998
Simon Kelley3be34542004-09-11 19:12:13 +0100999 /* May not have a "guessed" broadcast address if we got no packets via a relay
1000 from this net yet (ie just unicast renewals after a restart */
1001 if (context->broadcast.s_addr &&
1002 in_list(req_options, OPTION_BROADCAST) &&
Simon Kelley33820b72004-04-03 21:10:00 +01001003 !option_find2(netid, config_opts, OPTION_BROADCAST))
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001004 p = option_put(p, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
1005
Simon Kelley3be34542004-09-11 19:12:13 +01001006 /* Same comments as broadcast apply, and also may not be able to get a sensible
1007 default when using subnet select. User must configure by steam in that case. */
1008 if (context->router.s_addr &&
1009 in_list(req_options, OPTION_ROUTER) &&
Simon Kelley33820b72004-04-03 21:10:00 +01001010 !option_find2(netid, config_opts, OPTION_ROUTER))
Simon Kelley3be34542004-09-11 19:12:13 +01001011 p = option_put(p, end, OPTION_ROUTER, INADDRSZ, ntohl(context->router.s_addr));
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001012
1013 if (in_list(req_options, OPTION_DNSSERVER) &&
Simon Kelley33820b72004-04-03 21:10:00 +01001014 !option_find2(netid, config_opts, OPTION_DNSSERVER))
Simon Kelley0a852542005-03-23 20:28:59 +00001015 p = option_put(p, end, OPTION_DNSSERVER, INADDRSZ, ntohl(context->local.s_addr));
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001016
Simon Kelley3be34542004-09-11 19:12:13 +01001017 if (daemon->domain_suffix && in_list(req_options, OPTION_DOMAINNAME) &&
Simon Kelley33820b72004-04-03 21:10:00 +01001018 !option_find2(netid, config_opts, OPTION_DOMAINNAME))
Simon Kelley3be34542004-09-11 19:12:13 +01001019 p = option_put_string(p, end, OPTION_DOMAINNAME, daemon->domain_suffix);
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001020
1021 /* Note that we ignore attempts to set the hostname using
Simon Kelley3d8df262005-08-29 12:19:27 +01001022 --dhcp-option=12,<name> and the fqdn using
1023 --dhc-option=81,<name> */
1024 if (hostname)
1025 {
1026 if (in_list(req_options, OPTION_HOSTNAME))
1027 p = option_put_string(p, end, OPTION_HOSTNAME, hostname);
1028
1029 if (fqdn_flags != 0)
1030 {
1031 int len = strlen(hostname) + 3;
1032 if (fqdn_flags & 0x04)
1033 len += 2;
1034
1035 if (daemon->domain_suffix)
1036 len += strlen(daemon->domain_suffix) + 1;
1037
1038 if (p + len + 1 < end)
1039 {
1040 *(p++) = OPTION_CLIENT_FQDN;
1041 *(p++) = len;
1042 *(p++) = fqdn_flags;
1043 *(p++) = 255;
1044 *(p++) = 255;
1045
1046 if (fqdn_flags & 0x04)
1047 {
1048 p = do_rfc1035_name(p, hostname);
1049 if (daemon->domain_suffix)
1050 p = do_rfc1035_name(p, daemon->domain_suffix);
1051 *p++ = 0;
1052 }
1053 else
1054 {
1055 memcpy(p, hostname, strlen(hostname));
1056 p += strlen(hostname);
1057 if (daemon->domain_suffix)
1058 {
1059 *(p++) = '.';
1060 memcpy(p, daemon->domain_suffix, strlen(daemon->domain_suffix));
1061 p += strlen(daemon->domain_suffix);
1062 }
1063 }
1064 }
1065 }
1066 }
1067
Simon Kelleya84fa1d2004-04-23 22:21:21 +01001068 for (opt=config_opts; opt; opt = opt->next)
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001069 {
Simon Kelleya84fa1d2004-04-23 22:21:21 +01001070 if (opt->opt == OPTION_HOSTNAME ||
Simon Kelley3d8df262005-08-29 12:19:27 +01001071 opt->opt == OPTION_CLIENT_FQDN ||
Simon Kelleya84fa1d2004-04-23 22:21:21 +01001072 opt->opt == OPTION_MAXMESSAGE ||
1073 !in_list(req_options, opt->opt) ||
Simon Kelley91dccd02005-03-31 17:48:32 +01001074 opt != option_find2(netid, config_opts, opt->opt))
Simon Kelley33820b72004-04-03 21:10:00 +01001075 continue;
1076
1077 /* For the options we have default values on
1078 dhc-option=<optionno> means "don't include this option"
1079 not "include a zero-length option" */
1080 if (opt->len == 0 &&
1081 (opt->opt == OPTION_NETMASK ||
1082 opt->opt == OPTION_BROADCAST ||
1083 opt->opt == OPTION_ROUTER ||
1084 opt->opt == OPTION_DNSSERVER))
1085 continue;
Simon Kelley91dccd02005-03-31 17:48:32 +01001086
1087 /* opt->val has terminating zero */
1088 if (opt->opt == OPTION_VENDOR_ID)
Simon Kelley3d8df262005-08-29 12:19:27 +01001089 vendor_class = (char *)opt->val;
Simon Kelley91dccd02005-03-31 17:48:32 +01001090 else
1091 p = do_opt(opt, p, end, context->local);
1092 }
1093
1094 if (in_list(req_options, OPTION_VENDOR_ID))
1095 {
1096 for (opt = daemon->vendor_opts; opt; opt = opt->next)
1097 if (!opt->netid || match_netid(opt->netid, netid))
1098 {
Simon Kelley3d8df262005-08-29 12:19:27 +01001099 if (vendor_class && strcmp(vendor_class, (char *)opt->vendor_class) != 0)
Simon Kelley91dccd02005-03-31 17:48:32 +01001100 syslog(LOG_WARNING, "More than one vendor class matches, using %s", vendor_class);
1101 else
Simon Kelley3d8df262005-08-29 12:19:27 +01001102 vendor_class = (char *)opt->vendor_class;
Simon Kelley91dccd02005-03-31 17:48:32 +01001103 }
1104
1105 if (vendor_class)
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001106 {
Simon Kelley91dccd02005-03-31 17:48:32 +01001107 p = option_put_string(p, end, OPTION_VENDOR_ID, vendor_class);
1108
1109 if (in_list(req_options, OPTION_VENDOR_CLASS_OPT))
Simon Kelley1ab84e22004-01-29 16:48:35 +00001110 {
Simon Kelley91dccd02005-03-31 17:48:32 +01001111 unsigned char *plen, *oend = end;
1112
1113 /* encapsulated options can only be 256 bytes,
1114 even of the packet is larger */
1115 if (p + 256 < end)
1116 oend = p + 256;
1117
1118 if (p + 3 >= oend)
1119 return p;
1120
1121 *(p++) = OPTION_VENDOR_CLASS_OPT;
1122 plen = p++; /* fill in later */
1123
1124 for (opt = daemon->vendor_opts; opt; opt = opt->next)
1125 if ((!opt->netid || match_netid(opt->netid, netid)) &&
Simon Kelley3d8df262005-08-29 12:19:27 +01001126 strcmp(vendor_class, (char *)opt->vendor_class) == 0)
Simon Kelley91dccd02005-03-31 17:48:32 +01001127 p = do_opt(opt, p, oend, context->local);
1128
1129 *plen = p - plen - 1;
Simon Kelley1ab84e22004-01-29 16:48:35 +00001130 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001131 }
Simon Kelley91dccd02005-03-31 17:48:32 +01001132 }
1133
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001134 return p;
1135}
1136
1137