1/*
2 * Copyright (c) 1999, 2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 * dhcpd.c
25 * - DHCP server
26 */
27
28/*
29 * Modification History
30 * June 17, 1998 	Dieter Siegmund (dieter@apple.com)
31 * - initial revision
32 */
33#include <unistd.h>
34#include <stdlib.h>
35#include <sys/stat.h>
36#include <sys/socket.h>
37#include <sys/ioctl.h>
38#include <sys/file.h>
39#include <sys/time.h>
40#include <errno.h>
41#include <net/if.h>
42#include <netinet/in.h>
43#include <netinet/in_systm.h>
44#include <netinet/ip.h>
45#include <netinet/udp.h>
46#include <netinet/bootp.h>
47#include <netinet/if_ether.h>
48#include <syslog.h>
49#include <arpa/inet.h>
50#include <net/if_arp.h>
51#include <mach/boolean.h>
52#include "util.h"
53#include "netinfo.h"
54#include "dhcp.h"
55#include "rfc_options.h"
56#include "dhcp_options.h"
57#include "host_identifier.h"
58#include "hostlist.h"
59#include "interfaces.h"
60#include "dhcpd.h"
61#include "NICache.h"
62#include "NICachePrivate.h"
63#include "dhcplib.h"
64#include "bootpd.h"
65#include "subnets.h"
66#include "bootpdfile.h"
67#include "bootplookup.h"
68#include "nbo.h"
69
70
71typedef long			dhcp_time_secs_t;
72#define DHCP_INFINITE_TIME	((dhcp_time_secs_t)-1)
73
74#define MAX_RETRY	5
75
76static boolean_t	S_extend_leases = TRUE;
77
78typedef struct {
79    PLCache_t		list;
80} DHCPLeases_t;
81
82static DHCPLeases_t	S_leases;
83
84void
85DHCPLeases_free(DHCPLeases_t * leases)
86{
87    PLCache_free(&leases->list);
88    bzero(leases, sizeof(*leases));
89}
90
91#define DHCP_LEASES_FILE		"/var/db/dhcpd_leases"
92boolean_t
93DHCPLeases_init(DHCPLeases_t * leases)
94{
95    bzero(leases, sizeof(*leases));
96    PLCache_init(&leases->list);
97#define ARBITRARILY_LARGE_NUMBER	(100 * 1024 * 1024)
98    PLCache_set_max(&leases->list, ARBITRARILY_LARGE_NUMBER);
99    if (PLCache_read(&leases->list, DHCP_LEASES_FILE) == FALSE) {
100	goto failed;
101    }
102    return (TRUE);
103 failed:
104    DHCPLeases_free(leases);
105    return (FALSE);
106}
107
108static boolean_t S_remove_host(PLCacheEntry_t * * entry);
109
110boolean_t
111DHCPLeases_reclaim(DHCPLeases_t * leases, interface_t * if_p,
112		   struct in_addr giaddr, struct timeval * time_in_p,
113		   struct in_addr * client_ip)
114{
115    PLCacheEntry_t *	scan;
116
117    for (scan = leases->list.tail; scan; scan = scan->prev) {
118	dhcp_time_secs_t	expiry = 0;
119	struct in_addr		iaddr;
120	int			ip_index;
121	ni_namelist *		ip_nl_p;
122	int			lease_index;
123
124	/* check the IP address */
125	ip_index = ni_proplist_match(scan->pl, NIPROP_IPADDR, NULL);
126	if (ip_index == NI_INDEX_NULL) {
127	    continue;
128	}
129	ip_nl_p = &scan->pl.nipl_val[ip_index].nip_val;
130	if (ip_nl_p->ninl_len == 0) {
131	    continue;
132	}
133	if (inet_aton(ip_nl_p->ninl_val[0], &iaddr) == 0) {
134	    continue;
135	}
136	if (!ip_address_reachable(iaddr, giaddr, if_p)) {
137	    /* not applicable to this network */
138	    continue;
139	}
140	/* check the lease expiration */
141	lease_index = ni_proplist_match(scan->pl, NIPROP_DHCP_LEASE, NULL);
142	if (lease_index != NI_INDEX_NULL) {
143	    ni_namelist *		lease_nl_p;
144	    long			val;
145
146	    lease_nl_p = &scan->pl.nipl_val[lease_index].nip_val;
147	    if (lease_nl_p->ninl_len == 0) {
148		continue;
149	    }
150	    val = strtol(lease_nl_p->ninl_val[0], NULL, 0);
151	    if (val == LONG_MAX && errno == ERANGE) {
152		continue;
153	    }
154	    expiry = (dhcp_time_secs_t)val;
155	}
156	if (lease_index == NI_INDEX_NULL || time_in_p->tv_sec > expiry) {
157	    if (S_remove_host(&scan)) {
158		my_log(LOG_DEBUG, "dhcp: reclaimed address %s",
159		       inet_ntoa(iaddr));
160		*client_ip = iaddr;
161		return (TRUE);
162	    }
163	}
164    }
165    return (FALSE);
166}
167
168
169int
170dhcp_max_message_size(dhcpol_t * options)
171{
172    u_char * 	opt;
173    int 	opt_len;
174    int		val = DHCP_PACKET_MIN;
175
176    opt = dhcpol_find(options, dhcptag_max_dhcp_message_size_e,
177		      &opt_len, NULL);
178    if (opt != NULL && opt_len == 2) {
179	u_int16_t sval = net_uint16_get(opt);
180	if (sval > DHCP_PACKET_MIN) {
181	    val = sval;
182	}
183    }
184    return (val);
185}
186
187void
188dhcp_init()
189{
190    static boolean_t 	first = TRUE;
191
192    if (first == TRUE) {
193	if (DHCPLeases_init(&S_leases) == FALSE) {
194	    return;
195	}
196	first = FALSE;
197    }
198    else {
199	DHCPLeases_t new_leases;
200
201	my_log(LOG_INFO, "dhcp: re-reading lease list");
202	if (DHCPLeases_init(&new_leases) == TRUE) {
203	    DHCPLeases_free(&S_leases);
204	    S_leases = new_leases;
205	}
206    }
207    return;
208}
209
210boolean_t
211DHCPLeases_ip_in_use(DHCPLeases_t * leases, struct in_addr ip)
212{
213    PLCacheEntry_t * entry = PLCache_lookup_ip(&leases->list, ip);
214    return (entry != NULL);
215}
216
217static boolean_t
218S_remove_host(PLCacheEntry_t * * entry)
219{
220    PLCacheEntry_t *	ent = *entry;
221
222    PLCache_remove(&S_leases.list, ent);
223    PLCacheEntry_free(ent);
224    *entry = NULL;
225    PLCache_write(&S_leases.list, DHCP_LEASES_FILE);
226    return (TRUE);
227}
228
229static __inline__ boolean_t
230S_commit_mods()
231{
232    return (PLCache_write(&S_leases.list, DHCP_LEASES_FILE));
233}
234
235static __inline__ char *
236S_lease_propval(ni_proplist * pl_p)
237{
238    return (ni_valforprop(pl_p, NIPROP_DHCP_LEASE));
239}
240
241#define LEASE_FORMAT	"0x%lx"
242
243static void
244S_set_lease(ni_proplist * pl_p, dhcp_time_secs_t lease_time_expiry,
245	    boolean_t * mod)
246{
247    char buf[32];
248
249    sprintf(buf, LEASE_FORMAT, lease_time_expiry);
250    ni_set_prop(pl_p, NIPROP_DHCP_LEASE, buf, mod);
251    return;
252}
253
254static dhcp_time_secs_t
255S_lease_time_expiry(ni_proplist * pl_p)
256{
257    dhcp_time_secs_t 	expiry = DHCP_INFINITE_TIME;
258    ni_name 		str = S_lease_propval(pl_p);
259    long		val;
260
261    if (str) {
262	val = strtol(str, NULL, 0);
263	if (val == LONG_MAX && errno == ERANGE) {
264	    my_log(LOG_INFO, "S_lease_time_expiry: lease '%s' bad", str);
265	    return (0);
266	}
267	expiry = (dhcp_time_secs_t)val;
268    }
269    return (expiry);
270
271}
272
273struct dhcp *
274make_dhcp_reply(struct dhcp * reply, int pkt_size,
275		struct in_addr server_id, dhcp_msgtype_t msg,
276		struct dhcp * request, dhcpoa_t * options)
277{
278    *reply = *request;
279    reply->dp_hops = 0;
280    reply->dp_secs = 0;
281    reply->dp_op = BOOTREPLY;
282    bcopy(rfc_magic, reply->dp_options, sizeof(rfc_magic));
283    dhcpoa_init(options, reply->dp_options + sizeof(rfc_magic),
284		pkt_size - sizeof(struct dhcp) - sizeof(rfc_magic));
285    /* make the reply a dhcp message */
286    if (dhcpoa_add_dhcpmsg(options, msg) != dhcpoa_success_e) {
287	my_log(LOG_INFO,
288	       "make_dhcp_reply: couldn't add dhcp message tag %d: %s", msg,
289	       dhcpoa_err(options));
290	goto err;
291    }
292    /* add our server identifier */
293    if (dhcpoa_add(options, dhcptag_server_identifier_e,
294		   sizeof(server_id), &server_id) != dhcpoa_success_e) {
295	my_log(LOG_INFO,
296	       "make_dhcp_reply: couldn't add server identifier tag: %s",
297	       dhcpoa_err(options));
298	goto err;
299    }
300    return (reply);
301  err:
302    return (NULL);
303}
304
305static struct dhcp *
306make_dhcp_nak(struct dhcp * reply, int pkt_size,
307	      struct in_addr server_id, dhcp_msgtype_t * msg_p,
308	      const char * nak_msg, struct dhcp * request,
309	      dhcpoa_t * options)
310{
311    struct dhcp * r;
312
313    if (debug)
314	printf("sending a NAK: '%s'\n", nak_msg);
315
316    r = make_dhcp_reply(reply, pkt_size, server_id, dhcp_msgtype_nak_e,
317			request, options);
318    if (r == NULL)
319	return (NULL);
320
321    r->dp_ciaddr.s_addr = 0;
322    r->dp_yiaddr.s_addr = 0;
323
324    if (nak_msg) {
325	if (dhcpoa_add(options, dhcptag_message_e, strlen(nak_msg),
326		       nak_msg) != dhcpoa_success_e) {
327	    my_log(LOG_INFO, "dhcpd: couldn't add NAK message type: %s",
328		   dhcpoa_err(options));
329	    goto err;
330	}
331    }
332    if (dhcpoa_add(options, dhcptag_end_e, 0, 0) != dhcpoa_success_e) {
333	my_log(LOG_INFO, "dhcpd: couldn't add end tag: %s",
334	       dhcpoa_err(options));
335	goto err;
336    }
337    *msg_p = dhcp_msgtype_nak_e;
338    return (r);
339 err:
340    return (NULL);
341}
342
343static struct hosts *		S_pending_hosts = NULL;
344
345#define DEFAULT_PENDING_SECS	60
346
347static bool
348S_ipinuse(void * arg, struct in_addr ip)
349{
350    struct hosts * 	hp;
351    struct timeval * 	time_in_p = (struct timeval *)arg;
352
353    if (bootp_getbyip_file(ip, NULL, NULL)
354#if !TARGET_OS_EMBEDDED
355	|| ((use_open_directory == TRUE)
356	    && bootp_getbyip_ds(ip, NULL, NULL))
357#endif /* !TARGET_OS_EMBEDDED */
358        ) {
359	return (TRUE);
360    }
361
362    if (DHCPLeases_ip_in_use(&S_leases, ip) == TRUE) {
363	return (TRUE);
364    }
365    hp = hostbyip(S_pending_hosts, ip);
366    if (hp) {
367	u_long pending_secs = time_in_p->tv_sec - hp->tv.tv_sec;
368
369	if (pending_secs < DEFAULT_PENDING_SECS) {
370	    my_log(LOG_DEBUG, "dhcpd: %s will remain pending %d secs",
371		   inet_ntoa(ip), DEFAULT_PENDING_SECS - pending_secs);
372	    return (TRUE);
373	}
374	hostfree(&S_pending_hosts, hp); /* remove it from the list */
375	return (FALSE);
376    }
377
378    return (FALSE);
379}
380
381#define DHCPD_CREATOR		"dhcpd"
382
383static char *
384S_get_hostname(void * hostname_opt, int hostname_opt_len)
385{
386
387    if (hostname_opt && hostname_opt_len > 0) {
388      	int		i;
389	char * 		h = (char *)hostname_opt;
390	char * 		hostname = malloc(hostname_opt_len + 1);
391
392	for (i = 0; i < hostname_opt_len; i++) {
393	    char 	ch = h[i];
394	    if (ch == 0 || ch == '\n') {
395		ch = '.';
396	    }
397	    hostname[i] = ch;
398	}
399	hostname[hostname_opt_len] = '\0';
400	return (hostname);
401    }
402    return (NULL);
403}
404
405static boolean_t
406S_create_host(char * idstr, char * hwstr,
407	      struct in_addr iaddr, void * hostname_opt, int hostname_opt_len,
408	      dhcp_time_secs_t lease_time_expiry)
409{
410    char		lease_str[128];
411    ni_proplist 	pl;
412
413
414    /* add DHCP-specific properties */
415    NI_INIT(&pl);
416    if (hostname_opt) {
417	char *	h;
418	h = S_get_hostname(hostname_opt, hostname_opt_len);
419
420	ni_proplist_addprop(&pl, NIPROP_NAME, (ni_name)h);
421	free(h);
422    }
423    ni_proplist_addprop(&pl, NIPROP_IPADDR,
424			(ni_name) inet_ntoa(iaddr));
425    ni_proplist_addprop(&pl, NIPROP_HWADDR,
426			(ni_name) hwstr);
427    ni_proplist_addprop(&pl, NIPROP_IDENTIFIER,
428			(ni_name) idstr);
429    sprintf(lease_str, LEASE_FORMAT, lease_time_expiry);
430    ni_proplist_addprop(&pl, NIPROP_DHCP_LEASE, (ni_name)lease_str);
431
432    PLCache_add(&S_leases.list, PLCacheEntry_create(pl));
433    PLCache_write(&S_leases.list, DHCP_LEASES_FILE);
434    ni_proplist_free(&pl);
435    return (TRUE);
436}
437
438typedef enum {
439    dhcp_binding_none_e = 0,
440    dhcp_binding_permanent_e,
441    dhcp_binding_temporary_e,
442} dhcp_binding_t;
443
444static SubnetRef
445acquire_ip(struct in_addr giaddr, interface_t * if_p,
446	   struct timeval * time_in_p, struct in_addr * iaddr_p)
447{
448    SubnetRef 	subnet = NULL;
449
450    if (subnets == NULL) {
451	return (NULL);
452    }
453    if (giaddr.s_addr) {
454	*iaddr_p = giaddr;
455	subnet = SubnetListAcquireAddress(subnets, iaddr_p, S_ipinuse,
456					  time_in_p);
457    }
458    else {
459	int 			i;
460	inet_addrinfo_t *	info;
461
462	for (i = 0; i < if_inet_count(if_p); i++) {
463	    info = if_inet_addr_at(if_p, i);
464	    *iaddr_p = info->netaddr;
465	    subnet = SubnetListAcquireAddress(subnets, iaddr_p, S_ipinuse,
466					      time_in_p);
467	    if (subnet != NULL) {
468		break;
469	    }
470	}
471    }
472    return (subnet);
473}
474
475boolean_t
476dhcp_bootp_allocate(char * idstr, char * hwstr, struct dhcp * rq,
477		    interface_t * if_p, struct timeval * time_in_p,
478		    struct in_addr * iaddr_p, SubnetRef * subnet_p)
479{
480    PLCacheEntry_t * 	entry = NULL;
481    struct in_addr 	iaddr;
482    dhcp_time_secs_t	lease_time_expiry = 0;
483    subnet_match_args_t	match;
484    dhcp_lease_time_t	max_lease;
485    boolean_t		modified = FALSE;
486    SubnetRef		subnet = NULL;
487
488    bzero(&match, sizeof(match));
489    match.if_p = if_p;
490    match.giaddr = rq->dp_giaddr;
491    match.has_binding = FALSE;
492
493    if (bootp_getbyhw_file(rq->dp_htype, rq->dp_chaddr, rq->dp_hlen,
494			   subnet_match, &match, &iaddr, NULL, NULL)
495#if !TARGET_OS_EMBEDDED
496	|| ((use_open_directory == TRUE)
497	    && bootp_getbyhw_ds(rq->dp_htype, rq->dp_chaddr, rq->dp_hlen,
498				subnet_match, &match, &iaddr, NULL, NULL))
499#endif /* !TARGET_OS_EMBEDDED */
500	) {
501
502	/* infinite lease */
503	*iaddr_p = iaddr;
504	if (subnets != NULL) {
505	    /* try exact */
506	    subnet = SubnetListGetSubnetForAddress(subnets, iaddr, TRUE);
507	    if (subnet == NULL) {
508		/* try any */
509		subnet = SubnetListGetSubnetForAddress(subnets, iaddr, FALSE);
510	    }
511	}
512	*subnet_p = subnet;
513	return (TRUE);
514    }
515
516    match.has_binding = FALSE;
517    entry = PLCache_lookup_identifier(&S_leases.list, idstr,
518				      subnet_match, &match, &iaddr,
519				      NULL);
520    if (entry) {
521	if (subnets != NULL) {
522	    subnet = SubnetListGetSubnetForAddress(subnets, iaddr, TRUE);
523	}
524	if (subnet != NULL) {
525	    max_lease = SubnetGetMaxLease(subnet);
526	    lease_time_expiry = max_lease + time_in_p->tv_sec;
527	    S_set_lease(&entry->pl, lease_time_expiry, &modified);
528
529	    PLCache_make_head(&S_leases.list, entry);
530	    *iaddr_p = iaddr;
531	    *subnet_p = subnet;
532	    if (modified && S_commit_mods() == FALSE) {
533		return (FALSE);
534	    }
535	    return (TRUE);
536	}
537	/* remove the old binding, it's not valid */
538	PLCache_remove(&S_leases.list, entry);
539	modified = TRUE;
540	entry = NULL;
541    }
542
543    subnet = acquire_ip(rq->dp_giaddr, if_p, time_in_p, &iaddr);
544    if (subnet == NULL) {
545	if (DHCPLeases_reclaim(&S_leases, if_p, rq->dp_giaddr,
546			       time_in_p, &iaddr)) {
547	    subnet = SubnetListGetSubnetForAddress(subnets, iaddr, TRUE);
548	}
549	if (subnet == NULL) {
550	    if (debug) {
551		printf("no ip addresses\n");
552	    }
553	    if (modified) {
554		S_commit_mods();
555	    }
556	    return (FALSE);
557	}
558    }
559    *subnet_p = subnet;
560    *iaddr_p = iaddr;
561    max_lease = SubnetGetMaxLease(subnet);
562    lease_time_expiry = max_lease + time_in_p->tv_sec;
563    if (S_create_host(idstr, hwstr,
564		      iaddr, NULL, 0, lease_time_expiry) == FALSE) {
565	return (FALSE);
566    }
567    return (TRUE);
568}
569
570void
571dhcp_request(request_t * request, dhcp_msgtype_t msgtype,
572	     boolean_t dhcp_allocate)
573{
574    dhcp_binding_t	binding = dhcp_binding_none_e;
575    char		cid_type;
576    int			cid_len;
577    void *		cid;
578    PLCacheEntry_t * 	entry = NULL;
579    boolean_t		has_binding = FALSE;
580    void *		hostname_opt = NULL;
581    int			hostname_opt_len = 0;
582    char *		hostname = NULL;
583    char *		hwstr = NULL;
584    char *		idstr = NULL;
585    struct in_addr	iaddr;
586    dhcp_lease_time_t	lease = 0;
587    dhcp_time_secs_t	lease_time_expiry = 0;
588    int			len;
589    int			max_packet;
590    dhcp_lease_time_t	min_lease;
591    dhcp_lease_time_t	max_lease;
592    boolean_t		modified = FALSE;
593    dhcpoa_t		options;
594    boolean_t		orphan = FALSE;
595    struct dhcp *	reply = NULL;
596    dhcp_msgtype_t	reply_msgtype = dhcp_msgtype_none_e;
597    struct dhcp *	rq = request->pkt;
598    char		scratch_idstr[128];
599    char		scratch_hwstr[sizeof(rq->dp_chaddr) * 3];
600    SubnetRef 		subnet = NULL;
601    dhcp_lease_time_t *	suggested_lease = NULL;
602    dhcp_cstate_t	state = dhcp_cstate_none_e;
603    uint32_t		txbuf[ETHERMTU / sizeof(uint32_t)];
604    boolean_t		use_broadcast = FALSE;
605
606    iaddr.s_addr = 0;
607    max_packet = dhcp_max_message_size(request->options_p);
608    if (max_packet > sizeof(txbuf)) {
609	max_packet = sizeof(txbuf);
610    }
611    /* need to exclude the IP/UDP header from what we send back */
612    max_packet -= DHCP_PACKET_OVERHEAD;
613
614    /* check for a client identifier */
615    cid = dhcpol_find(request->options_p, dhcptag_client_identifier_e,
616		      &cid_len, NULL);
617    if (cid != NULL) {
618	if (cid_len > 1) {
619	    /* use the client identifier as provided */
620	    cid_type = *((char *)cid);
621	    cid_len--;
622	    cid++;
623	}
624	else {
625	    cid = NULL;
626	}
627    }
628    if (cid == NULL
629	|| (dhcp_ignore_client_identifier && rq->dp_hlen != 0)) {
630	/* use the hardware address as the identifier */
631	cid = rq->dp_chaddr;
632	cid_type = rq->dp_htype;
633	cid_len = rq->dp_hlen;
634    }
635    if (cid_len == 0) {
636	goto no_reply;
637    }
638    idstr = identifierToStringWithBuffer(cid_type, cid, cid_len,
639					 scratch_idstr, sizeof(scratch_idstr));
640    if (idstr == NULL) {
641	goto no_reply;
642    }
643    if (cid_type == 0) {
644	hwstr = identifierToStringWithBuffer(rq->dp_htype, rq->dp_chaddr,
645					     rq->dp_hlen, scratch_hwstr,
646					     sizeof(scratch_hwstr));
647	if (hwstr == NULL) {
648	    goto no_reply;
649	}
650    }
651    else {
652	hwstr = idstr;
653    }
654
655    hostname_opt = dhcpol_find(request->options_p, dhcptag_host_name_e,
656			       &hostname_opt_len, NULL);
657    if (hostname_opt && hostname_opt_len) {
658	my_log(LOG_INFO, "DHCP %s [%s]: %s <%.*s>",
659	       dhcp_msgtype_names(msgtype), if_name(request->if_p), idstr,
660	       hostname_opt_len, hostname_opt);
661    }
662    else {
663	my_log(LOG_INFO, "DHCP %s [%s]: %s",
664	       dhcp_msgtype_names(msgtype), if_name(request->if_p), idstr);
665    }
666
667    suggested_lease =
668	(dhcp_lease_time_t *)dhcpol_find(request->options_p,
669					 dhcptag_lease_time_e,
670					 &len, NULL);
671    if (cid_type != 0) {
672	subnet_match_args_t	match;
673
674	bzero(&match, sizeof(match));
675	match.if_p = request->if_p;
676	match.giaddr = rq->dp_giaddr;
677	match.ciaddr = rq->dp_ciaddr;
678	match.has_binding = FALSE;
679
680	if (bootp_getbyhw_file(cid_type, cid, cid_len,
681			       subnet_match, &match, &iaddr,
682			       &hostname, NULL)
683#if !TARGET_OS_EMBEDDED
684	    || ((use_open_directory == TRUE)
685		&& bootp_getbyhw_ds(cid_type, cid, cid_len,
686				    subnet_match, &match, &iaddr,
687				    &hostname, NULL))
688#endif /* !TARGET_OS_EMBEDDED */
689	    ) {
690	    binding = dhcp_binding_permanent_e;
691	    lease_time_expiry = DHCP_INFINITE_TIME;
692	}
693	if (match.has_binding == TRUE) {
694	    has_binding = TRUE;
695	}
696    }
697
698    if (binding == dhcp_binding_none_e) {
699	boolean_t		some_binding = FALSE;
700	subnet_match_args_t	match;
701
702	bzero(&match, sizeof(match));
703	match.if_p = request->if_p;
704	match.giaddr = rq->dp_giaddr;
705	match.ciaddr = rq->dp_ciaddr;
706
707	/* no permanent netinfo binding: check for a lease */
708	entry = PLCache_lookup_identifier(&S_leases.list, idstr,
709					  subnet_match, &match, &iaddr,
710					  &some_binding);
711	if (some_binding == TRUE) {
712	    has_binding = TRUE;
713	}
714	if (entry != NULL) {
715	    if (subnets != NULL) {
716		subnet = SubnetListGetSubnetForAddress(subnets, iaddr, TRUE);
717	    }
718	    if (subnet == NULL || SubnetDoesAllocate(subnet) == FALSE) {
719		S_remove_host(&entry);
720		my_log(LOG_INFO, "dhcpd: removing %s binding for %s",
721		       idstr, inet_ntoa(iaddr));
722		orphan = TRUE;
723		entry = NULL;
724	    }
725	    else {
726		binding = dhcp_binding_temporary_e;
727		lease_time_expiry = S_lease_time_expiry(&entry->pl);
728		PLCache_make_head(&S_leases.list, entry);
729	    }
730	}
731    }
732    if (binding != dhcp_binding_none_e) {
733	/* client is already bound on this subnet */
734	if (lease_time_expiry == DHCP_INFINITE_TIME) {
735	    /* permanent entry */
736	    lease = DHCP_INFINITE_LEASE;
737	}
738	else {
739	    max_lease = SubnetGetMaxLease(subnet);
740	    min_lease = SubnetGetMinLease(subnet);
741	    if (suggested_lease) {
742		lease = dhcp_lease_ntoh(*suggested_lease);
743		if (lease > max_lease)
744		    lease = max_lease;
745		else if (lease < min_lease)
746		    lease = min_lease;
747	    }
748	    else if ((request->time_in_p->tv_sec + min_lease)
749		     >= lease_time_expiry) {
750		/* expired lease: give it the default lease */
751		lease = min_lease;
752	    }
753	    else { /* give the host the remaining time on the lease */
754		lease = lease_time_expiry - request->time_in_p->tv_sec;
755	    }
756	}
757    }
758
759    switch (msgtype) {
760      case dhcp_msgtype_discover_e: {
761	  state = dhcp_cstate_init_e;
762
763	  { /* delete the pending host entry */
764	      struct hosts *	hp;
765	      hp = hostbyaddr(S_pending_hosts, cid_type, cid, cid_len,
766			      NULL, NULL);
767	      if (hp)
768		  hostfree(&S_pending_hosts, hp);
769	  }
770
771	  if (binding != dhcp_binding_none_e) {
772	      /* client is already bound on this subnet */
773	  }
774	  else if (dhcp_allocate == FALSE) {
775	      /* NetBoot 1.0 enabled, but DHCP is not */
776	      goto no_reply;
777	  }
778	  else { /* find an ip address */
779	      /* allocate a new ip address */
780	      subnet = acquire_ip(rq->dp_giaddr,
781				  request->if_p, request->time_in_p, &iaddr);
782	      if (subnet == NULL) {
783		  if (DHCPLeases_reclaim(&S_leases, request->if_p,
784					 rq->dp_giaddr,
785					 request->time_in_p, &iaddr)) {
786		      if (subnets != NULL) {
787			  subnet = SubnetListGetSubnetForAddress(subnets, iaddr,
788								 TRUE);
789		      }
790		  }
791		  if (subnet == NULL) {
792		      if (debug) {
793			  printf("no ip addresses\n");
794		      }
795		      goto no_reply; /* out of ip addresses */
796		  }
797	      }
798	      max_lease = SubnetGetMaxLease(subnet);
799	      min_lease = SubnetGetMinLease(subnet);
800	      if (suggested_lease) {
801		  lease = dhcp_lease_ntoh(*suggested_lease);
802		  if (lease > max_lease)
803		      lease = max_lease;
804		  else if (lease < min_lease)
805		      lease = min_lease;
806	      }
807	      else {
808		  lease = min_lease;
809	      }
810	  }
811	  { /* keep track of this offer in the pending hosts list */
812	      struct hosts *	hp;
813
814	      hp = hostadd(&S_pending_hosts, request->time_in_p,
815			   cid_type, cid, cid_len,
816			   &iaddr, NULL, NULL);
817	      if (hp == NULL)
818		  goto no_reply;
819	      hp->lease = lease;
820	  }
821	  /*
822	   * allow for drift between server/client clocks by offering
823	   * a lease shorter than the recorded value
824	   */
825	  if (lease == DHCP_INFINITE_LEASE)
826	      lease = dhcp_lease_hton(lease);
827	  else
828	      lease = dhcp_lease_hton(lease_prorate(lease));
829
830	  /* form a reply */
831	  reply = make_dhcp_reply((struct dhcp *)txbuf, max_packet,
832				  if_inet_addr(request->if_p),
833				  reply_msgtype = dhcp_msgtype_offer_e,
834				  rq, &options);
835	  if (reply == NULL)
836	      goto no_reply;
837	  reply->dp_ciaddr.s_addr = 0;
838	  reply->dp_yiaddr = iaddr;
839	  if (dhcpoa_add(&options, dhcptag_lease_time_e, sizeof(lease),
840			 &lease) != dhcpoa_success_e) {
841	      my_log(LOG_INFO, "dhcpd: couldn't add lease time tag: %s",
842		     dhcpoa_err(&options));
843	      goto no_reply;
844	  }
845	  break;
846      }
847      case dhcp_msgtype_request_e: {
848	  const char * 		nak = NULL;
849	  int			optlen;
850	  struct in_addr * 	req_ip;
851	  struct in_addr * 	server_id;
852
853	  server_id = (struct in_addr *)
854	      dhcpol_find(request->options_p, dhcptag_server_identifier_e,
855			  &optlen, NULL);
856	  req_ip = (struct in_addr *)
857	      dhcpol_find(request->options_p, dhcptag_requested_ip_address_e,
858			  &optlen, NULL);
859	  if (server_id) { /* SELECT */
860	      struct hosts *	hp = hostbyaddr(S_pending_hosts, cid_type,
861						cid, cid_len,
862						NULL, FALSE);
863	      if (debug)
864		  printf("SELECT\n");
865	      state = dhcp_cstate_select_e;
866
867	      if (server_id->s_addr != if_inet_addr(request->if_p).s_addr) {
868		  if (debug)
869		      printf("client selected %s\n", inet_ntoa(*server_id));
870		  /* clean up */
871		  if (hp) {
872		      hostfree(&S_pending_hosts, hp);
873		  }
874
875		  if (binding == dhcp_binding_temporary_e) {
876		      S_remove_host(&entry);
877		  }
878		  if (detect_other_dhcp_server) {
879		      my_log(LOG_INFO,
880			     "dhcpd: detected another DHCP server %s, exiting",
881			     inet_ntoa(*server_id));
882		      exit(2);
883		  }
884		  goto no_reply;
885	      }
886	      if (binding == dhcp_binding_none_e && hp == NULL) {
887		  goto no_reply;
888	      }
889
890	      if (hp) {
891		  iaddr = hp->iaddr;
892		  if (hp->lease == DHCP_INFINITE_LEASE)
893		      lease_time_expiry = DHCP_INFINITE_LEASE;
894		  else {
895		      lease_time_expiry
896			  = hp->lease + request->time_in_p->tv_sec;
897		  }
898		  lease = hp->lease;
899	      }
900	      else {
901		  /* this case only happens if the client sends
902		   * a REQUEST without sending a DISCOVER first
903		   * but we have a binding
904		   */
905
906		  /* iaddr, lease_time_expiry, lease
907		   * are all set above
908		   */
909	      }
910	      if (req_ip == NULL
911		  || req_ip->s_addr != iaddr.s_addr) {
912		  if (req_ip == NULL) {
913		      my_log(LOG_INFO,
914			     "dhcpd: host %s sends SELECT without"
915			     " Requested IP option", idstr);
916		  }
917		  else {
918		      my_log(LOG_INFO,
919			     "dhcpd: host %s sends SELECT with wrong"
920			     " IP address %s, should be " IP_FORMAT,
921			     idstr, inet_ntoa(*req_ip), IP_LIST(&iaddr));
922		  }
923		  use_broadcast = TRUE;
924		  reply = make_dhcp_nak((struct dhcp *)txbuf, max_packet,
925					if_inet_addr(request->if_p),
926					&reply_msgtype,
927					"protocol error in SELECT state",
928					rq, &options);
929		  if (reply)
930		      goto reply;
931		  goto no_reply;
932	      }
933	      if (binding != dhcp_binding_none_e) {
934		  if (binding == dhcp_binding_temporary_e) {
935		      if (hostname_opt && hostname_opt_len > 0) {
936			  char *	h;
937
938			  h = S_get_hostname(hostname_opt,
939					     hostname_opt_len);
940			  ni_set_prop(&entry->pl, NIPROP_NAME, h, &modified);
941			  free(h);
942		      }
943		      S_set_lease(&entry->pl, lease_time_expiry, &modified);
944		  }
945	      }
946	      else { /* create a new host entry */
947		  if (subnets != NULL) {
948		      subnet = SubnetListGetSubnetForAddress(subnets, iaddr,
949							     TRUE);
950		  }
951		  if (subnet == NULL
952		      || S_create_host(idstr, hwstr, iaddr,
953				       hostname_opt, hostname_opt_len,
954				       lease_time_expiry) == FALSE) {
955		      reply = make_dhcp_nak((struct dhcp *)txbuf,
956					    max_packet,
957					    if_inet_addr(request->if_p),
958					    &reply_msgtype,
959					    "unexpected server failure",
960					    rq, &options);
961		      if (reply)
962			  goto reply;
963		      goto no_reply;
964		  }
965	      }
966	  } /* select */
967	  else /* init-reboot/renew/rebind */ {
968	      if (req_ip) { /* init-reboot */
969		  if (debug)
970		      printf("init-reboot\n");
971		  state = dhcp_cstate_init_reboot_e;
972		  if (binding == dhcp_binding_none_e) {
973		      if (orphan == FALSE) {
974			  my_log(LOG_DEBUG, "dhcpd: INIT-REBOOT host "
975				 "%s binding for %s with another server",
976				 idstr, inet_ntoa(*req_ip));
977			  goto no_reply;
978		      }
979		      nak = "requested address no longer available";
980		      use_broadcast = TRUE;
981		      goto send_nak;
982		  }
983		  if (req_ip->s_addr != iaddr.s_addr) {
984		      nak = "requested address incorrect";
985		      use_broadcast = TRUE;
986		      goto send_nak;
987		  }
988	      } /* init-reboot */
989	      else if (rq->dp_ciaddr.s_addr) { /* renew/rebind */
990		  if (debug) {
991		      if (request->dstaddr_p == NULL
992			  || ntohl(request->dstaddr_p->s_addr)
993			  == INADDR_BROADCAST)
994			  printf("rebind\n");
995		      else
996			  printf("renew\n");
997		  }
998		  if (binding == dhcp_binding_none_e) {
999		      if (orphan == FALSE) {
1000			  if (debug) {
1001			      if (has_binding)
1002				  printf("Client binding is not applicable\n");
1003			      else
1004				  printf("No binding for client\n");
1005			  }
1006			  goto no_reply;
1007		      }
1008		      nak = "requested address no longer available";
1009		      use_broadcast = TRUE;
1010		      goto send_nak;
1011		  }
1012		  if (request->dstaddr_p == NULL
1013		      || ntohl(request->dstaddr_p->s_addr) == INADDR_BROADCAST
1014		      || rq->dp_giaddr.s_addr) { /* REBIND */
1015		      state = dhcp_cstate_rebind_e;
1016		      if (rq->dp_ciaddr.s_addr != iaddr.s_addr) {
1017			  if (debug)
1018			      printf("Incorrect ciaddr " IP_FORMAT
1019				     " should be " IP_FORMAT "\n",
1020				     IP_LIST(&rq->dp_ciaddr),
1021				     IP_LIST(&iaddr));
1022			  goto no_reply;
1023		      }
1024		  }
1025		  else { /* RENEW */
1026		      state = dhcp_cstate_renew_e;
1027		      if (rq->dp_ciaddr.s_addr != iaddr.s_addr) {
1028			  my_log(LOG_INFO,
1029				 "dhcpd: client ciaddr=%s should use "
1030				 IP_FORMAT, inet_ntoa(rq->dp_ciaddr),
1031				 IP_LIST(&iaddr));
1032			  iaddr = rq->dp_ciaddr; /* trust it anyways */
1033		      }
1034		  }
1035	      } /* renew/rebind */
1036	      else {
1037		  my_log(LOG_DEBUG,
1038			 "dhcpd: host %s in unknown state", idstr);
1039		  goto no_reply;
1040	      }
1041
1042	      if (binding == dhcp_binding_permanent_e) {
1043		  lease = DHCP_INFINITE_LEASE;
1044	      }
1045	      else {
1046		  if (hostname_opt && hostname_opt_len > 0) {
1047		      char * h;
1048		      h = S_get_hostname(hostname_opt, hostname_opt_len);
1049		      ni_set_prop(&entry->pl, NIPROP_NAME, h, &modified);
1050		      free(h);
1051		  }
1052		  max_lease = SubnetGetMaxLease(subnet);
1053		  min_lease = SubnetGetMaxLease(subnet);
1054		  if (suggested_lease) {
1055		      lease = dhcp_lease_ntoh(*suggested_lease);
1056		      if (lease > max_lease)
1057			  lease = max_lease;
1058		      else if (lease < min_lease)
1059			  lease = min_lease;
1060		  }
1061		  else if (S_extend_leases) {
1062		      /* automatically extend the lease */
1063		      lease = min_lease;
1064		      my_log(LOG_DEBUG,
1065			     "dhcpd: %s lease extended to %s client",
1066			     inet_ntoa(iaddr), dhcp_cstate_str(state));
1067		  }
1068		  else {
1069		      if (request->time_in_p->tv_sec >= lease_time_expiry) {
1070			  /* send a nak */
1071			  nak = "lease expired";
1072			  goto send_nak;
1073		      }
1074		      /* give the host the remaining time on the lease */
1075		      lease = lease_time_expiry - request->time_in_p->tv_sec;
1076		  }
1077		  if (lease == DHCP_INFINITE_LEASE) {
1078		      lease_time_expiry = DHCP_INFINITE_TIME;
1079		  }
1080		  else {
1081		      lease_time_expiry = lease + request->time_in_p->tv_sec;
1082		  }
1083		  S_set_lease(&entry->pl, lease_time_expiry, &modified);
1084	      }
1085	  } /* init-reboot/renew/rebind */
1086      send_nak:
1087	  if (nak) {
1088	      reply = make_dhcp_nak((struct dhcp *)txbuf, max_packet,
1089				    if_inet_addr(request->if_p),
1090				    &reply_msgtype, nak,
1091				    rq, &options);
1092	      if (reply)
1093		  goto reply;
1094	      goto no_reply;
1095	  }
1096	  /*
1097	   * allow for drift between server/client clocks by offering
1098	   * a lease shorter than the recorded value
1099	   */
1100	  if (lease == DHCP_INFINITE_LEASE)
1101	      lease = dhcp_lease_hton(lease);
1102	  else
1103	      lease = dhcp_lease_hton(lease_prorate(lease));
1104
1105	  reply = make_dhcp_reply((struct dhcp *)txbuf, max_packet,
1106				  if_inet_addr(request->if_p),
1107				  reply_msgtype = dhcp_msgtype_ack_e,
1108				  rq, &options);
1109	  reply->dp_yiaddr = iaddr;
1110	  if (dhcpoa_add(&options, dhcptag_lease_time_e,
1111			 sizeof(lease), &lease) != dhcpoa_success_e) {
1112	      my_log(LOG_INFO, "dhcpd: couldn't add lease time tag: %s",
1113		     dhcpoa_err(&options));
1114	      goto no_reply;
1115	  }
1116	  break;
1117      }
1118      case dhcp_msgtype_decline_e: {
1119	  int			optlen;
1120	  struct in_addr * 	req_ip;
1121	  struct in_addr * 	server_id;
1122
1123	  server_id = (struct in_addr *)
1124	      dhcpol_find(request->options_p, dhcptag_server_identifier_e,
1125			  &optlen, NULL);
1126	  req_ip = (struct in_addr *)
1127	      dhcpol_find(request->options_p, dhcptag_requested_ip_address_e,
1128			  &optlen, NULL);
1129	  if (server_id == NULL || req_ip == NULL) {
1130	      goto no_reply;
1131	  }
1132	  if (server_id->s_addr != if_inet_addr(request->if_p).s_addr) {
1133	      my_log(LOG_DEBUG, "dhcpd: host %s "
1134		     "declines IP %s from server " IP_FORMAT,
1135		     idstr, inet_ntoa(*req_ip), IP_LIST(server_id));
1136	      goto no_reply;
1137	  }
1138
1139	  if (binding == dhcp_binding_temporary_e
1140	      && iaddr.s_addr == req_ip->s_addr) {
1141	      ni_delete_prop(&entry->pl, NIPROP_IDENTIFIER, &modified);
1142	      S_set_lease(&entry->pl,
1143			  request->time_in_p->tv_sec + DHCP_DECLINE_WAIT_SECS,
1144			  &modified);
1145	      ni_set_prop(&entry->pl, NIPROP_DHCP_DECLINED,
1146			  idstr, &modified);
1147	      my_log(LOG_INFO, "dhcpd: IP %s declined by %s",
1148		     inet_ntoa(iaddr), idstr);
1149	      if (debug) {
1150		  printf("marking host %s as declined\n", inet_ntoa(iaddr));
1151	      }
1152	  }
1153	  break;
1154      }
1155      case dhcp_msgtype_release_e: {
1156	  if (binding == dhcp_binding_temporary_e) {
1157	      if (debug) {
1158		  printf("%s released by client, setting expiration to now\n",
1159			 inet_ntoa(iaddr));
1160	      }
1161	      /* set the lease expiration time to now */
1162	      S_set_lease(&entry->pl, request->time_in_p->tv_sec, &modified);
1163	  }
1164	  break;
1165      }
1166      case dhcp_msgtype_inform_e: {
1167	  iaddr = rq->dp_ciaddr;
1168	  reply = make_dhcp_reply((struct dhcp *)txbuf, max_packet,
1169				  if_inet_addr(request->if_p),
1170				  reply_msgtype = dhcp_msgtype_ack_e,
1171				  rq, &options);
1172	  if (reply)
1173	      goto reply;
1174	  goto no_reply;
1175	  break;
1176      }
1177      default: {
1178	  if (debug)
1179	      printf("unknown message ignored\n");
1180      }
1181      break;
1182      }
1183
1184  reply:
1185    if (debug)
1186	printf("state=%s\n", dhcp_cstate_str(state));
1187    if (binding == dhcp_binding_temporary_e && modified) {
1188	if (S_commit_mods() == FALSE)
1189	    goto no_reply;
1190    }
1191    { /* check the seconds field */
1192	u_int16_t	secs;
1193
1194	secs = (u_int16_t)ntohs(rq->dp_secs);
1195	if (secs < reply_threshold_seconds) {
1196	    if (debug) {
1197		printf("rp->dp_secs %d < threshold %d\n",
1198		       secs, reply_threshold_seconds);
1199	    }
1200	    goto no_reply;
1201	}
1202
1203    }
1204    if (reply) {
1205	if (reply_msgtype == dhcp_msgtype_ack_e ||
1206	    reply_msgtype == dhcp_msgtype_offer_e) {
1207	    int			num_params;
1208	    const uint8_t *	params;
1209
1210	    params = (const uint8_t *)
1211		dhcpol_find(request->options_p,
1212			    dhcptag_parameter_request_list_e,
1213			    &num_params, NULL);
1214
1215	    bzero(reply->dp_file, sizeof(reply->dp_file));
1216
1217	    reply->dp_siaddr = if_inet_addr(request->if_p);
1218	    strcpy((char *)reply->dp_sname, server_name);
1219
1220	    /* add the client-specified parameters */
1221	    if (params != NULL)
1222		(void)add_subnet_options(hostname, iaddr,
1223					 request->if_p,
1224					 &options, params, num_params);
1225	    /* terminate the options */
1226	    if (dhcpoa_add(&options, dhcptag_end_e, 0, NULL)
1227		!= dhcpoa_success_e) {
1228		my_log(LOG_INFO, "couldn't add end tag: %s",
1229		       dhcpoa_err(&options));
1230		goto no_reply;
1231	    }
1232	}
1233	{
1234	    int size = sizeof(struct dhcp) + sizeof(rfc_magic)
1235		+ dhcpoa_used(&options);
1236
1237	    if (size < sizeof(struct bootp)) {
1238		/* pad out to BOOTP-sized packet */
1239		size = sizeof(struct bootp);
1240	    }
1241	    if (debug) {
1242		printf("\nSending: DHCP %s (size %d)\n",
1243		       dhcp_msgtype_names(reply_msgtype), size);
1244	    }
1245	    if (sendreply(request->if_p, (struct bootp *)reply, size,
1246			  use_broadcast, &iaddr)) {
1247		if (hostname == NULL && entry != NULL) {
1248		    hostname = ni_valforprop(&entry->pl, NIPROP_NAME);
1249		    if (hostname != NULL)
1250			hostname = strdup(hostname);
1251		}
1252		my_log(LOG_INFO, "%s sent %s %s pktsize %d",
1253		       dhcp_msgtype_names(reply_msgtype),
1254		       (hostname != NULL)
1255		       ? hostname : (char *)"<no hostname>",
1256		       inet_ntoa(iaddr), size);
1257	    }
1258	}
1259    }
1260 no_reply:
1261    if (hostname != NULL)
1262	free(hostname);
1263    if (idstr != scratch_idstr)
1264	free(idstr);
1265    if (hwstr != NULL && hwstr != idstr && hwstr != scratch_hwstr)
1266	free(hwstr);
1267    return;
1268}
1269