/* * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * dhcpd.m * - netinfo-aware DHCP server */ /* * Modification History * June 17, 1998 Dieter Siegmund (dieter@apple.com) * - initial revision */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "netinfo.h" #include "dhcp.h" #include "rfc_options.h" #include "dhcp_options.h" #include "host_identifier.h" #include "hostlist.h" #include "interfaces.h" #include "dhcpd.h" #include "NIDomain.h" #include "NICache.h" #include "NICachePrivate.h" #include "dhcplib.h" #include "bootpd.h" #import "subnetDescr.h" #define MAX_RETRY 5 static boolean_t S_extend_leases = TRUE; typedef struct { NIDomain_t * domain; ni_id dir; PLCache_t list; } DHCPLeases_t; static DHCPLeases_t S_leases; void DHCPLeases_free(DHCPLeases_t * leases) { PLCache_free(&leases->list); bzero(leases, sizeof(*leases)); } #define DHCP_LEASES_FILE "/var/db/dhcpd_leases" boolean_t DHCPLeases_init(DHCPLeases_t * leases) { bzero(leases, sizeof(*leases)); PLCache_init(&leases->list); #define ARBITRARILY_LARGE_NUMBER (100 * 1024 * 1024) PLCache_set_max(&leases->list, ARBITRARILY_LARGE_NUMBER); if (PLCache_read(&leases->list, DHCP_LEASES_FILE) == FALSE) { goto failed; } return (TRUE); failed: DHCPLeases_free(leases); return (FALSE); } static boolean_t S_remove_host(PLCacheEntry_t * * entry); boolean_t DHCPLeases_reclaim(DHCPLeases_t * leases, interface_t * if_p, struct in_addr giaddr, struct timeval * time_in_p, struct in_addr * client_ip) { PLCacheEntry_t * scan; for (scan = leases->list.tail; scan; scan = scan->prev) { dhcp_time_secs_t expiry = 0; struct in_addr iaddr; int ip_index; ni_namelist * ip_nl_p; int lease_index; /* check the IP address */ ip_index = ni_proplist_match(scan->pl, NIPROP_IPADDR, NULL); if (ip_index == NI_INDEX_NULL) { continue; } ip_nl_p = &scan->pl.nipl_val[ip_index].nip_val; if (ip_nl_p->ninl_len == 0) { continue; } if (inet_aton(ip_nl_p->ninl_val[0], &iaddr) == 0) { continue; } if (!ip_address_reachable(iaddr, giaddr, if_p)) { /* not applicable to this network */ continue; } /* check the lease expiration */ lease_index = ni_proplist_match(scan->pl, NIPROP_DHCP_LEASE, NULL); if (lease_index != NI_INDEX_NULL) { ni_namelist * lease_nl_p; lease_nl_p = &scan->pl.nipl_val[lease_index].nip_val; if (lease_nl_p->ninl_len == 0) { continue; } expiry = strtol(lease_nl_p->ninl_val[0], NULL, NULL); if (expiry == LONG_MAX && errno == ERANGE) { continue; } } if (lease_index == NI_INDEX_NULL || time_in_p->tv_sec > expiry) { if (S_remove_host(&scan)) { my_log(LOG_DEBUG, "dhcp: reclaimed address %s", inet_ntoa(iaddr)); *client_ip = iaddr; return (TRUE); } } } return (FALSE); } int dhcp_max_message_size(dhcpol_t * options) { u_char * opt; int opt_len; int val = DHCP_PACKET_MIN; opt = dhcpol_find(options, dhcptag_max_dhcp_message_size_e, &opt_len, NULL); if (opt != NULL && opt_len == 2) { u_int16_t sval; sval = ntohs(*((u_int16_t *)opt)); if (sval > val && sval <= 1500) { val = sval; } } return (val); } void dhcp_init() { static boolean_t first = TRUE; if (first == TRUE) { if (DHCPLeases_init(&S_leases) == FALSE) { return; } first = FALSE; } else { DHCPLeases_t new_leases; my_log(LOG_INFO, "dhcp: re-reading lease list"); if (DHCPLeases_init(&new_leases) == TRUE) { DHCPLeases_free(&S_leases); S_leases = new_leases; } } return; } boolean_t DHCPLeases_ip_in_use(DHCPLeases_t * leases, struct in_addr ip) { PLCacheEntry_t * entry = PLCache_lookup_ip(&leases->list, ip); return (entry != NULL); } static boolean_t S_remove_host(PLCacheEntry_t * * entry) { PLCacheEntry_t * ent = *entry; PLCache_remove(&S_leases.list, ent); PLCacheEntry_free(ent); *entry = NULL; PLCache_write(&S_leases.list, DHCP_LEASES_FILE); return (TRUE); } static __inline__ boolean_t S_commit_mods() { return (PLCache_write(&S_leases.list, DHCP_LEASES_FILE)); } static __inline__ u_char * S_lease_propval(ni_proplist * pl_p) { return (ni_valforprop(pl_p, NIPROP_DHCP_LEASE)); } #define LEASE_FORMAT "0x%x" static void S_set_lease(ni_proplist * pl_p, dhcp_time_secs_t lease_time_expiry, boolean_t * mod) { u_char buf[32]; sprintf(buf, LEASE_FORMAT, lease_time_expiry); ni_set_prop(pl_p, NIPROP_DHCP_LEASE, buf, mod); return; } static dhcp_time_secs_t S_lease_time_expiry(ni_proplist * pl_p) { dhcp_time_secs_t expiry = DHCP_INFINITE_TIME; ni_name str = S_lease_propval(pl_p); if (str) { expiry = strtol(str, NULL, NULL); if (expiry == LONG_MAX && errno == ERANGE) { my_log(LOG_INFO, "S_lease_time_expiry: lease '%s' bad", str); return (0); } } return (expiry); } static dhcp_lease_t max_lease_length = DHCP_MAX_LEASE; static dhcp_lease_t min_lease_length = DHCP_MIN_LEASE; static void lease_from_subnet(id subnet, dhcp_lease_t * min_lease, dhcp_lease_t * max_lease) { ni_namelist * nl_p; *min_lease = min_lease_length; *max_lease = max_lease_length; nl_p = [subnet lookup:SUBNETPROP_LEASE_MIN]; if (nl_p != NULL && nl_p->ninl_len) { *min_lease = (dhcp_lease_t) strtol(nl_p->ninl_val[0], NULL, NULL); if (debug) printf("min_lease is %d\n", *min_lease); } nl_p = [subnet lookup:SUBNETPROP_LEASE_MAX]; if (nl_p != NULL && nl_p->ninl_len) { *max_lease = (dhcp_lease_t) strtol(nl_p->ninl_val[0], NULL, NULL); if (debug) printf("max_lease is %d\n", *max_lease); } return; } struct dhcp * make_dhcp_reply(struct dhcp * reply, int pkt_size, struct in_addr server_id, dhcp_msgtype_t msg, struct dhcp * request, dhcpoa_t * options) { *reply = *request; reply->dp_hops = 0; reply->dp_secs = 0; reply->dp_op = BOOTREPLY; bcopy(rfc_magic, reply->dp_options, sizeof(rfc_magic)); dhcpoa_init(options, reply->dp_options + sizeof(rfc_magic), pkt_size - sizeof(struct dhcp) - sizeof(rfc_magic)); /* make the reply a dhcp message */ if (dhcpoa_add_dhcpmsg(options, msg) != dhcpoa_success_e) { my_log(LOG_INFO, "make_dhcp_reply: couldn't add dhcp message tag %d: %s", msg, dhcpoa_err(options)); goto err; } /* add our server identifier */ if (dhcpoa_add(options, dhcptag_server_identifier_e, sizeof(server_id), &server_id) != dhcpoa_success_e) { my_log(LOG_INFO, "make_dhcp_reply: couldn't add server identifier tag: %s", dhcpoa_err(options)); goto err; } return (reply); err: return (NULL); } static __inline__ struct dhcp * make_dhcp_nak(struct dhcp * reply, int pkt_size, struct in_addr server_id, dhcp_msgtype_t * msg_p, char * nak_msg, struct dhcp * request, dhcpoa_t * options) { struct dhcp * r; if (debug) printf("sending a NAK: '%s'\n", nak_msg); r = make_dhcp_reply(reply, pkt_size, server_id, dhcp_msgtype_nak_e, request, options); if (r == NULL) return (NULL); r->dp_ciaddr.s_addr = 0; r->dp_yiaddr.s_addr = 0; if (nak_msg) { if (dhcpoa_add(options, dhcptag_message_e, strlen(nak_msg), nak_msg) != dhcpoa_success_e) { my_log(LOG_INFO, "dhcpd: couldn't add NAK message type: %s", dhcpoa_err(options)); goto err; } } if (dhcpoa_add(options, dhcptag_end_e, 0, 0) != dhcpoa_success_e) { my_log(LOG_INFO, "dhcpd: couldn't add end tag: %s", dhcpoa_err(options)); goto err; } *msg_p = dhcp_msgtype_nak_e; return (r); err: return (NULL); } static struct hosts * S_pending_hosts = NULL; #define DEFAULT_PENDING_SECS 60 static boolean_t S_ipinuse(void * arg, struct in_addr ip) { struct hosts * hp; struct timeval * time_in_p = (struct timeval *)arg; if (NICache_ip_in_use(&cache, ip, NULL)) { return (TRUE); } if (DHCPLeases_ip_in_use(&S_leases, ip) == TRUE) { return (TRUE); } hp = hostbyip(S_pending_hosts, ip); if (hp) { u_long pending_secs = time_in_p->tv_sec - hp->tv.tv_sec; if (pending_secs < DEFAULT_PENDING_SECS) { my_log(LOG_DEBUG, "dhcpd: %s will remain pending %d secs", inet_ntoa(ip), DEFAULT_PENDING_SECS - pending_secs); return (TRUE); } hostfree(&S_pending_hosts, hp); /* remove it from the list */ return (FALSE); } return (FALSE); } #define DHCPD_CREATOR "dhcpd" static char * S_get_hostname(void * hostname_opt, int hostname_opt_len, struct in_addr iaddr) { if (hostname_opt && hostname_opt_len > 0) { int i; char * h = (char *)hostname_opt; char * hostname = malloc(hostname_opt_len + 1); for (i = 0; i < hostname_opt_len; i++) { char ch = h[i]; if (ch == 0 || ch == '\n') { ch = '.'; } hostname[i] = ch; } hostname[hostname_opt_len] = '\0'; return (hostname); } return (NULL); } static boolean_t S_create_host(char * idstr, char * hwstr, struct in_addr iaddr, u_char * hostname, dhcp_time_secs_t lease_time_expiry) { ni_id child = {0, 0}; u_char lease_str[128]; ni_proplist pl; /* add DHCP-specific properties */ NI_INIT(&pl); if (hostname) { ni_proplist_addprop(&pl, NIPROP_NAME, (ni_name)hostname); } ni_proplist_addprop(&pl, NIPROP_IPADDR, (ni_name) inet_ntoa(iaddr)); ni_proplist_addprop(&pl, NIPROP_HWADDR, (ni_name) hwstr); ni_proplist_addprop(&pl, NIPROP_IDENTIFIER, (ni_name) idstr); sprintf(lease_str, LEASE_FORMAT, lease_time_expiry); ni_proplist_addprop(&pl, NIPROP_DHCP_LEASE, (ni_name)lease_str); PLCache_add(&S_leases.list, PLCacheEntry_create(child, pl)); PLCache_write(&S_leases.list, DHCP_LEASES_FILE); ni_proplist_free(&pl); return (TRUE); } #if 0 static boolean_t S_find_parameter(u_char * params, int nparams, int param) { int i; for (i = 0; params && i < nparams; i++) { if (params[i] == param) return (TRUE); } return (FALSE); } #endif 0 #define TXBUF_SIZE 2048 static char txbuf[TXBUF_SIZE]; typedef enum { dhcp_binding_none_e = 0, dhcp_binding_permanent_e, dhcp_binding_temporary_e, } dhcp_binding_t; static id acquire_ip(struct in_addr giaddr, interface_t * if_p, struct timeval * time_in_p, struct in_addr * iaddr_p) { id subnet = nil; if (giaddr.s_addr) { *iaddr_p = giaddr; subnet = [subnets acquireIpSupernet:iaddr_p ClientType:DHCP_CLIENT_TYPE Func:S_ipinuse Arg:time_in_p]; } else { int i; inet_addrinfo_t * info; for (i = 0; i < if_inet_count(if_p); i++) { info = if_inet_addr_at(if_p, i); *iaddr_p = info->netaddr; subnet = [subnets acquireIpSupernet:iaddr_p ClientType:DHCP_CLIENT_TYPE Func:S_ipinuse Arg:time_in_p]; if (subnet != nil) { break; } } } return (subnet); } boolean_t dhcp_bootp_allocate(char * idstr, char * hwstr, struct dhcp * rq, interface_t * if_p, struct timeval * time_in_p, struct in_addr * iaddr_p, id * subnet_p) { PLCacheEntry_t * entry = NULL; struct in_addr iaddr; dhcp_time_secs_t lease_time_expiry = 0; subnet_match_args_t match; dhcp_lease_t max_lease = max_lease_length; dhcp_lease_t min_lease = min_lease_length; boolean_t modified = FALSE; subnetEntry * subnet = nil; bzero(&match, sizeof(match)); match.if_p = if_p; match.giaddr = rq->dp_giaddr; match.has_binding = FALSE; /* check for a permanent binding */ entry = NICache_lookup_hw(&cache, time_in_p, rq->dp_htype, rq->dp_chaddr, rq->dp_hlen, subnet_match, &match, NULL, &iaddr); if (entry) { /* infinite lease */ *iaddr_p = iaddr; *subnet_p = [subnets entry:iaddr]; return (TRUE); } match.has_binding = FALSE; entry = PLCache_lookup_identifier(&S_leases.list, idstr, subnet_match, &match, &iaddr, NULL); if (entry) { subnet = [subnets entry:iaddr]; if (subnet != nil) { lease_from_subnet(subnet, &min_lease, &max_lease); lease_time_expiry = max_lease + time_in_p->tv_sec; S_set_lease(&entry->pl, lease_time_expiry, &modified); PLCache_make_head(&S_leases.list, entry); *iaddr_p = iaddr; *subnet_p = subnet; if (modified && S_commit_mods() == FALSE) { return (FALSE); } return (TRUE); } /* remove the old binding, it's not valid */ PLCache_remove(&S_leases.list, entry); modified = TRUE; entry = NULL; } subnet = acquire_ip(rq->dp_giaddr, if_p, time_in_p, &iaddr); if (subnet == nil) { if (DHCPLeases_reclaim(&S_leases, if_p, rq->dp_giaddr, time_in_p, &iaddr)) { subnet = [subnets entry:iaddr]; } if (subnet == nil) { if (debug) { printf("no ip addresses\n"); } if (modified) { S_commit_mods(); } return (FALSE); } } *subnet_p = subnet; *iaddr_p = iaddr; lease_from_subnet(subnet, &min_lease, &max_lease); lease_time_expiry = max_lease + time_in_p->tv_sec; if (S_create_host(idstr, hwstr, iaddr, NULL, lease_time_expiry) == FALSE) { return (FALSE); } return (TRUE); } void dhcp_request(request_t * request, dhcp_msgtype_t msgtype, boolean_t dhcp_allocate) { dhcp_binding_t binding = dhcp_binding_none_e; u_char * bootfile = NULL; char cid_type; int cid_len; void * cid; NIDomain_t * domain = NULL; PLCacheEntry_t * entry = NULL; boolean_t has_binding = FALSE; void * hostname_opt = NULL; int hostname_opt_len = 0; char * hostname = NULL; char * hwstr = NULL; char * idstr = NULL; struct in_addr iaddr; dhcp_lease_t lease = 0; dhcp_time_secs_t lease_time_expiry = 0; int len; int max_packet = dhcp_max_message_size(request->options_p); dhcp_lease_t min_lease = min_lease_length; dhcp_lease_t max_lease = max_lease_length; boolean_t modified = FALSE; dhcpoa_t options; boolean_t orphan = FALSE; struct dhcp * reply = NULL; dhcp_msgtype_t reply_msgtype = dhcp_msgtype_none_e; struct dhcp * rq = request->pkt; id subnet = nil; dhcp_lease_t * suggested_lease = NULL; dhcp_cstate_t state = dhcp_cstate_none_e; boolean_t use_broadcast = FALSE; iaddr.s_addr = 0; /* check for a client identifier */ cid = dhcpol_find(request->options_p, dhcptag_client_identifier_e, &cid_len, NULL); if (cid && cid_len > 1) { /* use the client identifier as provided */ cid_type = *((char *)cid); cid_len--; cid++; } else { /* use the hardware address as the identifier */ cid = rq->dp_chaddr; cid_type = rq->dp_htype; cid_len = rq->dp_hlen; } idstr = identifierToString(cid_type, cid, cid_len); if (cid_type == 0) hwstr = identifierToString(rq->dp_htype, rq->dp_chaddr, rq->dp_hlen); else { hwstr = idstr; } hostname_opt = dhcpol_find(request->options_p, dhcptag_host_name_e, &hostname_opt_len, NULL); if (hostname_opt && hostname_opt_len) { my_log(LOG_INFO, "DHCP %s [%s]: %s <%.*s>", dhcp_msgtype_names(msgtype), if_name(request->if_p), idstr, hostname_opt_len, hostname_opt); } else { my_log(LOG_INFO, "DHCP %s [%s]: %s", dhcp_msgtype_names(msgtype), if_name(request->if_p), idstr); } suggested_lease = (dhcp_lease_t *)dhcpol_find(request->options_p, dhcptag_lease_time_e, &len, NULL); if (cid_type != 0) { subnet_match_args_t match; bzero(&match, sizeof(match)); match.if_p = request->if_p; match.giaddr = rq->dp_giaddr; match.ciaddr = rq->dp_ciaddr; match.has_binding = FALSE; /* identifier is a h/w address - check for a permanent binding */ entry = NICache_lookup_hw(&cache, request->time_in_p, cid_type, cid, cid_len, subnet_match, &match, &domain, &iaddr); if (match.has_binding == TRUE) { has_binding = TRUE; } if (entry) { binding = dhcp_binding_permanent_e; lease_time_expiry = DHCP_INFINITE_TIME; } } if (entry == NULL) { boolean_t some_binding = FALSE; subnet_match_args_t match; bzero(&match, sizeof(match)); match.if_p = request->if_p; match.giaddr = rq->dp_giaddr; match.ciaddr = rq->dp_ciaddr; /* no permanent netinfo binding: check for a lease */ entry = PLCache_lookup_identifier(&S_leases.list, idstr, subnet_match, &match, &iaddr, &some_binding); if (some_binding == TRUE) { has_binding = TRUE; } if (entry) { subnet = [subnets entry:iaddr]; if (subnet == nil) { S_remove_host(&entry); my_log(LOG_INFO, "dhcpd: removing %s binding for %s", idstr, inet_ntoa(iaddr)); orphan = TRUE; entry = NULL; } else { domain = [subnets domain]; binding = dhcp_binding_temporary_e; lease_time_expiry = S_lease_time_expiry(&entry->pl); PLCache_make_head(&S_leases.list, entry); } } } if (entry) { subnet = [subnets entry:iaddr]; lease_from_subnet(subnet, &min_lease, &max_lease); hostname = ni_valforprop(&entry->pl, NIPROP_NAME); if (hostname) hostname = strdup(hostname); #if 0 bootfile = ni_valforprop(&entry->pl, NIPROP_BOOTFILE); if (bootfile) bootfile = strdup(bootfile); #endif 0 /* client is already bound on this subnet */ if (lease_time_expiry == DHCP_INFINITE_TIME) { /* permanent entry */ lease = DHCP_INFINITE_LEASE; } else { if (suggested_lease) { lease = dhcp_lease_ntoh(*suggested_lease); if (lease > max_lease) lease = max_lease; else if (lease < min_lease) lease = min_lease; } else if ((request->time_in_p->tv_sec + min_lease) >= lease_time_expiry) { /* expired lease: give it the default lease */ lease = min_lease; } else { /* give the host the remaining time on the lease */ lease = lease_time_expiry - request->time_in_p->tv_sec; } } } switch (msgtype) { case dhcp_msgtype_discover_e: { state = dhcp_cstate_init_e; { /* delete the pending host entry */ struct hosts * hp; hp = hostbyaddr(S_pending_hosts, cid_type, cid, cid_len); if (hp) hostfree(&S_pending_hosts, hp); } if (binding != dhcp_binding_none_e) { /* client is already bound on this subnet */ } else if (dhcp_allocate == FALSE) { /* NetBoot 1.0 enabled, but DHCP is not */ goto no_reply; } else { /* find an ip address */ /* allocate a new ip address */ subnet = acquire_ip(rq->dp_giaddr, request->if_p, request->time_in_p, &iaddr); if (subnet == nil) { if (DHCPLeases_reclaim(&S_leases, request->if_p, rq->dp_giaddr, request->time_in_p, &iaddr)) { subnet = [subnets entry:iaddr]; } if (subnet == nil) { if (debug) { printf("no ip addresses\n"); } goto no_reply; /* out of ip addresses */ } } lease_from_subnet(subnet, &min_lease, &max_lease); domain = [subnets domain]; if (suggested_lease) { lease = dhcp_lease_ntoh(*suggested_lease); if (lease > max_lease) lease = max_lease; else if (lease < min_lease) lease = min_lease; } else { lease = min_lease; } } { /* keep track of this offer in the pending hosts list */ struct hosts * hp; hp = hostadd(&S_pending_hosts, request->time_in_p, cid_type, cid, cid_len, &iaddr, hostname, NULL); if (hp == NULL) goto no_reply; hp->lease = lease; } /* * allow for drift between server/client clocks by offering * a lease shorter than the recorded value */ if (lease == DHCP_INFINITE_LEASE) lease = dhcp_lease_hton(lease); else lease = dhcp_lease_hton(lease_prorate(lease)); /* form a reply */ reply = make_dhcp_reply((struct dhcp *)txbuf, max_packet, if_inet_addr(request->if_p), reply_msgtype = dhcp_msgtype_offer_e, rq, &options); if (reply == NULL) goto no_reply; reply->dp_ciaddr.s_addr = 0; reply->dp_yiaddr = iaddr; if (dhcpoa_add(&options, dhcptag_lease_time_e, sizeof(lease), &lease) != dhcpoa_success_e) { my_log(LOG_INFO, "dhcpd: couldn't add lease time tag: %s", dhcpoa_err(&options)); goto no_reply; } break; } case dhcp_msgtype_request_e: { u_char * nak = NULL; int optlen; struct in_addr * req_ip; struct in_addr * server_id; server_id = (struct in_addr *) dhcpol_find(request->options_p, dhcptag_server_identifier_e, &optlen, NULL); req_ip = (struct in_addr *) dhcpol_find(request->options_p, dhcptag_requested_ip_address_e, &optlen, NULL); if (server_id) { /* SELECT */ struct hosts * hp = hostbyaddr(S_pending_hosts, cid_type, cid, cid_len); if (debug) printf("SELECT\n"); state = dhcp_cstate_select_e; if (server_id->s_addr != if_inet_addr(request->if_p).s_addr) { if (debug) printf("client selected %s\n", inet_ntoa(*server_id)); /* clean up */ if (hp) { hostfree(&S_pending_hosts, hp); } if (binding == dhcp_binding_temporary_e) { S_remove_host(&entry); binding = dhcp_binding_none_e; } goto no_reply; } if (entry == NULL && hp == NULL) { goto no_reply; } if (hp) { iaddr = hp->iaddr; if (hp->lease == DHCP_INFINITE_LEASE) lease_time_expiry = DHCP_INFINITE_LEASE; else { lease_time_expiry = hp->lease + request->time_in_p->tv_sec; } lease = hp->lease; if (hp->hostname) { if (hostname) { free(hostname); } hostname = strdup(hp->hostname); } } else { /* this case only happens if the client sends * a REQUEST without sending a DISCOVER first * but we have a binding */ /* iaddr, lease_time_expiry, hostname, lease * are all set above */ } if (req_ip == NULL || req_ip->s_addr != iaddr.s_addr) { if (req_ip == NULL) { my_log(LOG_INFO, "dhcpd: host %s sends SELECT without" " Requested IP option", idstr); } else { my_log(LOG_INFO, "dhcpd: host %s sends SELECT with wrong" " IP address %s, should be " IP_FORMAT, idstr, inet_ntoa(*req_ip), IP_LIST(&iaddr)); } use_broadcast = TRUE; reply = make_dhcp_nak((struct dhcp *)txbuf, max_packet, if_inet_addr(request->if_p), &reply_msgtype, "protocol error in SELECT state", rq, &options); if (reply) goto reply; goto no_reply; } if (hostname_opt && hostname_opt_len > 0) { if (binding == dhcp_binding_temporary_e) { if (hostname) free(hostname); hostname = S_get_hostname(hostname_opt, hostname_opt_len, iaddr); ni_set_prop(&entry->pl, NIPROP_NAME, hostname, &modified); } else if (hostname == NULL) { hostname = S_get_hostname(hostname_opt, hostname_opt_len, iaddr); } } if (binding != dhcp_binding_none_e) { if (binding == dhcp_binding_temporary_e) { S_set_lease(&entry->pl, lease_time_expiry, &modified); } } else { /* create a new host entry */ id subnet; subnet = [subnets entry:iaddr]; domain = [subnet domain]; if (subnet == nil || S_create_host(idstr, hwstr, iaddr, hostname, lease_time_expiry) == FALSE) { reply = make_dhcp_nak((struct dhcp *)txbuf, max_packet, if_inet_addr(request->if_p), &reply_msgtype, "unexpected server failure", rq, &options); if (reply) goto reply; goto no_reply; } } } /* select */ else /* init-reboot/renew/rebind */ { if (req_ip) { /* init-reboot */ if (debug) printf("init-reboot\n"); state = dhcp_cstate_init_reboot_e; if (binding == dhcp_binding_none_e) { if (orphan == FALSE) { my_log(LOG_DEBUG, "dhcpd: INIT-REBOOT host " "%s binding for %s with another server", idstr, inet_ntoa(*req_ip)); goto no_reply; } nak = "requested address no longer available"; use_broadcast = TRUE; goto send_nak; } if (req_ip->s_addr != iaddr.s_addr) { nak = "requested address incorrect"; use_broadcast = TRUE; goto send_nak; } } /* init-reboot */ else if (rq->dp_ciaddr.s_addr) { /* renew/rebind */ if (debug) { if (request->dstaddr_p == NULL || ntohl(request->dstaddr_p->s_addr) == INADDR_BROADCAST) printf("rebind\n"); else printf("renew\n"); } if (binding == dhcp_binding_none_e) { if (orphan == FALSE) { if (debug) { if (has_binding) printf("Client binding is not applicable\n"); else printf("No binding for client\n"); } goto no_reply; } nak = "requested address no longer available"; use_broadcast = TRUE; goto send_nak; } if (request->dstaddr_p == NULL || ntohl(request->dstaddr_p->s_addr) == INADDR_BROADCAST || rq->dp_giaddr.s_addr) { /* REBIND */ state = dhcp_cstate_rebind_e; if (rq->dp_ciaddr.s_addr != iaddr.s_addr) { if (debug) printf("Incorrect ciaddr " IP_FORMAT " should be " IP_FORMAT "\n", IP_LIST(&rq->dp_ciaddr), IP_LIST(&iaddr)); goto no_reply; } } else { /* RENEW */ state = dhcp_cstate_renew_e; if (rq->dp_ciaddr.s_addr != iaddr.s_addr) { my_log(LOG_INFO, "dhcpd: client ciaddr=%s should use " IP_FORMAT, inet_ntoa(rq->dp_ciaddr), IP_LIST(&iaddr)); iaddr = rq->dp_ciaddr; /* trust it anyways */ } } } /* renew/rebind */ else { my_log(LOG_DEBUG, "dhcpd: host %s in unknown state", idstr); goto no_reply; } if (binding == dhcp_binding_permanent_e) { lease = DHCP_INFINITE_LEASE; } else { if (hostname_opt && hostname_opt_len > 0) { if (hostname) { free(hostname); } hostname = S_get_hostname(hostname_opt, hostname_opt_len, iaddr); ni_set_prop(&entry->pl, NIPROP_NAME, hostname, &modified); } if (suggested_lease) { lease = dhcp_lease_ntoh(*suggested_lease); if (lease > max_lease) lease = max_lease; else if (lease < min_lease) lease = min_lease; } else if (S_extend_leases) { /* automatically extend the lease */ lease = min_lease; my_log(LOG_DEBUG, "dhcpd: %s lease extended to %s client", inet_ntoa(iaddr), dhcp_cstate_str(state)); } else { if (request->time_in_p->tv_sec >= lease_time_expiry) { /* send a nak */ nak = "lease expired"; goto send_nak; } /* give the host the remaining time on the lease */ lease = lease_time_expiry - request->time_in_p->tv_sec; } if (lease == DHCP_INFINITE_LEASE) { lease_time_expiry = DHCP_INFINITE_TIME; } else { lease_time_expiry = lease + request->time_in_p->tv_sec; } S_set_lease(&entry->pl, lease_time_expiry, &modified); } } /* init-reboot/renew/rebind */ send_nak: if (nak) { reply = make_dhcp_nak((struct dhcp *)txbuf, max_packet, if_inet_addr(request->if_p), &reply_msgtype, nak, rq, &options); if (reply) goto reply; goto no_reply; } /* * allow for drift between server/client clocks by offering * a lease shorter than the recorded value */ if (lease == DHCP_INFINITE_LEASE) lease = dhcp_lease_hton(lease); else lease = dhcp_lease_hton(lease_prorate(lease)); reply = make_dhcp_reply((struct dhcp *)txbuf, max_packet, if_inet_addr(request->if_p), reply_msgtype = dhcp_msgtype_ack_e, rq, &options); reply->dp_yiaddr = iaddr; if (dhcpoa_add(&options, dhcptag_lease_time_e, sizeof(lease), &lease) != dhcpoa_success_e) { my_log(LOG_INFO, "dhcpd: couldn't add lease time tag: %s", dhcpoa_err(&options)); goto no_reply; } break; } case dhcp_msgtype_decline_e: { int optlen; struct in_addr * req_ip; struct in_addr * server_id; server_id = (struct in_addr *) dhcpol_find(request->options_p, dhcptag_server_identifier_e, &optlen, NULL); req_ip = (struct in_addr *) dhcpol_find(request->options_p, dhcptag_requested_ip_address_e, &optlen, NULL); if (server_id == NULL || req_ip == NULL) { goto no_reply; } if (server_id->s_addr != if_inet_addr(request->if_p).s_addr) { my_log(LOG_DEBUG, "dhcpd: host %s " "declines IP %s from server " IP_FORMAT, idstr, inet_ntoa(*req_ip), IP_LIST(server_id)); goto no_reply; } if (binding == dhcp_binding_temporary_e && iaddr.s_addr == req_ip->s_addr) { ni_delete_prop(&entry->pl, NIPROP_IDENTIFIER, &modified); S_set_lease(&entry->pl, request->time_in_p->tv_sec + DHCP_DECLINE_WAIT_SECS, &modified); ni_set_prop(&entry->pl, NIPROP_DHCP_DECLINED, idstr, &modified); my_log(LOG_INFO, "dhcpd: IP %s declined by %s", inet_ntoa(iaddr), idstr); if (debug) { printf("marking host %s as declined\n", inet_ntoa(iaddr)); } } break; } case dhcp_msgtype_release_e: { if (binding == dhcp_binding_temporary_e) { if (debug) { printf("%s released by client, setting expiration to now\n", inet_ntoa(iaddr)); } /* set the lease expiration time to now */ S_set_lease(&entry->pl, request->time_in_p->tv_sec, &modified); } break; } case dhcp_msgtype_inform_e: { iaddr = rq->dp_ciaddr; reply = make_dhcp_reply((struct dhcp *)txbuf, max_packet, if_inet_addr(request->if_p), reply_msgtype = dhcp_msgtype_ack_e, rq, &options); if (reply) goto reply; goto no_reply; break; } default: { if (debug) printf("unknown message ignored\n"); } break; } reply: if (debug) printf("state=%s\n", dhcp_cstate_str(state)); if (binding == dhcp_binding_temporary_e && modified) { if (S_commit_mods() == FALSE) goto no_reply; } if (reply) { if (reply_msgtype == dhcp_msgtype_ack_e || reply_msgtype == dhcp_msgtype_offer_e) { int num_added; int num_params; u_char * params; params = (char *) dhcpol_find(request->options_p, dhcptag_parameter_request_list_e, &num_params, NULL); bzero(reply->dp_file, sizeof(reply->dp_file)); reply->dp_siaddr = if_inet_addr(request->if_p); strcpy(reply->dp_sname, server_name); /* add the client-specified parameters */ if (params) num_added = add_subnet_options(domain, hostname, iaddr, request->if_p, &options, params, num_params); if (bootfile) { #if 0 char file[PATH_MAX]; #endif 0 if (bootp_add_bootfile(rq->dp_file, hostname, bootfile, reply->dp_file) == FALSE) goto no_reply; #if 0 if (dhcpoa_add(&options, dhcptag_bootfile_name_e strlen(file), file) != dhcpoa_success_e) { my_log(LOG_INFO, "couldn't add bootfile name option: %s", dhcpoa_err(&options)); goto no_reply; } #endif 0 } /* terminate the options */ if (dhcpoa_add(&options, dhcptag_end_e, 0, NULL) != dhcpoa_success_e) { my_log(LOG_INFO, "couldn't add end tag: %s", dhcpoa_err(&options)); goto no_reply; } } { int size = sizeof(struct dhcp) + sizeof(rfc_magic) + dhcpoa_used(&options); if (size < sizeof(struct bootp)) { /* pad out to BOOTP-sized packet */ size = sizeof(struct bootp); } if (debug) { printf("\nSending: DHCP %s (size %d)\n", dhcp_msgtype_names(reply_msgtype), size); } if (sendreply(request->if_p, (struct bootp *)reply, size, use_broadcast, &iaddr)) { my_log(LOG_INFO, "%s sent %s %s pktsize %d", dhcp_msgtype_names(reply_msgtype), (hostname != NULL) ? hostname : (char *)"", inet_ntoa(iaddr), size); } } } no_reply: if (bootfile) free(bootfile); if (hostname) free(hostname); if (idstr) free(idstr); if (hwstr && hwstr != idstr) free(hwstr); return; }