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