diff options
Diffstat (limited to 'src/rfc2131.c')
-rw-r--r-- | src/rfc2131.c | 930 |
1 files changed, 480 insertions, 450 deletions
diff --git a/src/rfc2131.c b/src/rfc2131.c index 3dbfd79..9f69ed5 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2011 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,16 +18,13 @@ #ifdef HAVE_DHCP -#define have_config(config, mask) ((config) && ((config)->flags & (mask))) #define option_len(opt) ((int)(((unsigned char *)(opt))[1])) #define option_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2u+(unsigned int)(i)])) #ifdef HAVE_SCRIPT -static void add_extradata_data(struct dhcp_lease *lease, unsigned char *data, size_t len, int delim); static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt); #endif -static int match_bytes(struct dhcp_opt *o, unsigned char *p, int len); static int sanitise(unsigned char *opt, char *buf); static struct in_addr server_id(struct dhcp_context *context, struct in_addr override, struct in_addr fallback); static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt); @@ -35,39 +32,41 @@ static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, in static void option_put_string(struct dhcp_packet *mess, unsigned char *end, int opt, char *string, int null_term); static struct in_addr option_addr(unsigned char *opt); -static struct in_addr option_addr_arr(unsigned char *opt, int offset); static unsigned int option_uint(unsigned char *opt, int i, int size); static void log_packet(char *type, void *addr, unsigned char *ext_mac, - int mac_len, char *interface, char *string, u32 xid); + int mac_len, char *interface, char *string, char *err, u32 xid); static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize); static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int minsize); -static size_t dhcp_packet_size(struct dhcp_packet *mess, struct dhcp_netid *netid, - unsigned char *agent_id, unsigned char *real_end); +static size_t dhcp_packet_size(struct dhcp_packet *mess, unsigned char *agent_id, unsigned char *real_end); static void clear_packet(struct dhcp_packet *mess, unsigned char *end); +static int in_list(unsigned char *list, int opt); static void do_options(struct dhcp_context *context, struct dhcp_packet *mess, unsigned char *real_end, unsigned char *req_options, char *hostname, - char *domain, char *config_domain, + char *config_domain, struct dhcp_netid *netid, - struct in_addr subnet_addr, + struct in_addr subnet_addr, unsigned char fqdn_flags, int null_term, int pxearch, unsigned char *uuid, - int vendor_class_len); + int vendor_class_len, + time_t now, + unsigned int lease_time, + unsigned short fuzz); static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt); static int do_encap_opts(struct dhcp_opt *opts, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term); static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid); static int prune_vendor_opts(struct dhcp_netid *netid); -static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local); +static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now); struct dhcp_boot *find_boot(struct dhcp_netid *netid); size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, - size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe) + size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe, struct in_addr fallback) { unsigned char *opt, *clid = NULL; struct dhcp_lease *ltmp, *lease = NULL; @@ -85,7 +84,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, unsigned int time; struct dhcp_config *config; struct dhcp_netid *netid, *tagif_netid; - struct in_addr subnet_addr, fallback, override; + struct in_addr subnet_addr, override; unsigned short fuzz = 0; unsigned int mess_type = 0; unsigned char fqdn_flags = 0; @@ -95,7 +94,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, struct dhcp_netid known_id, iface_id, cpewan_id; struct dhcp_opt *o; unsigned char pxe_uuid[17]; - unsigned char *oui = NULL, *serial = NULL, *class = NULL; + unsigned char *oui = NULL, *serial = NULL; +#ifdef HAVE_SCRIPT + unsigned char *class = NULL; +#endif subnet_addr.s_addr = override.s_addr = 0; @@ -159,8 +161,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, unsigned char *y = option_ptr(opt, offset + elen + 5); oui = option_find1(x, y, 1, 1); serial = option_find1(x, y, 2, 1); - class = option_find1(x, y, 3, 1); - +#ifdef HAVE_SCRIPT + class = option_find1(x, y, 3, 1); +#endif /* If TR069-id is present set the tag "cpewan-id" to facilitate echoing the gateway id back. Note that the device class is optional */ if (oui && serial) @@ -297,17 +300,43 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (!context_new) for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next) - if (context_tmp->netmask.s_addr && - is_same_net(addr, context_tmp->start, context_tmp->netmask) && - is_same_net(addr, context_tmp->end, context_tmp->netmask)) - { - context_tmp->current = context_new; - context_new = context_tmp; - } + { + struct in_addr netmask = context_tmp->netmask; + + /* guess the netmask for relayed networks */ + if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0) + { + if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr))) + netmask.s_addr = htonl(0xff000000); + else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr))) + netmask.s_addr = htonl(0xffff0000); + else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr))) + netmask.s_addr = htonl(0xffffff00); + } + + /* This section fills in context mainly when a client which is on a remote (relayed) + network renews a lease without using the relay, after dnsmasq has restarted. */ + if (netmask.s_addr != 0 && + is_same_net(addr, context_tmp->start, netmask) && + is_same_net(addr, context_tmp->end, netmask)) + { + context_tmp->netmask = netmask; + if (context_tmp->local.s_addr == 0) + context_tmp->local = fallback; + if (context_tmp->router.s_addr == 0) + context_tmp->router = mess->giaddr; + + /* fill in missing broadcast addresses for relayed ranges */ + if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 ) + context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr; + + context_tmp->current = context_new; + context_new = context_tmp; + } + } if (context_new || force) - context = context_new; - + context = context_new; } if (!context) @@ -318,9 +347,6 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, return 0; } - /* keep _a_ local address available. */ - fallback = context->local; - if (option_bool(OPT_LOG_OPTS)) { struct dhcp_context *context_tmp; @@ -335,6 +361,117 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, ntohl(mess->xid), daemon->namebuff, inet_ntoa(context_tmp->end)); } } + + /* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match. + Otherwise assume the option is an array, and look for a matching element. + If no data given, existance of the option is enough. This code handles + rfc3925 V-I classes too. */ + for (o = daemon->dhcp_match; o; o = o->next) + { + unsigned int len, elen, match = 0; + size_t offset, o2; + + if (o->flags & DHOPT_RFC3925) + { + if (!(opt = option_find(mess, sz, OPTION_VENDOR_IDENT, 5))) + continue; + + for (offset = 0; offset < (option_len(opt) - 5u); offset += len + 5) + { + len = option_uint(opt, offset + 4 , 1); + /* Need to take care that bad data can't run us off the end of the packet */ + if ((offset + len + 5 <= (option_len(opt))) && + (option_uint(opt, offset, 4) == (unsigned int)o->u.encap)) + for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1) + { + elen = option_uint(opt, o2, 1); + if ((o2 + elen + 1 <= option_len(opt)) && + (match = match_bytes(o, option_ptr(opt, o2 + 1), elen))) + break; + } + if (match) + break; + } + } + else + { + if (!(opt = option_find(mess, sz, o->opt, 1))) + continue; + + match = match_bytes(o, option_ptr(opt, 0), option_len(opt)); + } + + if (match) + { + o->netid->next = netid; + netid = o->netid; + } + } + + /* user-class options are, according to RFC3004, supposed to contain + a set of counted strings. Here we check that this is so (by seeing + if the counts are consistent with the overall option length) and if + so zero the counts so that we don't get spurious matches between + the vendor string and the counts. If the lengths don't add up, we + assume that the option is a single string and non RFC3004 compliant + and just do the substring match. dhclient provides these broken options. + The code, later, which sends user-class data to the lease-change script + relies on the transformation done here. + */ + + if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1))) + { + unsigned char *ucp = option_ptr(opt, 0); + int tmp, j; + for (j = 0; j < option_len(opt); j += ucp[j] + 1); + if (j == option_len(opt)) + for (j = 0; j < option_len(opt); j = tmp) + { + tmp = j + ucp[j] + 1; + ucp[j] = 0; + } + } + + for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next) + { + int mopt; + + if (vendor->match_type == MATCH_VENDOR) + mopt = OPTION_VENDOR_ID; + else if (vendor->match_type == MATCH_USER) + mopt = OPTION_USER_CLASS; + else + continue; + + if ((opt = option_find(mess, sz, mopt, 1))) + { + int i; + for (i = 0; i <= (option_len(opt) - vendor->len); i++) + if (memcmp(vendor->data, option_ptr(opt, i), vendor->len) == 0) + { + vendor->netid.next = netid; + netid = &vendor->netid; + break; + } + } + } + + /* mark vendor-encapsulated options which match the client-supplied vendor class, + save client-supplied vendor class */ + if ((opt = option_find(mess, sz, OPTION_VENDOR_ID, 1))) + { + memcpy(daemon->dhcp_buff3, option_ptr(opt, 0), option_len(opt)); + vendor_class_len = option_len(opt); + } + match_vendor_opts(opt, daemon->dhcp_opts); + + if (option_bool(OPT_LOG_OPTS)) + { + if (sanitise(opt, daemon->namebuff)) + my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %s"), ntohl(mess->xid), daemon->namebuff); + if (sanitise(option_find(mess, sz, OPTION_USER_CLASS, 1), daemon->namebuff)) + my_syslog(MS_DHCP | LOG_INFO, _("%u user class: %s"), ntohl(mess->xid), daemon->namebuff); + } mess->op = BOOTREPLY; @@ -441,9 +578,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, else if (context->netid.net) { context->netid.next = netid; - netid = &context->netid; - tagif_netid = run_tag_if(netid); + tagif_netid = run_tag_if(&context->netid); } + + log_tags(tagif_netid, ntohl(mess->xid)); if (!message && !nailed) { @@ -456,34 +594,34 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (!message && !lease && - (!(lease = lease_allocate(mess->yiaddr)))) + (!(lease = lease4_allocate(mess->yiaddr)))) message = _("no leases left"); if (!message) { logaddr = &mess->yiaddr; - lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0); + lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0, now, 1); if (hostname) - lease_set_hostname(lease, hostname, 1); + lease_set_hostname(lease, hostname, 1, get_domain(lease->addr), domain); /* infinite lease unless nailed in dhcp-host line. */ lease_set_expires(lease, have_config(config, CONFIG_TIME) ? config->lease_time : 0xffffffff, now); - lease_set_interface(lease, int_index); + lease_set_interface(lease, int_index, now); clear_packet(mess, end); do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr), - domain, tagif_netid, subnet_addr, 0, 0, 0, NULL, 0); + netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0); } } - log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, message, mess->xid); + log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, NULL, message, mess->xid); - return message ? 0 : dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + return message ? 0 : dhcp_packet_size(mess, agent_id, real_end); } - if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 4))) + if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 3))) { /* http://tools.ietf.org/wg/dhc/draft-ietf-dhc-fqdn-option/draft-ietf-dhc-fqdn-option-10.txt */ int len = option_len(opt); @@ -495,15 +633,25 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, op += 3; pp = op; - /* Always force update, since the client has no way to do it itself. */ - if (!(fqdn_flags & 0x01)) - fqdn_flags |= 0x02; - - fqdn_flags &= ~0x08; - fqdn_flags |= 0x01; + /* NB, the following always sets at least one bit */ + if (option_bool(OPT_FQDN_UPDATE)) + { + if (fqdn_flags & 0x01) + { + fqdn_flags |= 0x02; /* set O */ + fqdn_flags &= ~0x01; /* clear S */ + } + fqdn_flags |= 0x08; /* set N */ + } + else + { + if (!(fqdn_flags & 0x01)) + fqdn_flags |= 0x03; /* set S and O */ + fqdn_flags &= ~0x08; /* clear N */ + } if (fqdn_flags & 0x04) - while (*op != 0 && ((op + (*op) + 1) - pp) < len) + while (*op != 0 && ((op + (*op)) - pp) < len) { memcpy(pq, op+1, *op); pq += *op; @@ -591,119 +739,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } } - /* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match. - Otherwise assume the option is an array, and look for a matching element. - If no data given, existance of the option is enough. This code handles - rfc3925 V-I classes too. */ - for (o = daemon->dhcp_match; o; o = o->next) - { - unsigned int len, elen, match = 0; - size_t offset, o2; - - if (o->flags & DHOPT_RFC3925) - { - if (!(opt = option_find(mess, sz, OPTION_VENDOR_IDENT, 5))) - continue; - - for (offset = 0; offset < (option_len(opt) - 5u); offset += len + 5) - { - len = option_uint(opt, offset + 4 , 1); - /* Need to take care that bad data can't run us off the end of the packet */ - if ((offset + len + 5 <= (option_len(opt))) && - (option_uint(opt, offset, 4) == (unsigned int)o->u.encap)) - for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1) - { - elen = option_uint(opt, o2, 1); - if ((o2 + elen + 1 <= option_len(opt)) && - (match = match_bytes(o, option_ptr(opt, o2 + 1), elen))) - break; - } - if (match) - break; - } - } - else - { - if (!(opt = option_find(mess, sz, o->opt, 1))) - continue; - - match = match_bytes(o, option_ptr(opt, 0), option_len(opt)); - } - - if (match) - { - o->netid->next = netid; - netid = o->netid; - } - } - - /* user-class options are, according to RFC3004, supposed to contain - a set of counted strings. Here we check that this is so (by seeing - if the counts are consistent with the overall option length) and if - so zero the counts so that we don't get spurious matches between - the vendor string and the counts. If the lengths don't add up, we - assume that the option is a single string and non RFC3004 compliant - and just do the substring match. dhclient provides these broken options. - The code, later, which sends user-class data to the lease-change script - relies on the transformation done here. - */ - - if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1))) - { - unsigned char *ucp = option_ptr(opt, 0); - int tmp, j; - for (j = 0; j < option_len(opt); j += ucp[j] + 1); - if (j == option_len(opt)) - for (j = 0; j < option_len(opt); j = tmp) - { - tmp = j + ucp[j] + 1; - ucp[j] = 0; - } - } - - for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next) - { - int mopt; - - if (vendor->match_type == MATCH_VENDOR) - mopt = OPTION_VENDOR_ID; - else if (vendor->match_type == MATCH_USER) - mopt = OPTION_USER_CLASS; - else - continue; - - if ((opt = option_find(mess, sz, mopt, 1))) - { - int i; - for (i = 0; i <= (option_len(opt) - vendor->len); i++) - if (memcmp(vendor->data, option_ptr(opt, i), vendor->len) == 0) - { - vendor->netid.next = netid; - netid = &vendor->netid; - break; - } - } - } - - /* mark vendor-encapsulated options which match the client-supplied vendor class, - save client-supplied vendor class */ - if ((opt = option_find(mess, sz, OPTION_VENDOR_ID, 1))) - { - memcpy(daemon->dhcp_buff3, option_ptr(opt, 0), option_len(opt)); - vendor_class_len = option_len(opt); - } - match_vendor_opts(opt, daemon->dhcp_opts); - - if (option_bool(OPT_LOG_OPTS)) - { - if (sanitise(opt, daemon->namebuff)) - my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %s"), ntohl(mess->xid), daemon->namebuff); - if (sanitise(option_find(mess, sz, OPTION_USER_CLASS, 1), daemon->namebuff)) - my_syslog(MS_DHCP | LOG_INFO, _("%u user class: %s"), ntohl(mess->xid), daemon->namebuff); - } - tagif_netid = run_tag_if(netid); - + /* if all the netids in the ignore list are present, ignore this client */ for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next) if (match_netid(id_list->list, tagif_netid, 0)) @@ -768,14 +805,21 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (service->type == type) break; - if (!service || !service->basename) - return 0; + for (; context; context = context->current) + if (match_netid(context->filter, tagif_netid, 1) && + is_same_net(mess->ciaddr, context->start, context->netmask)) + break; + if (!service || !service->basename || !context) + return 0; + clear_packet(mess, end); mess->yiaddr = mess->ciaddr; mess->ciaddr.s_addr = 0; - if (service->server.s_addr != 0) + if (service->sname) + mess->siaddr = a_record_from_hosts(service->sname, now); + else if (service->server.s_addr != 0) mess->siaddr = service->server; else mess->siaddr = context->local; @@ -794,8 +838,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, opt71.next = daemon->dhcp_opts; do_encap_opts(&opt71, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); - log_packet("PXE", &mess->yiaddr, emac, emac_len, iface_name, (char *)mess->file, mess->xid); - return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + log_packet("PXE", &mess->yiaddr, emac, emac_len, iface_name, (char *)mess->file, NULL, mess->xid); + log_tags(tagif_netid, ntohl(mess->xid)); + return dhcp_packet_size(mess, agent_id, real_end); } if ((opt = option_find(mess, sz, OPTION_ARCH, 2))) @@ -814,8 +859,16 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (tmp) { - struct dhcp_boot *boot = find_boot(tagif_netid); - + struct dhcp_boot *boot; + + if (tmp->netid.net) + { + tmp->netid.next = netid; + tagif_netid = run_tag_if(&tmp->netid); + } + + boot = find_boot(tagif_netid); + mess->yiaddr.s_addr = 0; if (mess_type == DHCPDISCOVER || mess->ciaddr.s_addr == 0) { @@ -829,8 +882,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, and set discovery_control = 8 */ if (boot) { - if (boot->next_server.s_addr) + if (boot->next_server.s_addr) mess->siaddr = boot->next_server; + else if (boot->tftp_sname) + mess->siaddr = a_record_from_hosts(boot->tftp_sname, now); if (boot->file) strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); @@ -838,13 +893,14 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, option_put(mess, end, OPTION_MESSAGE_TYPE, 1, mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK); - option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr)); + option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr)); pxe_misc(mess, end, uuid); prune_vendor_opts(tagif_netid); - do_encap_opts(pxe_opts(pxearch, tagif_netid, context->local), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); + do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); - log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", mess->xid); - return ignore ? 0 : dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid); + log_tags(tagif_netid, ntohl(mess->xid)); + return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end); } } } @@ -874,7 +930,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ))) return 0; - log_packet("DHCPDECLINE", option_ptr(opt, 0), emac, emac_len, iface_name, daemon->dhcp_buff, mess->xid); + log_packet("DHCPDECLINE", option_ptr(opt, 0), emac, emac_len, iface_name, NULL, daemon->dhcp_buff, mess->xid); if (lease && lease->addr.s_addr == option_addr(opt).s_addr) lease_prune(lease, now); @@ -906,13 +962,15 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, else message = _("unknown lease"); - log_packet("DHCPRELEASE", &mess->ciaddr, emac, emac_len, iface_name, message, mess->xid); + log_packet("DHCPRELEASE", &mess->ciaddr, emac, emac_len, iface_name, NULL, message, mess->xid); return 0; case DHCPDISCOVER: if (ignore || have_config(config, CONFIG_DISABLE)) { + if (option_bool(OPT_QUIET_DHCP)) + return 0; message = _("ignored"); opt = NULL; } @@ -970,35 +1028,31 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, message = _("no address available"); } - log_packet("DHCPDISCOVER", opt ? option_ptr(opt, 0) : NULL, emac, emac_len, iface_name, message, mess->xid); + log_packet("DHCPDISCOVER", opt ? option_ptr(opt, 0) : NULL, emac, emac_len, iface_name, NULL, message, mess->xid); if (message || !(context = narrow_context(context, mess->yiaddr, tagif_netid))) return 0; - log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, mess->xid); - if (context->netid.net) { context->netid.next = netid; - netid = &context->netid; - tagif_netid = run_tag_if(netid); + tagif_netid = run_tag_if(&context->netid); } - + + log_tags(tagif_netid, ntohl(mess->xid)); + + log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid); + time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4)); clear_packet(mess, end); option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPOFFER); option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); option_put(mess, end, OPTION_LEASE_TIME, 4, time); /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */ - if (time != 0xffffffff) - { - option_put(mess, end, OPTION_T1, 4, (time/2)); - option_put(mess, end, OPTION_T2, 4, (time*7)/8); - } do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr), - domain, tagif_netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz); - return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + return dhcp_packet_size(mess, agent_id, real_end); case DHCPREQUEST: if (ignore || have_config(config, CONFIG_DISABLE)) @@ -1029,12 +1083,30 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (!context) { - /* In auth mode, a REQUEST sent to the wrong server - should be faulted, so that the client establishes - communication with us, otherwise, silently ignore. */ - if (!option_bool(OPT_AUTHORITATIVE)) - return 0; - message = _("wrong server-ID"); + /* Handle very strange configs where clients have more than one route to the server. + If a clients idea of its server-id matches any of our DHCP interfaces, we let it pass. + Have to set override to make sure we echo back the correct server-id */ + struct irec *intr; + + enumerate_interfaces(0); + + for (intr = daemon->interfaces; intr; intr = intr->next) + if (intr->addr.sa.sa_family == AF_INET && + intr->addr.in.sin_addr.s_addr == option_addr(opt).s_addr && + intr->tftp_ok) + break; + + if (intr) + override = intr->addr.in.sin_addr; + else + { + /* In auth mode, a REQUEST sent to the wrong server + should be faulted, so that the client establishes + communication with us, otherwise, silently ignore. */ + if (!option_bool(OPT_AUTHORITATIVE)) + return 0; + message = _("wrong server-ID"); + } } } @@ -1080,7 +1152,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, mess->yiaddr = mess->ciaddr; } - log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name, NULL, mess->xid); + log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid); if (!message) { @@ -1142,7 +1214,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, else if (!lease) { - if ((lease = lease_allocate(mess->yiaddr))) + if ((lease = lease4_allocate(mess->yiaddr))) do_classes = 1; else message = _("no leases left"); @@ -1152,7 +1224,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (message) { - log_packet("DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, message, mess->xid); + log_packet("DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, NULL, message, mess->xid); mess->yiaddr.s_addr = 0; clear_packet(mess, end); @@ -1173,47 +1245,74 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (context->netid.net) { context->netid.next = netid; - netid = &context->netid; - tagif_netid = run_tag_if(netid); + tagif_netid = run_tag_if( &context->netid); } + + log_tags(tagif_netid, ntohl(mess->xid)); -#ifdef HAVE_SCRIPT - if (do_classes && daemon->lease_change_command) + if (do_classes) { - struct dhcp_netid *n; - - if (mess->giaddr.s_addr) - lease->giaddr = mess->giaddr; - - lease->changed = 1; - free(lease->extradata); - lease->extradata = NULL; - lease->extradata_size = lease->extradata_len = 0; - - add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1)); - add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1)); - add_extradata_opt(lease, oui); - add_extradata_opt(lease, serial); - add_extradata_opt(lease, class); - - /* space-concat tag set */ - if (!tagif_netid) - add_extradata_opt(lease, NULL); - else - for (n = tagif_netid; n; n = n->next) - add_extradata_data(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0); - - if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1))) + /* pick up INIT-REBOOT events. */ + lease->flags |= LEASE_CHANGED; + +#ifdef HAVE_SCRIPT + if (daemon->lease_change_command) { - int len = option_len(opt); - unsigned char *ucp = option_ptr(opt, 0); - /* If the user-class option started as counted strings, the first byte will be zero. */ - if (len != 0 && ucp[0] == 0) - ucp++, len--; - add_extradata_data(lease, ucp, len, 0); + struct dhcp_netid *n; + + if (mess->giaddr.s_addr) + lease->giaddr = mess->giaddr; + + free(lease->extradata); + lease->extradata = NULL; + lease->extradata_size = lease->extradata_len = 0; + + add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1)); + add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1)); + add_extradata_opt(lease, oui); + add_extradata_opt(lease, serial); + add_extradata_opt(lease, class); + + if ((opt = option_find(mess, sz, OPTION_AGENT_ID, 1))) + { + add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_CIRCUIT_ID, 1)); + add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SUBSCR_ID, 1)); + add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_REMOTE_ID, 1)); + } + else + { + add_extradata_opt(lease, NULL); + add_extradata_opt(lease, NULL); + add_extradata_opt(lease, NULL); + } + + /* space-concat tag set */ + if (!tagif_netid) + add_extradata_opt(lease, NULL); + else + for (n = tagif_netid; n; n = n->next) + { + struct dhcp_netid *n1; + /* kill dupes */ + for (n1 = n->next; n1; n1 = n1->next) + if (strcmp(n->net, n1->net) == 0) + break; + if (!n1) + lease_add_extradata(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0); + } + + if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1))) + { + int len = option_len(opt); + unsigned char *ucp = option_ptr(opt, 0); + /* If the user-class option started as counted strings, the first byte will be zero. */ + if (len != 0 && ucp[0] == 0) + ucp++, len--; + lease_add_extradata(lease, ucp, len, 0); + } } - } #endif + } if (!hostname_auth && (client_hostname = host_from_dns(mess->yiaddr))) { @@ -1223,7 +1322,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4)); - lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len); + lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len, now, do_classes); /* if all the netids in the ignore_name list are present, ignore client-supplied name */ if (!hostname_auth) @@ -1254,41 +1353,33 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } if (hostname) - lease_set_hostname(lease, hostname, hostname_auth); + lease_set_hostname(lease, hostname, hostname_auth, get_domain(lease->addr), domain); lease_set_expires(lease, time, now); - lease_set_interface(lease, int_index); + lease_set_interface(lease, int_index, now); if (override.s_addr != 0) lease->override = override; else override = lease->override; - log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, mess->xid); - emit_dbus_signal(ACTION_CONNECT, lease, hostname); + log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid); clear_packet(mess, end); option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK); option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); option_put(mess, end, OPTION_LEASE_TIME, 4, time); - if (time != 0xffffffff) - { - while (fuzz > (time/16)) - fuzz = fuzz/2; - option_put(mess, end, OPTION_T1, 4, (time/2) - fuzz); - option_put(mess, end, OPTION_T2, 4, ((time/8)*7) - fuzz); - } do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr), - domain, tagif_netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz); } - return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + return dhcp_packet_size(mess, agent_id, real_end); case DHCPINFORM: if (ignore || have_config(config, CONFIG_DISABLE)) message = _("ignored"); - log_packet("DHCPINFORM", &mess->ciaddr, emac, emac_len, iface_name, message, mess->xid); + log_packet("DHCPINFORM", &mess->ciaddr, emac, emac_len, iface_name, message, NULL, mess->xid); if (message || mess->ciaddr.s_addr == 0) return 0; @@ -1303,20 +1394,22 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, lease->hostname) hostname = lease->hostname; - if (!hostname && (hostname = host_from_dns(mess->ciaddr))) - domain = get_domain(mess->ciaddr); - - log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, mess->xid); + if (!hostname) + hostname = host_from_dns(mess->ciaddr); if (context && context->netid.net) { context->netid.next = netid; - netid = &context->netid; - tagif_netid = run_tag_if(netid); + tagif_netid = run_tag_if(&context->netid); } + + log_tags(tagif_netid, ntohl(mess->xid)); + + log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid); if (lease) { + lease_set_interface(lease, int_index, now); if (override.s_addr != 0) lease->override = override; else @@ -1326,58 +1419,31 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, clear_packet(mess, end); option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK); option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); - - if (lease) + + /* RFC 2131 says that DHCPINFORM shouldn't include lease-time parameters, but + we supply a utility which makes DHCPINFORM requests to get this information. + Only include lease time if OPTION_LEASE_TIME is in the parameter request list, + which won't be true for ordinary clients, but will be true for the + dhcp_lease_time utility. */ + if (lease && in_list(req_options, OPTION_LEASE_TIME)) { if (lease->expires == 0) time = 0xffffffff; else time = (unsigned int)difftime(lease->expires, now); option_put(mess, end, OPTION_LEASE_TIME, 4, time); - lease_set_interface(lease, int_index); } do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr), - domain, tagif_netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0); *is_inform = 1; /* handle reply differently */ - return dhcp_packet_size(mess, tagif_netid, agent_id, real_end); + return dhcp_packet_size(mess, agent_id, real_end); } return 0; } -static int match_bytes(struct dhcp_opt *o, unsigned char *p, int len) -{ - int i; - - if (o->len > len) - return 0; - - if (o->len == 0) - return 1; - - if (o->flags & DHOPT_HEX) - { - if (memcmp_masked(o->val, p, o->len, o->u.wildcard_mask)) - return 1; - } - else - for (i = 0; i <= (len - o->len); ) - { - if (memcmp(o->val, p + i, o->len) == 0) - return 1; - - if (o->flags & DHOPT_STRING) - i++; - else - i += o->len; - } - - return 0; -} - - /* find a good value to use as MAC address for logging and address-allocation hashing. This is normally just the chaddr field from the DHCP packet, but eg Firewire will have hlen == 0 and use the client-id instead. @@ -1433,7 +1499,7 @@ static struct in_addr server_id(struct dhcp_context *context, struct in_addr ove { if (override.s_addr != 0) return override; - else if (context) + else if (context && context->local.s_addr != 0) return context->local; else return fallback; @@ -1463,59 +1529,23 @@ static int sanitise(unsigned char *opt, char *buf) } #ifdef HAVE_SCRIPT -static void add_extradata_data(struct dhcp_lease *lease, unsigned char *data, size_t len, int delim) -{ - if ((lease->extradata_size - lease->extradata_len) < (len + 1)) - { - size_t newsz = lease->extradata_len + len + 100; - unsigned char *new = whine_malloc(newsz); - - if (!new) - return; - - if (lease->extradata) - { - memcpy(new, lease->extradata, lease->extradata_len); - free(lease->extradata); - } - - lease->extradata = new; - lease->extradata_size = newsz; - } - - if (len != 0) - memcpy(lease->extradata + lease->extradata_len, data, len); - lease->extradata[lease->extradata_len + len] = delim; - lease->extradata_len += len + 1; -} - static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt) { if (!opt) - add_extradata_data(lease, NULL, 0, 0); + lease_add_extradata(lease, NULL, 0, 0); else - { - size_t i, len = option_len(opt); - unsigned char *ucp = option_ptr(opt, 0); - - /* check for embeded NULLs */ - for (i = 0; i < len; i++) - if (ucp[i] == 0) - { - len = i; - break; - } - - add_extradata_data(lease, ucp, len, 0); - } + lease_add_extradata(lease, option_ptr(opt, 0), option_len(opt), 0); } #endif static void log_packet(char *type, void *addr, unsigned char *ext_mac, - int mac_len, char *interface, char *string, u32 xid) + int mac_len, char *interface, char *string, char *err, u32 xid) { struct in_addr a; + if (!err && !option_bool(OPT_LOG_OPTS) && option_bool(OPT_QUIET_DHCP)) + return; + /* addr may be misaligned */ if (addr) memcpy(&a, addr, sizeof(a)); @@ -1523,52 +1553,34 @@ static void log_packet(char *type, void *addr, unsigned char *ext_mac, print_mac(daemon->namebuff, ext_mac, mac_len); if(option_bool(OPT_LOG_OPTS)) - my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s", + my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s%s", ntohl(xid), type, interface, addr ? inet_ntoa(a) : "", addr ? " " : "", daemon->namebuff, - string ? string : ""); + string ? string : "", + err ? err : ""); else - my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s %s", + my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s %s%s", type, interface, addr ? inet_ntoa(a) : "", addr ? " " : "", daemon->namebuff, - string ? string : ""); + string ? string : "", + err ? err : ""); } static void log_options(unsigned char *start, u32 xid) { while (*start != OPTION_END) { - int is_ip, is_name, i; - char *text = option_string(start[0], &is_ip, &is_name); - unsigned char trunc = option_len(start); + char *optname = option_string(AF_INET, start[0], option_ptr(start, 0), option_len(start), daemon->namebuff, MAXDNAME); - if (is_ip) - for (daemon->namebuff[0]= 0, i = 0; i <= trunc - INADDRSZ; i += INADDRSZ) - { - if (i != 0) - strncat(daemon->namebuff, ", ", 256 - strlen(daemon->namebuff)); - strncat(daemon->namebuff, inet_ntoa(option_addr_arr(start, i)), 256 - strlen(daemon->namebuff)); - } - else if (!is_name || !sanitise(start, daemon->namebuff)) - { - if (trunc > 13) - trunc = 13; - print_mac(daemon->namebuff, option_ptr(start, 0), trunc); - } - - my_syslog(MS_DHCP | LOG_INFO, "%u sent size:%3d option:%3d%s%s%s%s%s", - ntohl(xid), option_len(start), start[0], - text ? ":" : "", text ? text : "", - trunc == 0 ? "" : " ", - trunc == 0 ? "" : daemon->namebuff, - trunc == option_len(start) ? "" : "..."); + my_syslog(MS_DHCP | LOG_INFO, "%u sent size:%3d option:%3d %s %s", + ntohl(xid), option_len(start), start[0], optname, daemon->namebuff); start += start[1] + 2; } } @@ -1623,22 +1635,17 @@ static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt return NULL; } -static struct in_addr option_addr_arr(unsigned char *opt, int offset) +static struct in_addr option_addr(unsigned char *opt) { - /* this worries about unaligned data in the option. */ + /* this worries about unaligned data in the option. */ /* struct in_addr is network byte order */ struct in_addr ret; - memcpy(&ret, option_ptr(opt, offset), INADDRSZ); + memcpy(&ret, option_ptr(opt, 0), INADDRSZ); return ret; } -static struct in_addr option_addr(unsigned char *opt) -{ - return option_addr_arr(opt, 0); -} - static unsigned int option_uint(unsigned char *opt, int offset, int size) { /* this worries about unaligned data and byte order */ @@ -1673,15 +1680,12 @@ static unsigned char *find_overload(struct dhcp_packet *mess) return NULL; } -static size_t dhcp_packet_size(struct dhcp_packet *mess, struct dhcp_netid *netid, - unsigned char *agent_id, unsigned char *real_end) +static size_t dhcp_packet_size(struct dhcp_packet *mess, unsigned char *agent_id, unsigned char *real_end) { unsigned char *p = dhcp_skip_opts(&mess->options[0] + sizeof(u32)); unsigned char *overload; size_t ret; - struct dhcp_netid_list *id_list; - struct dhcp_netid *n; - + /* move agent_id back down to the end of the packet */ if (agent_id) { @@ -1690,27 +1694,6 @@ static size_t dhcp_packet_size(struct dhcp_packet *mess, struct dhcp_netid *neti memset(p, 0, real_end - p); /* in case of overlap */ } - /* We do logging too */ - if (netid && option_bool(OPT_LOG_OPTS)) - { - char *s = daemon->namebuff; - for (*s = 0; netid; netid = netid->next) - { - /* kill dupes. */ - for (n = netid->next; n; n = n->next) - if (strcmp(netid->net, n->net) == 0) - break; - - if (!n) - { - strncat (s, netid->net, (MAXDNAME-1) - strlen(s)); - if (netid->next) - strncat (s, ", ", (MAXDNAME-1) - strlen(s)); - } - } - my_syslog(MS_DHCP | LOG_INFO, _("%u tags: %s"), ntohl(mess->xid), s); - } - /* add END options to the regions. */ overload = find_overload(mess); @@ -1735,12 +1718,6 @@ static size_t dhcp_packet_size(struct dhcp_packet *mess, struct dhcp_netid *neti *p++ = OPTION_END; - for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next) - if ((!id_list->list) || match_netid(id_list->list, netid, 0)) - break; - if (id_list) - mess->flags |= htons(0x8000); /* force broadcast */ - if (option_bool(OPT_LOG_OPTS)) { if (mess->siaddr.s_addr != 0) @@ -1803,7 +1780,7 @@ static unsigned char *free_space(struct dhcp_packet *mess, unsigned char *end, i if (overload[2] & 2) { p = dhcp_skip_opts(mess->sname); - if (p + len + 3 >= mess->sname + sizeof(mess->file)) + if (p + len + 3 >= mess->sname + sizeof(mess->sname)) p = NULL; } } @@ -1870,7 +1847,8 @@ static int do_opt(struct dhcp_opt *opt, unsigned char *p, struct dhcp_context *c } } else - memcpy(p, opt->val, len); + /* empty string may be extended to "\0" by null_term */ + memcpy(p, opt->val ? opt->val : (unsigned char *)"", len); } return len; } @@ -1890,20 +1868,14 @@ static int in_list(unsigned char *list, int opt) return 0; } -static struct dhcp_opt *option_find2(struct dhcp_netid *netid, struct dhcp_opt *opts, int opt) +static struct dhcp_opt *option_find2(int opt) { - struct dhcp_opt *tmp; - for (tmp = opts; tmp; tmp = tmp->next) - if (tmp->opt == opt && !(tmp->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925))) - if (match_netid(tmp->netid, netid, 0)) - return tmp; - - /* No match, look for one without a netid */ - for (tmp = opts; tmp; tmp = tmp->next) - if (tmp->opt == opt && !(tmp->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925))) - if (match_netid(tmp->netid, netid, 1)) - return tmp; - + struct dhcp_opt *opts; + + for (opts = daemon->dhcp_opts; opts; opts = opts->next) + if (opts->opt == opt && (opts->flags & DHOPT_TAGOK)) + return opts; + return NULL; } @@ -2003,7 +1975,7 @@ static int prune_vendor_opts(struct dhcp_netid *netid) return force; } -static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local) +static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now) { #define NUM_OPTS 4 @@ -2060,8 +2032,9 @@ static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct return daemon->dhcp_opts; } - boot_server = service->basename ? local : service->server; - + boot_server = service->basename ? local : + (service->sname ? a_record_from_hosts(service->sname, now) : service->server); + if (boot_server.s_addr != 0) { if (q - (unsigned char *)daemon->dhcp_buff3 + 3 + INADDRSZ >= 253) @@ -2152,13 +2125,16 @@ static void do_options(struct dhcp_context *context, unsigned char *end, unsigned char *req_options, char *hostname, - char *domain, char *config_domain, + char *domain, struct dhcp_netid *netid, struct in_addr subnet_addr, unsigned char fqdn_flags, int null_term, int pxe_arch, unsigned char *uuid, - int vendor_class_len) + int vendor_class_len, + time_t now, + unsigned int lease_time, + unsigned short fuzz) { struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts; struct dhcp_boot *boot; @@ -2167,22 +2143,26 @@ static void do_options(struct dhcp_context *context, unsigned char f0 = 0, s0 = 0; int done_file = 0, done_server = 0; int done_vendor_class = 0; + struct dhcp_netid *tagif; + struct dhcp_netid_list *id_list; - if (config_domain && (!domain || !hostname_isequal(domain, config_domain))) - my_syslog(MS_DHCP | LOG_WARNING, _("Ignoring domain %s for DHCP host name %s"), config_domain, hostname); - + /* filter options based on tags, those we want get DHOPT_TAGOK bit set */ + if (context) + context->netid.next = NULL; + tagif = option_filter(netid, context && context->netid.net ? &context->netid : NULL, config_opts); + /* logging */ if (option_bool(OPT_LOG_OPTS) && req_options) { char *q = daemon->namebuff; for (i = 0; req_options[i] != OPTION_END; i++) { - char *s = option_string(req_options[i], NULL, NULL); + char *s = option_string(AF_INET, req_options[i], NULL, 0, NULL, 0); q += snprintf(q, MAXDNAME - (q - daemon->namebuff), "%d%s%s%s", req_options[i], - s ? ":" : "", - s ? s : "", + strlen(s) != 0 ? ":" : "", + s, req_options[i+1] == OPTION_END ? "" : ", "); if (req_options[i+1] == OPTION_END || (q - daemon->namebuff) > 40) { @@ -2192,6 +2172,12 @@ static void do_options(struct dhcp_context *context, } } + for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, netid, 0)) + break; + if (id_list) + mess->flags |= htons(0x8000); /* force broadcast */ + if (context) mess->siaddr = context->local; @@ -2201,7 +2187,7 @@ static void do_options(struct dhcp_context *context, provide an manual option to disable it. Some PXE ROMs have bugs (surprise!) and need zero-terminated names, so we always send those. */ - if ((boot = find_boot(netid))) + if ((boot = find_boot(tagif))) { if (boot->sname) { @@ -2223,8 +2209,10 @@ static void do_options(struct dhcp_context *context, strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); } - if (boot->next_server.s_addr) + if (boot->next_server.s_addr) mess->siaddr = boot->next_server; + else if (boot->tftp_sname) + mess->siaddr = a_record_from_hosts(boot->tftp_sname, now); } else /* Use the values of the relevant options if no dhcp-boot given and @@ -2233,20 +2221,20 @@ static void do_options(struct dhcp_context *context, dhcp-optsfile. */ { if ((!req_options || !in_list(req_options, OPTION_FILENAME)) && - (opt = option_find2(netid, config_opts, OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE)) + (opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE)) { strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file)-1); done_file = 1; } if ((!req_options || !in_list(req_options, OPTION_SNAME)) && - (opt = option_find2(netid, config_opts, OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE)) + (opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE)) { strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname)-1); done_server = 1; } - if ((opt = option_find2(netid, config_opts, OPTION_END))) + if ((opt = option_find2(OPTION_END))) mess->siaddr.s_addr = ((struct in_addr *)opt->val)->s_addr; } @@ -2270,40 +2258,76 @@ static void do_options(struct dhcp_context *context, /* rfc3011 says this doesn't need to be in the requested options list. */ if (subnet_addr.s_addr) option_put(mess, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr)); - + + if (lease_time != 0xffffffff) + { + unsigned int t1val = lease_time/2; + unsigned int t2val = (lease_time*7)/8; + unsigned int hval; + + /* If set by user, sanity check, so not longer than lease. */ + if ((opt = option_find2(OPTION_T1))) + { + hval = ntohl(*((unsigned int *)opt->val)); + if (hval < lease_time && hval > 2) + t1val = hval; + } + + if ((opt = option_find2(OPTION_T2))) + { + hval = ntohl(*((unsigned int *)opt->val)); + if (hval < lease_time && hval > 2) + t2val = hval; + } + + /* ensure T1 is still < T2 */ + if (t2val <= t1val) + t1val = t2val - 1; + + while (fuzz > (t1val/8)) + fuzz = fuzz/2; + + t1val -= fuzz; + t2val -= fuzz; + + option_put(mess, end, OPTION_T1, 4, t1val); + option_put(mess, end, OPTION_T2, 4, t2val); + } + /* replies to DHCPINFORM may not have a valid context */ if (context) { - if (!option_find2(netid, config_opts, OPTION_NETMASK)) + if (!option_find2(OPTION_NETMASK)) option_put(mess, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr)); /* May not have a "guessed" broadcast address if we got no packets via a relay from this net yet (ie just unicast renewals after a restart */ if (context->broadcast.s_addr && - !option_find2(netid, config_opts, OPTION_BROADCAST)) + !option_find2(OPTION_BROADCAST)) option_put(mess, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr)); /* Same comments as broadcast apply, and also may not be able to get a sensible default when using subnet select. User must configure by steam in that case. */ if (context->router.s_addr && in_list(req_options, OPTION_ROUTER) && - !option_find2(netid, config_opts, OPTION_ROUTER)) + !option_find2(OPTION_ROUTER)) option_put(mess, end, OPTION_ROUTER, INADDRSZ, ntohl(context->router.s_addr)); - if (in_list(req_options, OPTION_DNSSERVER) && - !option_find2(netid, config_opts, OPTION_DNSSERVER)) + if (daemon->port == NAMESERVER_PORT && + in_list(req_options, OPTION_DNSSERVER) && + !option_find2(OPTION_DNSSERVER)) option_put(mess, end, OPTION_DNSSERVER, INADDRSZ, ntohl(context->local.s_addr)); } if (domain && in_list(req_options, OPTION_DOMAINNAME) && - !option_find2(netid, config_opts, OPTION_DOMAINNAME)) + !option_find2(OPTION_DOMAINNAME)) option_put_string(mess, end, OPTION_DOMAINNAME, domain, null_term); /* Note that we ignore attempts to set the fqdn using --dhc-option=81,<name> */ if (hostname) { if (in_list(req_options, OPTION_HOSTNAME) && - !option_find2(netid, config_opts, OPTION_HOSTNAME)) + !option_find2(OPTION_HOSTNAME)) option_put_string(mess, end, OPTION_HOSTNAME, hostname, null_term); if (fqdn_flags != 0) @@ -2317,10 +2341,12 @@ static void do_options(struct dhcp_context *context, if (domain) len += strlen(domain) + 1; - + else if (fqdn_flags & 0x04) + len--; + if ((p = free_space(mess, end, OPTION_CLIENT_FQDN, len))) { - *(p++) = fqdn_flags; + *(p++) = fqdn_flags & 0x0f; /* MBZ bits to zero */ *(p++) = 255; *(p++) = 255; @@ -2328,8 +2354,10 @@ static void do_options(struct dhcp_context *context, { p = do_rfc1035_name(p, hostname); if (domain) - p = do_rfc1035_name(p, domain); - *p++ = 0; + { + p = do_rfc1035_name(p, domain); + *p++ = 0; + } } else { @@ -2352,16 +2380,22 @@ static void do_options(struct dhcp_context *context, { int optno = opt->opt; + /* netids match and not encapsulated? */ + if (!(opt->flags & DHOPT_TAGOK)) + continue; + /* was it asked for, or are we sending it anyway? */ if (!(opt->flags & DHOPT_FORCE) && !in_list(req_options, optno)) continue; - /* prohibit some used-internally options */ + /* prohibit some used-internally options. T1 and T2 already handled. */ if (optno == OPTION_CLIENT_FQDN || optno == OPTION_MAXMESSAGE || optno == OPTION_OVERLOAD || optno == OPTION_PAD || - optno == OPTION_END) + optno == OPTION_END || + optno == OPTION_T1 || + optno == OPTION_T2) continue; if (optno == OPTION_SNAME && done_server) @@ -2370,10 +2404,6 @@ static void do_options(struct dhcp_context *context, if (optno == OPTION_FILENAME && done_file) continue; - /* netids match and not encapsulated? */ - if (opt != option_find2(netid, config_opts, optno)) - continue; - /* For the options we have default values on dhc-option=<optionno> means "don't include this option" not "include a zero-length option" */ @@ -2411,8 +2441,8 @@ static void do_options(struct dhcp_context *context, /* Now send options to be encapsulated in arbitrary options, eg dhcp-option=encap:172,17,....... - Also hand vendor-identifying vendor-encapsulated options, - dhcp-option = rfc3925-encap:13,17,....... + Also handle vendor-identifying vendor-encapsulated options, + dhcp-option = vi-encap:13,17,....... The may be more that one "outer" to do, so group all the options which match each outer in turn. */ for (opt = config_opts; opt; opt = opt->next) @@ -2440,7 +2470,7 @@ static void do_options(struct dhcp_context *context, continue; o->flags |= DHOPT_ENCAP_DONE; - if (match_netid(o->netid, netid, 1) && + if (match_netid(o->netid, tagif, 1) && ((o->flags & DHOPT_FORCE) || in_list(req_options, outer))) { o->flags |= DHOPT_ENCAP_MATCH; @@ -2474,12 +2504,12 @@ static void do_options(struct dhcp_context *context, } } - force_encap = prune_vendor_opts(netid); + force_encap = prune_vendor_opts(tagif); if (context && pxe_arch != -1) { pxe_misc(mess, end, uuid); - config_opts = pxe_opts(pxe_arch, netid, context->local); + config_opts = pxe_opts(pxe_arch, tagif, context->local, now); } if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) && |