blob: 0c333247748fcff8a7d3511141c2f1b4748fe17a [file] [log] [blame]
Simon Kelley9e4abcb2004-01-22 19:47:41 +00001/* dnsmasq is Copyright (c) 2000-2003 Simon Kelley
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 dated June, 1991.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11*/
12
13/* Author's email: simon@thekelleys.org.uk */
14
15#include "dnsmasq.h"
16
17#define BOOTREQUEST 1
18#define BOOTREPLY 2
19#define DHCP_COOKIE 0x63825363
20
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 Kelley9e4abcb2004-01-22 19:47:41 +000032#define OPTION_REQUESTED_IP 50
33#define OPTION_LEASE_TIME 51
34#define OPTION_OVERLOAD 52
35#define OPTION_MESSAGE_TYPE 53
36#define OPTION_SERVER_IDENTIFIER 54
37#define OPTION_REQUESTED_OPTIONS 55
Simon Kelley44a2a312004-03-10 20:04:35 +000038#define OPTION_MESSAGE 56
Simon Kelley9e4abcb2004-01-22 19:47:41 +000039#define OPTION_MAXMESSAGE 57
Simon Kelley44a2a312004-03-10 20:04:35 +000040#define OPTION_T1 58
41#define OPTION_T2 59
Simon Kelleya84fa1d2004-04-23 22:21:21 +010042#define OPTION_VENDOR_ID 60
Simon Kelley44a2a312004-03-10 20:04:35 +000043#define OPTION_CLIENT_ID 61
Simon Kelleya2226412004-05-13 20:27:08 +010044#define OPTION_USER_CLASS 77
Simon Kelley3be34542004-09-11 19:12:13 +010045#define OPTION_SUBNET_SELECT 118
Simon Kelley9e4abcb2004-01-22 19:47:41 +000046#define OPTION_END 255
47
48#define DHCPDISCOVER 1
49#define DHCPOFFER 2
50#define DHCPREQUEST 3
51#define DHCPDECLINE 4
52#define DHCPACK 5
53#define DHCPNAK 6
54#define DHCPRELEASE 7
55#define DHCPINFORM 8
56
57static unsigned char *option_put(unsigned char *p, unsigned char *end, int opt, int len, unsigned int val);
Simon Kelleya2226412004-05-13 20:27:08 +010058static unsigned char *option_end(unsigned char *p, unsigned char *end, struct dhcp_packet *start);
Simon Kelley44a2a312004-03-10 20:04:35 +000059static unsigned char *option_put_string(unsigned char *p, unsigned char *end, int opt, char *string);
Simon Kelley26128d22004-11-14 16:43:54 +000060static void bootp_option_put(struct dhcp_packet *mess,
61 struct dhcp_boot *boot_opts, struct dhcp_netid *netids);
Simon Kelleybb01cb92004-12-13 20:56:23 +000062static unsigned int option_len(unsigned char *opt);
Simon Kelley9e4abcb2004-01-22 19:47:41 +000063static void *option_ptr(unsigned char *opt);
64static struct in_addr option_addr(unsigned char *opt);
Simon Kelley44a2a312004-03-10 20:04:35 +000065static unsigned int option_uint(unsigned char *opt, int size);
66static void log_packet(char *type, struct in_addr *addr, unsigned char *hwaddr, char *interface, char *string);
Simon Kelley26128d22004-11-14 16:43:54 +000067static int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool);
Simon Kelleybb01cb92004-12-13 20:56:23 +000068static unsigned char *option_find(struct dhcp_packet *mess, int size, int opt_type, unsigned int minsize);
Simon Kelley9e4abcb2004-01-22 19:47:41 +000069static unsigned char *do_req_options(struct dhcp_context *context,
70 unsigned char *p, unsigned char *end,
71 unsigned char *req_options,
Simon Kelley3be34542004-09-11 19:12:13 +010072 struct daemon *daemon,
73 char *hostname,
Simon Kelley44a2a312004-03-10 20:04:35 +000074 struct in_addr iface_addr,
Simon Kelley3be34542004-09-11 19:12:13 +010075 struct dhcp_netid *netid,
76 struct in_addr subnet_addr);
Simon Kelley9e4abcb2004-01-22 19:47:41 +000077
Simon Kelley33820b72004-04-03 21:10:00 +010078static int have_config(struct dhcp_config *config, unsigned int mask)
79{
80 return config && (config->flags & mask);
81}
Simon Kelley9e4abcb2004-01-22 19:47:41 +000082
Simon Kelley3be34542004-09-11 19:12:13 +010083int dhcp_reply(struct daemon *daemon, struct in_addr iface_addr, char *iface_name, unsigned int sz, time_t now)
Simon Kelley9e4abcb2004-01-22 19:47:41 +000084{
Simon Kelley36717ee2004-09-20 19:20:58 +010085 struct dhcp_context *context, *context_tmp;
Simon Kelley26128d22004-11-14 16:43:54 +000086 unsigned char *opt, *clid = NULL;
Simon Kelley9c74ec02004-08-13 21:13:03 +010087 struct dhcp_lease *lease, *ltmp;
Simon Kelleya2226412004-05-13 20:27:08 +010088 struct dhcp_vendor *vendor;
Simon Kelley26128d22004-11-14 16:43:54 +000089 struct dhcp_netid_list *id_list;
90 int clid_len = 0, ignore = 0;
Simon Kelley3be34542004-09-11 19:12:13 +010091 struct dhcp_packet *mess = &daemon->dhcp_packet->data;
92 unsigned char *p = mess->options + sizeof(u32); /* skip cookie */
93 unsigned char *end = (unsigned char *)(daemon->dhcp_packet + 1);
Simon Kelley9e4abcb2004-01-22 19:47:41 +000094 char *hostname = NULL;
95 char *req_options = NULL;
Simon Kelley44a2a312004-03-10 20:04:35 +000096 char *message = NULL;
Simon Kelley59353a62004-11-21 19:34:28 +000097 unsigned int time;
Simon Kelley9e4abcb2004-01-22 19:47:41 +000098 struct dhcp_config *config;
Simon Kelleya2226412004-05-13 20:27:08 +010099 struct dhcp_netid *netid = NULL;
Simon Kelley3be34542004-09-11 19:12:13 +0100100 struct in_addr addr, subnet_addr;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100101 unsigned short fuzz = 0;
Simon Kelley3be34542004-09-11 19:12:13 +0100102 unsigned int mess_type = 0;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100103
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 Kelley33820b72004-04-03 21:10:00 +0100109 /* Token ring is supported when we have packet sockets
110 to make the HW headers for us. We don't have the code to build
111 token ring headers when using BPF. We rely on the fact that
112 token ring hwaddrs are the same size as ethernet hwaddrs. */
Simon Kelley3be34542004-09-11 19:12:13 +0100113
Simon Kelley33820b72004-04-03 21:10:00 +0100114#ifdef HAVE_BPF
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100115 if (mess->htype != ARPHRD_ETHER)
Simon Kelley33820b72004-04-03 21:10:00 +0100116#else
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100117 if (mess->htype != ARPHRD_ETHER && mess->htype != ARPHRD_IEEE802)
Simon Kelley33820b72004-04-03 21:10:00 +0100118#endif
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100119 {
120 syslog(LOG_WARNING, "DHCP request for unsupported hardware type (%d) recieved on %s",
121 mess->htype, iface_name);
122 return 0;
123 }
Simon Kelley3be34542004-09-11 19:12:13 +0100124
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100125 if (mess->hlen != ETHER_ADDR_LEN)
126 return 0;
Simon Kelley3be34542004-09-11 19:12:13 +0100127
128 /* check for DHCP rather than BOOTP */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000129 if ((opt = option_find(mess, sz, OPTION_MESSAGE_TYPE, 1)))
Simon Kelley44a2a312004-03-10 20:04:35 +0000130 {
Simon Kelley3be34542004-09-11 19:12:13 +0100131 mess_type = option_uint(opt, 1);
Simon Kelley44a2a312004-03-10 20:04:35 +0000132
Simon Kelley3be34542004-09-11 19:12:13 +0100133 /* only insist on a cookie for DHCP. */
134 if (*((u32 *)&mess->options) != htonl(DHCP_COOKIE))
135 return 0;
136
137 /* Some buggy clients set ciaddr when they shouldn't, so clear that here since
138 it can affect the context-determination code. */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000139 if ((option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ) || mess_type == DHCPDISCOVER))
Simon Kelley3be34542004-09-11 19:12:13 +0100140 mess->ciaddr.s_addr = 0;
141
142 /* Check for RFC3011 subnet selector */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000143 if ((opt = option_find(mess, sz, OPTION_SUBNET_SELECT, INADDRSZ)))
Simon Kelley3be34542004-09-11 19:12:13 +0100144 subnet_addr = option_addr(opt);
Simon Kelley26128d22004-11-14 16:43:54 +0000145
146 /* If there is no client identifier option, use the hardware address */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000147 if ((opt = option_find(mess, sz, OPTION_CLIENT_ID, 1)))
Simon Kelley26128d22004-11-14 16:43:54 +0000148 {
Simon Kelley26128d22004-11-14 16:43:54 +0000149 clid_len = option_len(opt);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000150 clid = option_ptr(opt);
Simon Kelley26128d22004-11-14 16:43:54 +0000151 }
152 else
153 clid = mess->chaddr;
Simon Kelley44a2a312004-03-10 20:04:35 +0000154 }
Simon Kelley3be34542004-09-11 19:12:13 +0100155
156 /* Determine network for this packet. If the machine has an address already, and we don't have
157 have a giaddr or explicit subnet selector, use the ciaddr. This is necessary because a
158 machine which got a lease via a relay won't use the relay to renew. */
159 addr =
160 subnet_addr.s_addr ? subnet_addr :
161 (mess->giaddr.s_addr ? mess->giaddr :
162 (mess->ciaddr.s_addr ? mess->ciaddr : iface_addr));
Simon Kelley36717ee2004-09-20 19:20:58 +0100163
164 /* More than one context may match, we build a chain of them all on ->current
165 Note that if netmasks, netid or lease times don't match, odd things may happen. */
166
167 for (context = NULL, context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next)
168 if (context_tmp->netmask.s_addr &&
169 is_same_net(addr, context_tmp->start, context_tmp->netmask) &&
170 is_same_net(addr, context_tmp->end, context_tmp->netmask))
171 {
172 context_tmp->current = context;
173 context = context_tmp;
Simon Kelley36717ee2004-09-20 19:20:58 +0100174 }
Simon Kelley3be34542004-09-11 19:12:13 +0100175
176 if (!context)
177 {
178 syslog(LOG_WARNING, "no address range available for DHCP request %s %s",
179 subnet_addr.s_addr ? "with subnet selector" : "via",
180 subnet_addr.s_addr ? inet_ntoa(subnet_addr) : (mess->giaddr.s_addr ? inet_ntoa(mess->giaddr) : iface_name));
181 return 0;
182 }
183
184 mess->op = BOOTREPLY;
Simon Kelley36717ee2004-09-20 19:20:58 +0100185
Simon Kelley26128d22004-11-14 16:43:54 +0000186 config = find_config(daemon->dhcp_conf, context, clid, clid_len, mess->chaddr, NULL);
187
Simon Kelley3be34542004-09-11 19:12:13 +0100188 if (mess_type == 0)
189 {
190 /* BOOTP request */
Simon Kelley26128d22004-11-14 16:43:54 +0000191 struct dhcp_netid id;
192 char save = mess->file[128];
193 struct in_addr *logaddr = NULL;
194
195 if (have_config(config, CONFIG_ADDR))
Simon Kelley3be34542004-09-11 19:12:13 +0100196 {
Simon Kelley26128d22004-11-14 16:43:54 +0000197 logaddr = &config->addr;
Simon Kelley3be34542004-09-11 19:12:13 +0100198 mess->yiaddr = config->addr;
Simon Kelley26128d22004-11-14 16:43:54 +0000199 if (lease_find_by_addr(config->addr))
200 message = "address in use";
Simon Kelley59353a62004-11-21 19:34:28 +0000201 context = narrow_context(context, config->addr);
Simon Kelley3be34542004-09-11 19:12:13 +0100202 }
Simon Kelley26128d22004-11-14 16:43:54 +0000203 else
204 message = "no address configured";
205
206 if (have_config(config, CONFIG_DISABLE))
207 message = "disabled";
208
209 end = mess->options + 64; /* BOOTP vend area is only 64 bytes */
210
211 if (have_config(config, CONFIG_NAME))
212 hostname = config->hostname;
213
Simon Kelley59353a62004-11-21 19:34:28 +0000214 if (context->netid.net)
215 {
216 context->netid.next = netid;
217 netid = &context->netid;
218 }
219
Simon Kelley26128d22004-11-14 16:43:54 +0000220 if (have_config(config, CONFIG_NETID))
221 {
222 config->netid.next = netid;
223 netid = &config->netid;
224 }
225
226 /* Match incoming filename field as a netid. */
227 if (mess->file[0])
228 {
229 mess->file[128] = 0; /* ensure zero term. */
230 id.net = mess->file;
231 id.next = netid;
232 netid = &id;
233 }
234
235 for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
236 if (match_netid(id_list->list, netid))
237 message = "disabled";
238
239 p = do_req_options(context, p, end, NULL, daemon,
240 hostname, iface_addr, netid, subnet_addr);
241 /* must do this after do_req_options since it overwrites filename field. */
242 mess->siaddr = iface_addr;
243 bootp_option_put(mess, daemon->boot_config, netid);
244 p = option_end(p, end, mess);
245 log_packet(NULL, logaddr, mess->chaddr, iface_name, message);
246 mess->file[128] = save;
247
248 if (message)
249 return 0;
250 else
251 return p - (unsigned char *)mess;
Simon Kelley3be34542004-09-11 19:12:13 +0100252 }
253
Simon Kelleydfa666f2004-08-02 18:27:27 +0100254 if (have_config(config, CONFIG_NAME))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000255 hostname = config->hostname;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000256 else if ((opt = option_find(mess, sz, OPTION_HOSTNAME, 1)))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000257 {
Simon Kelleybb01cb92004-12-13 20:56:23 +0000258 unsigned int len = option_len(opt);
Simon Kelley3be34542004-09-11 19:12:13 +0100259 hostname = daemon->dhcp_buff;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000260 memcpy(hostname, option_ptr(opt), len);
261 /* May not be zero terminated */
262 hostname[len] = 0;
263 /* ensure there are no strange chars in there */
264 if (!canonicalise(hostname))
265 hostname = NULL;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000266 else if ((hostname = strip_hostname(daemon, hostname)) && !config)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000267 {
Simon Kelleydfa666f2004-08-02 18:27:27 +0100268 /* Search again now we have a hostname.
269 Only accept configs without CLID and HWADDR here, (they won't match)
270 to avoid impersonation by name. */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000271 struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, mess->chaddr, hostname);
272 if (!have_config(new, CONFIG_CLID) && !have_config(new, CONFIG_HWADDR))
273 config = new;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000274 }
275 }
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100276
Simon Kelleya2226412004-05-13 20:27:08 +0100277 if (have_config(config, CONFIG_NETID))
278 {
279 config->netid.next = netid;
280 netid = &config->netid;
281 }
Simon Kelley36717ee2004-09-20 19:20:58 +0100282
Simon Kelley26128d22004-11-14 16:43:54 +0000283 /* user-class options are, according to RFC3004, supposed to contain
284 a set of counted strings. Here we check that this is so (by seeing
285 if the counts are consistent with the overall option length) and if
286 so zero the counts so that we don't get spurious matches between
287 the vendor string and the counts. If the lengths don't add up, we
288 assume that the option is a single string and non RFC3004 compliant
289 and just do the substring match. dhclient provides these broken options. */
Simon Kelleya2226412004-05-13 20:27:08 +0100290
Simon Kelleybb01cb92004-12-13 20:56:23 +0000291 if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
Simon Kelleya2226412004-05-13 20:27:08 +0100292 {
Simon Kelley26128d22004-11-14 16:43:54 +0000293 unsigned char *ucp = option_ptr(opt);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000294 unsigned int tmp, j;
Simon Kelley26128d22004-11-14 16:43:54 +0000295 for (j = 0; j < option_len(opt); j += ucp[j] + 1);
296 if (j == option_len(opt))
297 for (j = 0; j < option_len(opt); j = tmp)
298 {
299 tmp = j + ucp[j] + 1;
300 ucp[j] = 0;
301 }
Simon Kelleya2226412004-05-13 20:27:08 +0100302 }
Simon Kelley26128d22004-11-14 16:43:54 +0000303
304 for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
Simon Kelleybb01cb92004-12-13 20:56:23 +0000305 if ((opt = option_find(mess, sz, vendor->is_vendor ? OPTION_VENDOR_ID : OPTION_USER_CLASS, 1)))
Simon Kelley26128d22004-11-14 16:43:54 +0000306 {
Simon Kelleybb01cb92004-12-13 20:56:23 +0000307 unsigned int i;
Simon Kelley26128d22004-11-14 16:43:54 +0000308 for (i = 0; i <= (option_len(opt) - vendor->len); i++)
309 if (memcmp(vendor->data, option_ptr(opt)+i, vendor->len) == 0)
310 {
311 vendor->netid.next = netid;
312 netid = &vendor->netid;
313 break;
314 }
315 }
316
317 /* if all the netids in the ignore list are present, ignore this client */
318 for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
319 if (match_netid(id_list->list, netid))
320 ignore = 1;
321
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100322 /* Can have setting to ignore the client ID for a particular MAC address or hostname */
323 if (have_config(config, CONFIG_NOCLID))
324 {
325 clid = mess->chaddr;
326 clid_len = 0;
327 }
328
329 /* do we have a lease in store? */
330 lease = lease_find_by_client(clid, clid_len);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000331
Simon Kelleybb01cb92004-12-13 20:56:23 +0000332 if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 0)))
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100333 {
Simon Kelley3be34542004-09-11 19:12:13 +0100334 req_options = daemon->dhcp_buff2;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000335 memcpy(req_options, option_ptr(opt), option_len(opt));
336 req_options[option_len(opt)] = OPTION_END;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100337 }
338
Simon Kelley3be34542004-09-11 19:12:13 +0100339 switch (mess_type)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000340 {
Simon Kelley44a2a312004-03-10 20:04:35 +0000341 case DHCPDECLINE:
Simon Kelleybb01cb92004-12-13 20:56:23 +0000342 if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
Simon Kelley44a2a312004-03-10 20:04:35 +0000343 (iface_addr.s_addr != option_addr(opt).s_addr))
344 return 0;
345
346 /* sanitise any message. Paranoid? Moi? */
Simon Kelleybb01cb92004-12-13 20:56:23 +0000347 if ((opt = option_find(mess, sz, OPTION_MESSAGE, 1)))
Simon Kelley44a2a312004-03-10 20:04:35 +0000348 {
Simon Kelley3be34542004-09-11 19:12:13 +0100349 char *p = option_ptr(opt), *q = daemon->dhcp_buff;
Simon Kelley44a2a312004-03-10 20:04:35 +0000350 int i;
351
352 for (i = option_len(opt); i > 0; i--)
353 {
354 char c = *p++;
355 if (isprint(c))
356 *q++ = c;
357 }
358 *q++ = 0; /* add terminator */
Simon Kelley3be34542004-09-11 19:12:13 +0100359 message = daemon->dhcp_buff;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000360 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000361
Simon Kelleybb01cb92004-12-13 20:56:23 +0000362 if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
Simon Kelley44a2a312004-03-10 20:04:35 +0000363 return 0;
364
365 log_packet("DECLINE", option_ptr(opt), mess->chaddr, iface_name, message);
366
367 if (lease && lease->addr.s_addr == option_addr(opt).s_addr)
368 lease_prune(lease, now);
369
Simon Kelley33820b72004-04-03 21:10:00 +0100370 if (have_config(config, CONFIG_ADDR) &&
Simon Kelley44a2a312004-03-10 20:04:35 +0000371 config->addr.s_addr == option_addr(opt).s_addr)
372 {
373 syslog(LOG_WARNING, "disabling DHCP static address %s", inet_ntoa(config->addr));
Simon Kelley33820b72004-04-03 21:10:00 +0100374 config->flags &= ~CONFIG_ADDR ;
Simon Kelley44a2a312004-03-10 20:04:35 +0000375 }
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100376 else
377 /* make sure this host gets a different address next time. */
Simon Kelley36717ee2004-09-20 19:20:58 +0100378 for (; context; context = context->current)
379 context->addr_epoch++;
Simon Kelley44a2a312004-03-10 20:04:35 +0000380
381 return 0;
382
383 case DHCPRELEASE:
Simon Kelleybb01cb92004-12-13 20:56:23 +0000384 if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
Simon Kelley44a2a312004-03-10 20:04:35 +0000385 (iface_addr.s_addr != option_addr(opt).s_addr))
386 return 0;
387
Simon Kelley44a2a312004-03-10 20:04:35 +0000388 if (lease && lease->addr.s_addr == mess->ciaddr.s_addr)
389 lease_prune(lease, now);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100390 else
391 message = "unknown lease";
392
393 log_packet("RELEASE", &mess->ciaddr, mess->chaddr, iface_name, message);
Simon Kelley44a2a312004-03-10 20:04:35 +0000394
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000395 return 0;
396
397 case DHCPDISCOVER:
Simon Kelleybb01cb92004-12-13 20:56:23 +0000398 if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100399 addr = option_addr(opt);
Simon Kelley26128d22004-11-14 16:43:54 +0000400 if (ignore || have_config(config, CONFIG_DISABLE))
Simon Kelley33820b72004-04-03 21:10:00 +0100401 message = "ignored";
Simon Kelley9c74ec02004-08-13 21:13:03 +0100402 else if (have_config(config, CONFIG_ADDR) &&
Simon Kelley3be34542004-09-11 19:12:13 +0100403 (!(ltmp = lease_find_by_addr(config->addr)) || ltmp == lease))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000404 mess->yiaddr = config->addr;
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100405 else if (lease && address_available(context, lease->addr))
Simon Kelley1cff1662004-03-12 08:12:58 +0000406 mess->yiaddr = lease->addr;
Simon Kelleydfa666f2004-08-02 18:27:27 +0100407 else if (opt && address_available(context, addr) && !lease_find_by_addr(addr) &&
Simon Kelley3be34542004-09-11 19:12:13 +0100408 !config_find_by_address(daemon->dhcp_conf, addr))
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100409 mess->yiaddr = addr;
Simon Kelley3be34542004-09-11 19:12:13 +0100410 else if (!address_allocate(context, daemon, &mess->yiaddr, mess->chaddr))
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100411 message = "no address available";
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100412 log_packet("DISCOVER", opt ? &addr : NULL, mess->chaddr, iface_name, message);
413
Simon Kelley33820b72004-04-03 21:10:00 +0100414 if (message)
415 return 0;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100416
Simon Kelley59353a62004-11-21 19:34:28 +0000417 context = narrow_context(context, mess->yiaddr);
418 if (context->netid.net)
419 {
420 context->netid.next = netid;
421 netid = &context->netid;
422 }
423
424 time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000425 if ((opt = option_find(mess, sz, OPTION_LEASE_TIME, 4)))
Simon Kelley59353a62004-11-21 19:34:28 +0000426 {
427 unsigned int req_time = option_uint(opt, 4);
428 if (time == 0xffffffff || (req_time != 0xffffffff && req_time < time))
429 time = req_time;
430 }
431 else if (lease && lease->expires != 0)
432 time = (unsigned int)difftime(lease->expires, now);
433
Simon Kelley26128d22004-11-14 16:43:54 +0000434 mess->siaddr = iface_addr;
435 bootp_option_put(mess, daemon->boot_config, netid);
Simon Kelley44a2a312004-03-10 20:04:35 +0000436 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPOFFER);
437 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
Simon Kelley59353a62004-11-21 19:34:28 +0000438 p = option_put(p, end, OPTION_LEASE_TIME, 4, time);
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100439 /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
Simon Kelley59353a62004-11-21 19:34:28 +0000440 if (time != 0xffffffff)
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100441 {
Simon Kelley59353a62004-11-21 19:34:28 +0000442 p = option_put(p, end, OPTION_T1, 4, (time/2));
443 p = option_put(p, end, OPTION_T2, 4, (time*7)/8);
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100444 }
Simon Kelley3be34542004-09-11 19:12:13 +0100445 p = do_req_options(context, p, end, req_options, daemon,
446 NULL, iface_addr, netid, subnet_addr);
Simon Kelleya2226412004-05-13 20:27:08 +0100447 p = option_end(p, end, mess);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000448
Simon Kelley44a2a312004-03-10 20:04:35 +0000449 log_packet("OFFER" , &mess->yiaddr, mess->chaddr, iface_name, NULL);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000450 return p - (unsigned char *)mess;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000451
452 case DHCPREQUEST:
Simon Kelley26128d22004-11-14 16:43:54 +0000453 if (ignore || have_config(config, CONFIG_DISABLE))
454 return 0;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000455 if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000456 {
457 /* SELECTING or INIT_REBOOT */
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000458 mess->yiaddr = option_addr(opt);
Simon Kelley44a2a312004-03-10 20:04:35 +0000459
Simon Kelleybb01cb92004-12-13 20:56:23 +0000460 if ((opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000461 {
Simon Kelley3be34542004-09-11 19:12:13 +0100462 /* SELECTING */
463 if (iface_addr.s_addr != option_addr(opt).s_addr)
464 return 0;
465
466 /* If a lease exists for this host and another address, squash it. */
467 if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
468 {
469 lease_prune(lease, now);
470 lease = NULL;
471 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000472 }
Simon Kelley3be34542004-09-11 19:12:13 +0100473 else
474 {
475 /* INIT-REBOOT */
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100476 if (!lease && !(daemon->options & OPT_AUTHORITATIVE))
Simon Kelley3be34542004-09-11 19:12:13 +0100477 return 0;
478
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100479 if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
Simon Kelley3be34542004-09-11 19:12:13 +0100480 message = "wrong address";
481 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000482 }
483 else
484 {
485 /* RENEWING or REBINDING */
486 /* Must exist a lease for this address */
Simon Kelley44a2a312004-03-10 20:04:35 +0000487 if (!lease || mess->ciaddr.s_addr != lease->addr.s_addr)
488 message = "lease not found";
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100489
490 /* desynchronise renewals */
491 fuzz = rand16();
Simon Kelley3be34542004-09-11 19:12:13 +0100492 mess->yiaddr = mess->ciaddr;
Simon Kelley44a2a312004-03-10 20:04:35 +0000493 }
494
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100495 log_packet("REQUEST", &mess->yiaddr, mess->chaddr, iface_name, NULL);
496
Simon Kelleydfa666f2004-08-02 18:27:27 +0100497 if (!message)
498 {
499 struct dhcp_config *addr_config;
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100500
Simon Kelleydfa666f2004-08-02 18:27:27 +0100501 /* If a machine moves networks whilst it has a lease, we catch that here. */
502 if (!is_same_net(mess->yiaddr, context->start, context->netmask))
503 message = "wrong network";
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100504
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100505 /* Check for renewal of a lease which is outside the allowed range. */
Simon Kelleydfa666f2004-08-02 18:27:27 +0100506 else if (!address_available(context, mess->yiaddr) &&
507 (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr))
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100508 message = "address not available";
Simon Kelleyfeba5c12004-07-27 20:28:58 +0100509
Simon Kelleydfa666f2004-08-02 18:27:27 +0100510 /* Check if a new static address has been configured. Be very sure that
511 when the client does DISCOVER, it will get the static address, otherwise
512 an endless protocol loop will ensue. */
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100513
514 else if (have_config(config, CONFIG_ADDR) &&
515 config->addr.s_addr != mess->yiaddr.s_addr &&
516 (!(ltmp = lease_find_by_addr(config->addr)) || ltmp == lease))
Simon Kelleydfa666f2004-08-02 18:27:27 +0100517 message = "static lease available";
518
519 /* Check to see if the address is reserved as a static address for another host */
Simon Kelley3be34542004-09-11 19:12:13 +0100520 else if ((addr_config = config_find_by_address(daemon->dhcp_conf, mess->yiaddr)) && addr_config != config)
Simon Kelleydfa666f2004-08-02 18:27:27 +0100521 message ="address reserved";
Simon Kelleydfa666f2004-08-02 18:27:27 +0100522
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100523 else if ((ltmp = lease_find_by_addr(mess->yiaddr)) && ltmp != lease)
524 message = "address in use";
525
526 else if (!lease && !(lease = lease_allocate(clid, clid_len, mess->yiaddr)))
527 message = "no leases left";
528 }
Simon Kelley44a2a312004-03-10 20:04:35 +0000529
530 if (message)
531 {
532 log_packet("NAK", &mess->yiaddr, mess->chaddr, iface_name, message);
533
534 mess->siaddr.s_addr = mess->yiaddr.s_addr = mess->ciaddr.s_addr = 0;
535 bootp_option_put(mess, NULL, NULL);
536 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPNAK);
537 p = option_put_string(p, end, OPTION_MESSAGE, message);
Simon Kelley44a2a312004-03-10 20:04:35 +0000538 mess->flags |= htons(0x8000); /* broadcast */
Simon Kelley44a2a312004-03-10 20:04:35 +0000539 }
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100540 else
Simon Kelley44a2a312004-03-10 20:04:35 +0000541 {
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100542 log_packet("ACK", &mess->yiaddr, mess->chaddr, iface_name, hostname);
543
Simon Kelley59353a62004-11-21 19:34:28 +0000544 context = narrow_context(context, mess->yiaddr);
545 if (context->netid.net)
546 {
547 context->netid.next = netid;
548 netid = &context->netid;
549 }
550
551 time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000552 if ((opt = option_find(mess, sz, OPTION_LEASE_TIME, 4)))
Simon Kelley59353a62004-11-21 19:34:28 +0000553 {
554 unsigned int req_time = option_uint(opt, 4);
555 if (time == 0xffffffff || (req_time != 0xffffffff && req_time < time))
556 time = req_time;
557 }
558
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100559 lease_set_hwaddr(lease, mess->chaddr);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000560 if (!hostname)
561 hostname = host_from_dns(daemon, mess->yiaddr);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100562 if (hostname)
563 lease_set_hostname(lease, hostname, daemon->domain_suffix);
Simon Kelley59353a62004-11-21 19:34:28 +0000564 lease_set_expires(lease, time == 0xffffffff ? 0 : now + (time_t)time);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100565
Simon Kelley26128d22004-11-14 16:43:54 +0000566 mess->siaddr = iface_addr;
567 bootp_option_put(mess, daemon->boot_config, netid);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100568 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
569 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
Simon Kelley59353a62004-11-21 19:34:28 +0000570 p = option_put(p, end, OPTION_LEASE_TIME, 4, time);
571 if (time != 0xffffffff)
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100572 {
Simon Kelley59353a62004-11-21 19:34:28 +0000573 while (fuzz > (time/16))
574 fuzz = fuzz/2;
575 p = option_put(p, end, OPTION_T1, 4, (time/2) - fuzz);
576 p = option_put(p, end, OPTION_T2, 4, ((time * 7)/8) - fuzz);
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100577 }
578 p = do_req_options(context, p, end, req_options, daemon,
579 hostname, iface_addr, netid, subnet_addr);
Simon Kelley44a2a312004-03-10 20:04:35 +0000580 }
Simon Kelleyfd9fa482004-10-21 20:24:00 +0100581
Simon Kelleya2226412004-05-13 20:27:08 +0100582 p = option_end(p, end, mess);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000583 return p - (unsigned char *)mess;
584
585 case DHCPINFORM:
Simon Kelley26128d22004-11-14 16:43:54 +0000586 if (ignore || have_config(config, CONFIG_DISABLE))
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100587 message = "ignored";
Simon Kelley33820b72004-04-03 21:10:00 +0100588
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100589 log_packet("INFORM", &mess->ciaddr, mess->chaddr, iface_name, message);
590
591 if (message || mess->ciaddr.s_addr == 0)
592 return 0;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000593
Simon Kelley59353a62004-11-21 19:34:28 +0000594 context = narrow_context(context, mess->ciaddr);
595 if (context->netid.net)
596 {
597 context->netid.next = netid;
598 netid = &context->netid;
599 }
600
Simon Kelley26128d22004-11-14 16:43:54 +0000601 mess->siaddr = iface_addr;
602 bootp_option_put(mess, daemon->boot_config, netid);
Simon Kelley44a2a312004-03-10 20:04:35 +0000603 p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
604 p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
Simon Kelleybb01cb92004-12-13 20:56:23 +0000605 if (!hostname)
606 hostname = host_from_dns(daemon, mess->yiaddr);
Simon Kelley3be34542004-09-11 19:12:13 +0100607 p = do_req_options(context, p, end, req_options, daemon,
608 hostname, iface_addr, netid, subnet_addr);
Simon Kelleya2226412004-05-13 20:27:08 +0100609 p = option_end(p, end, mess);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000610
Simon Kelley44a2a312004-03-10 20:04:35 +0000611 log_packet("ACK", &mess->ciaddr, mess->chaddr, iface_name, hostname);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000612 return p - (unsigned char *)mess;
613 }
614
615 return 0;
616}
617
Simon Kelley44a2a312004-03-10 20:04:35 +0000618static void log_packet(char *type, struct in_addr *addr, unsigned char *hwaddr, char *interface, char *string)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000619{
Simon Kelley3be34542004-09-11 19:12:13 +0100620 syslog(LOG_INFO, "%s%s(%s)%s%s %.2x:%.2x:%.2x:%.2x:%.2x:%.2x%s%s",
621 type ? "DHCP" : "BOOTP",
622 type ? type : "",
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000623 interface,
624 addr ? " " : "",
625 addr ? inet_ntoa(*addr) : "",
Simon Kelley44a2a312004-03-10 20:04:35 +0000626 hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5],
627 string ? " " : "",
628 string ? string : "");
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000629}
630
Simon Kelleybb01cb92004-12-13 20:56:23 +0000631static unsigned int option_len(unsigned char *opt)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000632{
633 return opt[1];
634}
635
636static void *option_ptr(unsigned char *opt)
637{
638 return &opt[2];
639}
640
641static struct in_addr option_addr(unsigned char *opt)
642{
643 /* this worries about unaligned data in the option. */
644 /* struct in_addr is network byte order */
645 struct in_addr ret;
646
647 memcpy(&ret, option_ptr(opt), INADDRSZ);
648
649 return ret;
650}
651
Simon Kelley44a2a312004-03-10 20:04:35 +0000652static unsigned int option_uint(unsigned char *opt, int size)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000653{
654 /* this worries about unaligned data and byte order */
Simon Kelley44a2a312004-03-10 20:04:35 +0000655 unsigned int ret = 0;
656 int i;
657 unsigned char *p = option_ptr(opt);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000658
Simon Kelley44a2a312004-03-10 20:04:35 +0000659 for (i = 0; i < size; i++)
660 ret = (ret << 8) | *p++;
661
662 return ret;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000663}
664
Simon Kelley26128d22004-11-14 16:43:54 +0000665static void bootp_option_put(struct dhcp_packet *mess,
666 struct dhcp_boot *boot_opts, struct dhcp_netid *netids)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000667{
Simon Kelley26128d22004-11-14 16:43:54 +0000668 struct dhcp_boot *tmp;
669
670 for (tmp = boot_opts; tmp; tmp = tmp->next)
671 if (match_netid(tmp->netid, netids))
672 break;
673 if (!tmp)
674 /* No match, look for one without a netid */
675 for (tmp = boot_opts; tmp; tmp = tmp->next)
676 if (!tmp->netid)
677 break;
678
679 /* Do this _after_ the matching above, since in
680 BOOTP mode, one if the things we match is the filename. */
681
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000682 memset(mess->sname, 0, sizeof(mess->sname));
683 memset(mess->file, 0, sizeof(mess->file));
Simon Kelley26128d22004-11-14 16:43:54 +0000684
685 if (tmp)
686 {
687 if (tmp->sname)
688 strncpy(mess->sname, tmp->sname, sizeof(mess->sname)-1);
689 if (tmp->file)
690 strncpy(mess->file, tmp->file, sizeof(mess->file)-1);
691 if (tmp->next_server.s_addr)
692 mess->siaddr = tmp->next_server;
693 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000694}
695
696static unsigned char *option_put(unsigned char *p, unsigned char *end, int opt, int len, unsigned int val)
697{
698 int i;
Simon Kelley44a2a312004-03-10 20:04:35 +0000699
700 /* always keep one octet space for the END option. */
Simon Kelleya2226412004-05-13 20:27:08 +0100701 if (p + len + 3 < end)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000702 {
703 *(p++) = opt;
Simon Kelleya2226412004-05-13 20:27:08 +0100704 *(p++) = len;
705
706 for (i = 0; i < len; i++)
707 *(p++) = val >> (8 * (len - (i + 1)));
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000708 }
709 return p;
710}
711
Simon Kelleya2226412004-05-13 20:27:08 +0100712static unsigned char *option_end(unsigned char *p, unsigned char *end, struct dhcp_packet *start)
713{
714 *(p++) = OPTION_END;
715 while ((p < end) && (p - ((unsigned char *)start) < MIN_PACKETSZ))
716 *p++ = 0;
717
718 return p;
719}
720
Simon Kelley44a2a312004-03-10 20:04:35 +0000721static unsigned char *option_put_string(unsigned char *p, unsigned char *end, int opt, char *string)
722{
Simon Kelley3be34542004-09-11 19:12:13 +0100723 int len = strlen(string);
724
725 if (p + len + 3 < end)
Simon Kelley44a2a312004-03-10 20:04:35 +0000726 {
727 *(p++) = opt;
Simon Kelley3be34542004-09-11 19:12:13 +0100728 *(p++) = len;
729 memcpy(p, string, len);
730 p += len;
Simon Kelley44a2a312004-03-10 20:04:35 +0000731 }
Simon Kelley3be34542004-09-11 19:12:13 +0100732
Simon Kelley44a2a312004-03-10 20:04:35 +0000733 return p;
734}
735
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000736static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int *overload)
737{
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000738 if (!p)
739 return NULL;
740
741 while (*p != OPTION_END)
742 {
Simon Kelleybb01cb92004-12-13 20:56:23 +0000743 if (p >= end)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000744 return 0; /* malformed packet */
745 else if (*p == OPTION_PAD)
746 p++;
747 else if (*p == OPTION_OVERLOAD)
748 {
Simon Kelleybb01cb92004-12-13 20:56:23 +0000749 if (p >= end - 3)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000750 return 0; /* malformed packet */
751 if (overload)
752 *overload = *(p+2);
753 p += 3;
754 }
755 else
756 {
757 int opt_len;;
Simon Kelleybb01cb92004-12-13 20:56:23 +0000758 if (p >= end - 2)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000759 return 0; /* malformed packet */
760 opt_len = option_len(p);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000761 if (p >= end - (2 + opt_len))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000762 return 0; /* malformed packet */
763 if (*p == opt)
764 return p;
765 p += opt_len + 2;
766 }
767 }
768
769 return NULL;
770}
771
Simon Kelleybb01cb92004-12-13 20:56:23 +0000772static unsigned char *option_find(struct dhcp_packet *mess, int size, int opt_type, unsigned int minsize)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000773{
774 int overload = 0;
775 unsigned char *ret;
776
Simon Kelley3be34542004-09-11 19:12:13 +0100777 /* skip over DHCP cookie; */
778 ret = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + size, opt_type, &overload);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000779
780 if (!ret && (overload & 1))
781 ret = option_find1(&mess->file[0], &mess->file[128], opt_type, &overload);
782
783 if (!ret && (overload & 2))
784 ret = option_find1(&mess->sname[0], &mess->file[64], opt_type, &overload);
Simon Kelleybb01cb92004-12-13 20:56:23 +0000785
786 /* Check the option field is big enough */
787 if (ret && (option_len(ret) < minsize))
788 ret = NULL;
789
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000790 return ret;
791}
792
793static int in_list(unsigned char *list, int opt)
794{
795 int i;
796
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100797 /* If no requested options, send everything, not nothing. */
798 if (!list)
799 return 1;
800
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000801 for (i = 0; list[i] != OPTION_END; i++)
802 if (opt == list[i])
803 return 1;
804
805 return 0;
806}
807
Simon Kelley26128d22004-11-14 16:43:54 +0000808/* Is every member of check matched by a member of pool? */
809static int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool)
810{
811 struct dhcp_netid *tmp1;
812
813 if (!check)
814 return 0;
815
816 for (; check; check = check->next)
817 {
818 if (check->net[0] != '#')
819 {
820 for (tmp1 = pool; tmp1; tmp1 = tmp1->next)
821 if (strcmp(check->net, tmp1->net) == 0)
822 break;
823 if (!tmp1)
824 return 0;
825 }
826 else
827 for (tmp1 = pool; tmp1; tmp1 = tmp1->next)
828 if (strcmp((check->net)+1, tmp1->net) == 0)
829 return 0;
830 }
831 return 1;
832}
833
Simon Kelleya2226412004-05-13 20:27:08 +0100834static struct dhcp_opt *option_find2(struct dhcp_netid *netid, struct dhcp_opt *opts, int opt)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000835{
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100836 struct dhcp_opt *tmp;
837
838 for (tmp = opts; tmp; tmp = tmp->next)
Simon Kelleya2226412004-05-13 20:27:08 +0100839 if (tmp->opt == opt)
840 {
841 if (netid)
842 {
Simon Kelley26128d22004-11-14 16:43:54 +0000843 if (match_netid(tmp->netid, netid))
844 return tmp;
Simon Kelleya2226412004-05-13 20:27:08 +0100845 }
846 else if (!tmp->netid)
847 return tmp;
848 }
849
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100850 return netid ? option_find2(NULL, opts, opt) : NULL;
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000851}
852
853static unsigned char *do_req_options(struct dhcp_context *context,
854 unsigned char *p, unsigned char *end,
855 unsigned char *req_options,
Simon Kelley3be34542004-09-11 19:12:13 +0100856 struct daemon *daemon,
857 char *hostname,
Simon Kelley44a2a312004-03-10 20:04:35 +0000858 struct in_addr iface_addr,
Simon Kelley3be34542004-09-11 19:12:13 +0100859 struct dhcp_netid *netid,
860 struct in_addr subnet_addr)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000861{
Simon Kelley3be34542004-09-11 19:12:13 +0100862 struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
863
Simon Kelley1ab84e22004-01-29 16:48:35 +0000864 if (in_list(req_options, OPTION_MAXMESSAGE))
Simon Kelley3be34542004-09-11 19:12:13 +0100865 p = option_put(p, end, OPTION_MAXMESSAGE, 2, end - (unsigned char *)daemon->dhcp_packet);
Simon Kelley1ab84e22004-01-29 16:48:35 +0000866
Simon Kelley3be34542004-09-11 19:12:13 +0100867 /* rfc3011 says this doesn't need to be in the requested options list. */
868 if (subnet_addr.s_addr)
869 p = option_put(p, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr));
870
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000871 if (in_list(req_options, OPTION_NETMASK) &&
Simon Kelley33820b72004-04-03 21:10:00 +0100872 !option_find2(netid, config_opts, OPTION_NETMASK))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000873 p = option_put(p, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr));
874
Simon Kelley3be34542004-09-11 19:12:13 +0100875 /* May not have a "guessed" broadcast address if we got no packets via a relay
876 from this net yet (ie just unicast renewals after a restart */
877 if (context->broadcast.s_addr &&
878 in_list(req_options, OPTION_BROADCAST) &&
Simon Kelley33820b72004-04-03 21:10:00 +0100879 !option_find2(netid, config_opts, OPTION_BROADCAST))
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000880 p = option_put(p, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
881
Simon Kelley3be34542004-09-11 19:12:13 +0100882 /* Same comments as broadcast apply, and also may not be able to get a sensible
883 default when using subnet select. User must configure by steam in that case. */
884 if (context->router.s_addr &&
885 in_list(req_options, OPTION_ROUTER) &&
Simon Kelley33820b72004-04-03 21:10:00 +0100886 !option_find2(netid, config_opts, OPTION_ROUTER))
Simon Kelley3be34542004-09-11 19:12:13 +0100887 p = option_put(p, end, OPTION_ROUTER, INADDRSZ, ntohl(context->router.s_addr));
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000888
889 if (in_list(req_options, OPTION_DNSSERVER) &&
Simon Kelley33820b72004-04-03 21:10:00 +0100890 !option_find2(netid, config_opts, OPTION_DNSSERVER))
Simon Kelley44a2a312004-03-10 20:04:35 +0000891 p = option_put(p, end, OPTION_DNSSERVER, INADDRSZ, ntohl(iface_addr.s_addr));
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000892
Simon Kelley3be34542004-09-11 19:12:13 +0100893 if (daemon->domain_suffix && in_list(req_options, OPTION_DOMAINNAME) &&
Simon Kelley33820b72004-04-03 21:10:00 +0100894 !option_find2(netid, config_opts, OPTION_DOMAINNAME))
Simon Kelley3be34542004-09-11 19:12:13 +0100895 p = option_put_string(p, end, OPTION_DOMAINNAME, daemon->domain_suffix);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000896
897 /* Note that we ignore attempts to set the hostname using
898 --dhcp-option=12,<name> */
Simon Kelley44a2a312004-03-10 20:04:35 +0000899 if (hostname && in_list(req_options, OPTION_HOSTNAME))
900 p = option_put_string(p, end, OPTION_HOSTNAME, hostname);
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000901
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100902 for (opt=config_opts; opt; opt = opt->next)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000903 {
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100904 if (opt->opt == OPTION_HOSTNAME ||
905 opt->opt == OPTION_MAXMESSAGE ||
906 !in_list(req_options, opt->opt) ||
907 opt != option_find2(netid, config_opts, opt->opt) ||
908 p + opt->len + 3 >= end)
Simon Kelley33820b72004-04-03 21:10:00 +0100909 continue;
910
911 /* For the options we have default values on
912 dhc-option=<optionno> means "don't include this option"
913 not "include a zero-length option" */
914 if (opt->len == 0 &&
915 (opt->opt == OPTION_NETMASK ||
916 opt->opt == OPTION_BROADCAST ||
917 opt->opt == OPTION_ROUTER ||
918 opt->opt == OPTION_DNSSERVER))
919 continue;
920
921 *(p++) = opt->opt;
922 *(p++) = opt->len;
923 if (opt->len == 0)
924 continue;
Simon Kelleya84fa1d2004-04-23 22:21:21 +0100925
Simon Kelley33820b72004-04-03 21:10:00 +0100926 if (opt->is_addr)
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000927 {
Simon Kelley33820b72004-04-03 21:10:00 +0100928 int j;
929 struct in_addr *a = (struct in_addr *)opt->val;
930 for (j = 0; j < opt->len; j+=INADDRSZ, a++)
Simon Kelley1ab84e22004-01-29 16:48:35 +0000931 {
Simon Kelley33820b72004-04-03 21:10:00 +0100932 /* zero means "self" */
933 if (a->s_addr == 0)
934 memcpy(p, &iface_addr, INADDRSZ);
Simon Kelley1ab84e22004-01-29 16:48:35 +0000935 else
Simon Kelley33820b72004-04-03 21:10:00 +0100936 memcpy(p, a, INADDRSZ);
937 p += INADDRSZ;
Simon Kelley1ab84e22004-01-29 16:48:35 +0000938 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000939 }
Simon Kelley33820b72004-04-03 21:10:00 +0100940 else
941 {
942 memcpy(p, opt->val, opt->len);
943 p += opt->len;
944 }
945 }
Simon Kelley9e4abcb2004-01-22 19:47:41 +0000946 return p;
947}
948
949