--- embedaddon/dnsmasq/src/rfc2131.c 2013/07/29 19:37:40 1.1.1.1 +++ embedaddon/dnsmasq/src/rfc2131.c 2016/11/02 09:57:01 1.1.1.3 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2016 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 @@ -34,11 +34,12 @@ static void option_put_string(struct dhcp_packet *mess static struct in_addr option_addr(unsigned char *opt); 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, 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, @@ -51,7 +52,9 @@ static void do_options(struct dhcp_context *context, int null_term, int pxearch, unsigned char *uuid, int vendor_class_len, - time_t now); + time_t now, + unsigned int lease_time, + unsigned short fuzz); static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt); @@ -60,7 +63,7 @@ static void pxe_misc(struct dhcp_packet *mess, unsigne 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, time_t now); struct dhcp_boot *find_boot(struct dhcp_netid *netid); - +static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe); 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, struct in_addr fallback) @@ -91,7 +94,10 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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; @@ -155,8 +161,9 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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) @@ -354,7 +361,118 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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; config = find_config(daemon->dhcp_conf, context, clid, clid_len, @@ -493,18 +611,17 @@ size_t dhcp_reply(struct dhcp_context *context, char * lease_set_interface(lease, int_index, now); clear_packet(mess, end); - match_vendor_opts(NULL, daemon->dhcp_opts); /* clear flags */ do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr), - netid, subnet_addr, 0, 0, -1, NULL, 0, now); + 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, 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); @@ -534,7 +651,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * } 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; @@ -622,119 +739,8 @@ size_t dhcp_reply(struct dhcp_context *context, char * } } - /* 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)) @@ -799,9 +805,14 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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; @@ -813,7 +824,10 @@ size_t dhcp_reply(struct dhcp_context *context, char * else mess->siaddr = context->local; - snprintf((char *)mess->file, sizeof(mess->file), "%s.%d", service->basename, layer); + snprintf((char *)mess->file, sizeof(mess->file), + strchr(service->basename, '.') ? "%s" :"%s.%d", + service->basename, layer); + option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK); option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr)); pxe_misc(mess, end, uuid); @@ -827,7 +841,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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); + 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); } @@ -840,6 +854,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST))) { struct dhcp_context *tmp; + int workaround = 0; for (tmp = context; tmp; tmp = tmp->current) if ((tmp->flags & CONTEXT_PROXY) && @@ -848,8 +863,17 @@ size_t dhcp_reply(struct dhcp_context *context, char * if (tmp) { - struct dhcp_boot *boot = find_boot(tagif_netid); - + struct dhcp_boot *boot; + int redirect4011 = 0; + + 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) { @@ -859,10 +883,21 @@ size_t dhcp_reply(struct dhcp_context *context, char * clear_packet(mess, end); - /* Provide the bootfile here, for gPXE, and in case we have no menu items - and set discovery_control = 8 */ - if (boot) + /* Redirect EFI clients to port 4011 */ + if (pxearch >= 6) { + redirect4011 = 1; + mess->siaddr = tmp->local; + } + + /* Returns true if only one matching service is available. On port 4011, + it also inserts the boot file and server name. */ + workaround = pxe_uefi_workaround(pxearch, tagif_netid, mess, tmp->local, now, pxe); + + if (!workaround && boot) + { + /* Provide the bootfile here, for gPXE, and in case we have no menu items + and set discovery_control = 8 */ if (boot->next_server.s_addr) mess->siaddr = boot->next_server; else if (boot->tftp_sname) @@ -874,12 +909,13 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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, 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); + if ((pxe && !workaround) || !redirect4011) + 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", NULL, mess->xid); log_tags(tagif_netid, ntohl(mess->xid)); return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end); } @@ -911,7 +947,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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); @@ -943,13 +979,15 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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; } @@ -1007,7 +1045,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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; @@ -1020,7 +1058,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * log_tags(tagif_netid, ntohl(mess->xid)); - log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, 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); @@ -1028,13 +1066,8 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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), - netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz); return dhcp_packet_size(mess, agent_id, real_end); @@ -1072,7 +1105,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * Have to set override to make sure we echo back the correct server-id */ struct irec *intr; - enumerate_interfaces(); + enumerate_interfaces(0); for (intr = daemon->interfaces; intr; intr = intr->next) if (intr->addr.sa.sa_family == AF_INET && @@ -1136,7 +1169,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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) { @@ -1208,7 +1241,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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); @@ -1292,7 +1325,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * /* 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); + lease_add_extradata(lease, ucp, len, -1); } } #endif @@ -1347,21 +1380,14 @@ size_t dhcp_reply(struct dhcp_context *context, char * else override = lease->override; - log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, mess->xid); + 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), - netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz); } return dhcp_packet_size(mess, agent_id, real_end); @@ -1370,7 +1396,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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; @@ -1385,8 +1411,8 @@ size_t dhcp_reply(struct dhcp_context *context, char * lease->hostname) hostname = lease->hostname; - if (!hostname && (hostname = host_from_dns(mess->ciaddr))) - domain = get_domain(mess->ciaddr); + if (!hostname) + hostname = host_from_dns(mess->ciaddr); if (context && context->netid.net) { @@ -1396,7 +1422,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * log_tags(tagif_netid, ntohl(mess->xid)); - log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, mess->xid); + log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid); if (lease) { @@ -1410,9 +1436,23 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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)); - + + /* 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); + } + do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr), - netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now); + 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, agent_id, real_end); @@ -1516,10 +1556,13 @@ static void add_extradata_opt(struct dhcp_lease *lease #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)); @@ -1527,22 +1570,24 @@ static void log_packet(char *type, void *addr, unsigne 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) @@ -1819,7 +1864,8 @@ static int do_opt(struct dhcp_opt *opt, unsigned char } } 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; } @@ -1946,6 +1992,56 @@ static int prune_vendor_opts(struct dhcp_netid *netid) return force; } + +/* Many UEFI PXE implementations have badly broken menu code. + If there's exactly one relevant menu item, we abandon the menu system, + and jamb the data direct into the DHCP file, siaddr and sname fields. + Note that in this case, we have to assume that layer zero would be requested + by the client PXE stack. */ +static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe) +{ + struct pxe_service *service, *found; + + /* Only workaround UEFI archs. */ + if (pxe_arch < 6) + return 0; + + for (found = NULL, service = daemon->pxe_services; service; service = service->next) + if (pxe_arch == service->CSA && service->basename && match_netid(service->netid, netid, 1)) + { + if (found) + return 0; /* More than one relevant menu item */ + + found = service; + } + + if (!found) + return 0; /* No relevant menu items. */ + + if (!pxe) + return 1; + + if (found->sname) + { + mess->siaddr = a_record_from_hosts(found->sname, now); + snprintf((char *)mess->sname, sizeof(mess->sname), "%s", found->sname); + } + else + { + if (found->server.s_addr != 0) + mess->siaddr = found->server; + else + mess->siaddr = local; + + inet_ntop(AF_INET, &mess->siaddr, (char *)mess->sname, INET_ADDRSTRLEN); + } + + snprintf((char *)mess->file, sizeof(mess->file), + strchr(found->basename, '.') ? "%s" : "%s.0", found->basename); + + return 1; +} + static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now) { #define NUM_OPTS 4 @@ -2103,7 +2199,9 @@ static void do_options(struct dhcp_context *context, int null_term, int pxe_arch, unsigned char *uuid, int vendor_class_len, - time_t now) + time_t now, + unsigned int lease_time, + unsigned short fuzz) { struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts; struct dhcp_boot *boot; @@ -2227,7 +2325,42 @@ 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) { @@ -2275,7 +2408,9 @@ 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 & 0x0f; /* MBZ bits to zero */ @@ -2286,8 +2421,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 { @@ -2318,12 +2455,14 @@ static void do_options(struct dhcp_context *context, 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) @@ -2437,7 +2576,8 @@ static void do_options(struct dhcp_context *context, if (context && pxe_arch != -1) { pxe_misc(mess, end, uuid); - config_opts = pxe_opts(pxe_arch, tagif, context->local, now); + if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0)) + config_opts = pxe_opts(pxe_arch, tagif, context->local, now); } if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) &&