diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/auth.c | 829 | ||||
-rw-r--r-- | src/blockdata.c | 151 | ||||
-rw-r--r-- | src/bpf.c | 321 | ||||
-rw-r--r-- | src/cache.c | 964 | ||||
-rw-r--r-- | src/config.h | 397 | ||||
-rw-r--r-- | src/conntrack.c | 90 | ||||
-rw-r--r-- | src/dbus.c | 668 | ||||
-rw-r--r-- | src/dhcp-common.c | 905 | ||||
-rw-r--r-- | src/dhcp-protocol.h (renamed from src/dhcp_protocol.h) | 7 | ||||
-rw-r--r-- | src/dhcp.c | 768 | ||||
-rw-r--r-- | src/dhcp6-protocol.h | 75 | ||||
-rw-r--r-- | src/dhcp6.c | 806 | ||||
-rw-r--r-- | src/dns-protocol.h (renamed from src/dns_protocol.h) | 61 | ||||
-rw-r--r-- | src/dnsmasq.c | 1112 | ||||
-rw-r--r-- | src/dnsmasq.h | 784 | ||||
-rw-r--r-- | src/dnssec.c | 2544 | ||||
-rw-r--r-- | src/domain.c | 232 | ||||
-rw-r--r-- | src/forward.c | 1787 | ||||
-rw-r--r-- | src/helper.c | 630 | ||||
-rw-r--r-- | src/inotify.c | 288 | ||||
-rw-r--r-- | src/ip6addr.h | 34 | ||||
-rw-r--r-- | src/ipset.c | 229 | ||||
-rw-r--r-- | src/lease.c | 725 | ||||
-rw-r--r-- | src/log.c | 41 | ||||
-rw-r--r-- | src/loop.c | 117 | ||||
-rw-r--r-- | src/netlink.c | 191 | ||||
-rw-r--r-- | src/network.c | 1291 | ||||
-rw-r--r-- | src/option.c | 2646 | ||||
-rw-r--r-- | src/outpacket.c | 108 | ||||
-rw-r--r-- | src/poll.c | 125 | ||||
-rw-r--r-- | src/radv-protocol.h | 58 | ||||
-rw-r--r-- | src/radv.c | 976 | ||||
-rw-r--r-- | src/rfc1035.c | 1242 | ||||
-rw-r--r-- | src/rfc2131.c | 930 | ||||
-rw-r--r-- | src/rfc3315.c | 2183 | ||||
-rw-r--r-- | src/slaac.c | 209 | ||||
-rw-r--r-- | src/tables.c | 173 | ||||
-rw-r--r-- | src/tftp.c | 252 | ||||
-rw-r--r-- | src/util.c | 279 |
39 files changed, 21217 insertions, 4011 deletions
diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 0000000..2b0b7d6 --- /dev/null +++ b/src/auth.c @@ -0,0 +1,829 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_AUTH + +static struct addrlist *find_subnet(struct auth_zone *zone, int flag, struct all_addr *addr_u) +{ + struct addrlist *subnet; + + for (subnet = zone->subnet; subnet; subnet = subnet->next) + { + if (!(subnet->flags & ADDRLIST_IPV6)) + { + struct in_addr netmask, addr = addr_u->addr.addr4; + + if (!(flag & F_IPV4)) + continue; + + netmask.s_addr = htonl(~(in_addr_t)0 << (32 - subnet->prefixlen)); + + if (is_same_net(addr, subnet->addr.addr.addr4, netmask)) + return subnet; + } +#ifdef HAVE_IPV6 + else if (is_same_net6(&(addr_u->addr.addr6), &subnet->addr.addr.addr6, subnet->prefixlen)) + return subnet; +#endif + + } + return NULL; +} + +static int filter_zone(struct auth_zone *zone, int flag, struct all_addr *addr_u) +{ + /* No zones specified, no filter */ + if (!zone->subnet) + return 1; + + return find_subnet(zone, flag, addr_u) != NULL; +} + +int in_zone(struct auth_zone *zone, char *name, char **cut) +{ + size_t namelen = strlen(name); + size_t domainlen = strlen(zone->domain); + + if (cut) + *cut = NULL; + + if (namelen >= domainlen && + hostname_isequal(zone->domain, &name[namelen - domainlen])) + { + + if (namelen == domainlen) + return 1; + + if (name[namelen - domainlen - 1] == '.') + { + if (cut) + *cut = &name[namelen - domainlen - 1]; + return 1; + } + } + + return 0; +} + + +size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now, union mysockaddr *peer_addr, int local_query) +{ + char *name = daemon->namebuff; + unsigned char *p, *ansp; + int qtype, qclass; + int nameoffset, axfroffset = 0; + int q, anscount = 0, authcount = 0; + struct crec *crecp; + int auth = !local_query, trunc = 0, nxdomain = 1, soa = 0, ns = 0, axfr = 0; + struct auth_zone *zone = NULL; + struct addrlist *subnet = NULL; + char *cut; + struct mx_srv_record *rec, *move, **up; + struct txt_record *txt; + struct interface_name *intr; + struct naptr *na; + struct all_addr addr; + struct cname *a; + + if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY ) + return 0; + + /* determine end of question section (we put answers there) */ + if (!(ansp = skip_questions(header, qlen))) + return 0; /* bad packet */ + + /* now process each question, answers go in RRs after the question */ + p = (unsigned char *)(header+1); + + for (q = ntohs(header->qdcount); q != 0; q--) + { + unsigned short flag = 0; + int found = 0; + + /* save pointer to name for copying into answers */ + nameoffset = p - (unsigned char *)header; + + /* now extract name as .-concatenated string into name */ + if (!extract_name(header, qlen, &p, name, 1, 4)) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qclass != C_IN) + { + auth = 0; + continue; + } + + if ((qtype == T_PTR || qtype == T_SOA || qtype == T_NS) && + (flag = in_arpa_name_2_addr(name, &addr)) && + !local_query) + { + for (zone = daemon->auth_zones; zone; zone = zone->next) + if ((subnet = find_subnet(zone, flag, &addr))) + break; + + if (!zone) + { + auth = 0; + continue; + } + else if (qtype == T_SOA) + soa = 1, found = 1; + else if (qtype == T_NS) + ns = 1, found = 1; + } + + if (qtype == T_PTR && flag) + { + intr = NULL; + + if (flag == F_IPV4) + for (intr = daemon->int_names; intr; intr = intr->next) + { + struct addrlist *addrlist; + + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr.addr4.s_addr == addrlist->addr.addr.addr4.s_addr) + break; + + if (addrlist) + break; + else + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; + } +#ifdef HAVE_IPV6 + else if (flag == F_IPV6) + for (intr = daemon->int_names; intr; intr = intr->next) + { + struct addrlist *addrlist; + + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr.addr6, &addrlist->addr.addr.addr6)) + break; + + if (addrlist) + break; + else + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; + } +#endif + + if (intr) + { + if (local_query || in_zone(zone, intr->name, NULL)) + { + found = 1; + log_query(flag | F_REVERSE | F_CONFIG, intr->name, &addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, + T_PTR, C_IN, "d", intr->name)) + anscount++; + } + } + + if ((crecp = cache_find_by_addr(NULL, &addr, now, flag))) + do { + strcpy(name, cache_get_name(crecp)); + + if (crecp->flags & F_DHCP && !option_bool(OPT_DHCP_FQDN)) + { + char *p = strchr(name, '.'); + if (p) + *p = 0; /* must be bare name */ + + /* add external domain */ + if (zone) + { + strcat(name, "."); + strcat(name, zone->domain); + } + log_query(flag | F_DHCP | F_REVERSE, name, &addr, record_source(crecp->uid)); + found = 1; + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, + T_PTR, C_IN, "d", name)) + anscount++; + } + else if (crecp->flags & (F_DHCP | F_HOSTS) && (local_query || in_zone(zone, name, NULL))) + { + log_query(crecp->flags & ~F_FORWARD, name, &addr, record_source(crecp->uid)); + found = 1; + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, + T_PTR, C_IN, "d", name)) + anscount++; + } + else + continue; + + } while ((crecp = cache_find_by_addr(crecp, &addr, now, flag))); + + if (found) + nxdomain = 0; + else + log_query(flag | F_NEG | F_NXDOMAIN | F_REVERSE | (auth ? F_AUTH : 0), NULL, &addr, NULL); + + continue; + } + + cname_restart: + if (found) + /* NS and SOA .arpa requests have set found above. */ + cut = NULL; + else + { + for (zone = daemon->auth_zones; zone; zone = zone->next) + if (in_zone(zone, name, &cut)) + break; + + if (!zone) + { + auth = 0; + continue; + } + } + + for (rec = daemon->mxnames; rec; rec = rec->next) + if (!rec->issrv && hostname_isequal(name, rec->name)) + { + nxdomain = 0; + + if (qtype == T_MX) + { + found = 1; + log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl, + NULL, T_MX, C_IN, "sd", rec->weight, rec->target)) + anscount++; + } + } + + for (move = NULL, up = &daemon->mxnames, rec = daemon->mxnames; rec; rec = rec->next) + if (rec->issrv && hostname_isequal(name, rec->name)) + { + nxdomain = 0; + + if (qtype == T_SRV) + { + found = 1; + log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl, + NULL, T_SRV, C_IN, "sssd", + rec->priority, rec->weight, rec->srvport, rec->target)) + + anscount++; + } + + /* unlink first SRV record found */ + if (!move) + { + move = rec; + *up = rec->next; + } + else + up = &rec->next; + } + else + up = &rec->next; + + /* put first SRV record back at the end. */ + if (move) + { + *up = move; + move->next = NULL; + } + + for (txt = daemon->rr; txt; txt = txt->next) + if (hostname_isequal(name, txt->name)) + { + nxdomain = 0; + if (txt->class == qtype) + { + found = 1; + log_query(F_CONFIG | F_RRNAME, name, NULL, "<RR>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl, + NULL, txt->class, C_IN, "t", txt->len, txt->txt)) + anscount++; + } + } + + for (txt = daemon->txt; txt; txt = txt->next) + if (txt->class == C_IN && hostname_isequal(name, txt->name)) + { + nxdomain = 0; + if (qtype == T_TXT) + { + found = 1; + log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl, + NULL, T_TXT, C_IN, "t", txt->len, txt->txt)) + anscount++; + } + } + + for (na = daemon->naptr; na; na = na->next) + if (hostname_isequal(name, na->name)) + { + nxdomain = 0; + if (qtype == T_NAPTR) + { + found = 1; + log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl, + NULL, T_NAPTR, C_IN, "sszzzd", + na->order, na->pref, na->flags, na->services, na->regexp, na->replace)) + anscount++; + } + } + + if (qtype == T_A) + flag = F_IPV4; + +#ifdef HAVE_IPV6 + if (qtype == T_AAAA) + flag = F_IPV6; +#endif + + for (intr = daemon->int_names; intr; intr = intr->next) + if (hostname_isequal(name, intr->name)) + { + struct addrlist *addrlist; + + nxdomain = 0; + + if (flag) + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == qtype && + (local_query || filter_zone(zone, flag, &addrlist->addr))) + { +#ifdef HAVE_IPV6 + if (addrlist->flags & ADDRLIST_REVONLY) + continue; +#endif + found = 1; + log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, qtype, C_IN, + qtype == T_A ? "4" : "6", &addrlist->addr)) + anscount++; + } + } + + for (a = daemon->cnames; a; a = a->next) + if (hostname_isequal(name, a->alias) ) + { + log_query(F_CONFIG | F_CNAME, name, NULL, NULL); + strcpy(name, a->target); + if (!strchr(name, '.')) + { + strcat(name, "."); + strcat(name, zone->domain); + } + found = 1; + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, &nameoffset, + T_CNAME, C_IN, "d", name)) + anscount++; + + goto cname_restart; + } + + if (!cut) + { + nxdomain = 0; + + if (qtype == T_SOA) + { + auth = soa = 1; /* inhibits auth section */ + found = 1; + log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<SOA>"); + } + else if (qtype == T_AXFR) + { + struct iname *peers; + + if (peer_addr->sa.sa_family == AF_INET) + peer_addr->in.sin_port = 0; +#ifdef HAVE_IPV6 + else + { + peer_addr->in6.sin6_port = 0; + peer_addr->in6.sin6_scope_id = 0; + } +#endif + + for (peers = daemon->auth_peers; peers; peers = peers->next) + if (sockaddr_isequal(peer_addr, &peers->addr)) + break; + + /* Refuse all AXFR unless --auth-sec-servers is set */ + if ((!peers && daemon->auth_peers) || !daemon->secondary_forward_server) + { + if (peer_addr->sa.sa_family == AF_INET) + inet_ntop(AF_INET, &peer_addr->in.sin_addr, daemon->addrbuff, ADDRSTRLEN); +#ifdef HAVE_IPV6 + else + inet_ntop(AF_INET6, &peer_addr->in6.sin6_addr, daemon->addrbuff, ADDRSTRLEN); +#endif + + my_syslog(LOG_WARNING, _("ignoring zone transfer request from %s"), daemon->addrbuff); + return 0; + } + + auth = 1; + soa = 1; /* inhibits auth section */ + ns = 1; /* ensure we include NS records! */ + axfr = 1; + found = 1; + axfroffset = nameoffset; + log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<AXFR>"); + } + else if (qtype == T_NS) + { + auth = 1; + ns = 1; /* inhibits auth section */ + found = 1; + log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<NS>"); + } + } + + if (!option_bool(OPT_DHCP_FQDN) && cut) + { + *cut = 0; /* remove domain part */ + + if (!strchr(name, '.') && (crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6))) + { + if (crecp->flags & F_DHCP) + do + { + nxdomain = 0; + if ((crecp->flags & flag) && + (local_query || filter_zone(zone, flag, &(crecp->addr.addr)))) + { + *cut = '.'; /* restore domain part */ + log_query(crecp->flags, name, &crecp->addr.addr, record_source(crecp->uid)); + *cut = 0; /* remove domain part */ + found = 1; + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, qtype, C_IN, + qtype == T_A ? "4" : "6", &crecp->addr)) + anscount++; + } + } while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6))); + } + + *cut = '.'; /* restore domain part */ + } + + if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6))) + { + if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN)))) + do + { + nxdomain = 0; + if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr.addr)))) + { + log_query(crecp->flags, name, &crecp->addr.addr, record_source(crecp->uid)); + found = 1; + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, qtype, C_IN, + qtype == T_A ? "4" : "6", &crecp->addr)) + anscount++; + } + } while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6))); + } + + if (!found) + log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL); + + } + + /* Add auth section */ + if (auth && zone) + { + char *authname; + int newoffset, offset = 0; + + if (!subnet) + authname = zone->domain; + else + { + /* handle NS and SOA for PTR records */ + + authname = name; + + if (!(subnet->flags & ADDRLIST_IPV6)) + { + in_addr_t a = ntohl(subnet->addr.addr.addr4.s_addr) >> 8; + char *p = name; + + if (subnet->prefixlen >= 24) + p += sprintf(p, "%d.", a & 0xff); + a = a >> 8; + if (subnet->prefixlen >= 16 ) + p += sprintf(p, "%d.", a & 0xff); + a = a >> 8; + p += sprintf(p, "%d.in-addr.arpa", a & 0xff); + + } +#ifdef HAVE_IPV6 + else + { + char *p = name; + int i; + + for (i = subnet->prefixlen-1; i >= 0; i -= 4) + { + int dig = ((unsigned char *)&subnet->addr.addr.addr6)[i>>3]; + p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4); + } + p += sprintf(p, "ip6.arpa"); + + } +#endif + } + + /* handle NS and SOA in auth section or for explicit queries */ + newoffset = ansp - (unsigned char *)header; + if (((anscount == 0 && !ns) || soa) && + add_resource_record(header, limit, &trunc, 0, &ansp, + daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll", + authname, daemon->authserver, daemon->hostmaster, + daemon->soa_sn, daemon->soa_refresh, + daemon->soa_retry, daemon->soa_expiry, + daemon->auth_ttl)) + { + offset = newoffset; + if (soa) + anscount++; + else + authcount++; + } + + if (anscount != 0 || ns) + { + struct name_list *secondary; + + newoffset = ansp - (unsigned char *)header; + if (add_resource_record(header, limit, &trunc, -offset, &ansp, + daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, daemon->authserver)) + { + if (offset == 0) + offset = newoffset; + if (ns) + anscount++; + else + authcount++; + } + + if (!subnet) + for (secondary = daemon->secondary_forward_server; secondary; secondary = secondary->next) + if (add_resource_record(header, limit, &trunc, offset, &ansp, + daemon->auth_ttl, NULL, T_NS, C_IN, "d", secondary->name)) + { + if (ns) + anscount++; + else + authcount++; + } + } + + if (axfr) + { + for (rec = daemon->mxnames; rec; rec = rec->next) + if (in_zone(zone, rec->name, &cut)) + { + if (cut) + *cut = 0; + + if (rec->issrv) + { + if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl, + NULL, T_SRV, C_IN, "sssd", cut ? rec->name : NULL, + rec->priority, rec->weight, rec->srvport, rec->target)) + + anscount++; + } + else + { + if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl, + NULL, T_MX, C_IN, "sd", cut ? rec->name : NULL, rec->weight, rec->target)) + anscount++; + } + + /* restore config data */ + if (cut) + *cut = '.'; + } + + for (txt = daemon->rr; txt; txt = txt->next) + if (in_zone(zone, txt->name, &cut)) + { + if (cut) + *cut = 0; + + if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl, + NULL, txt->class, C_IN, "t", cut ? txt->name : NULL, txt->len, txt->txt)) + anscount++; + + /* restore config data */ + if (cut) + *cut = '.'; + } + + for (txt = daemon->txt; txt; txt = txt->next) + if (txt->class == C_IN && in_zone(zone, txt->name, &cut)) + { + if (cut) + *cut = 0; + + if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl, + NULL, T_TXT, C_IN, "t", cut ? txt->name : NULL, txt->len, txt->txt)) + anscount++; + + /* restore config data */ + if (cut) + *cut = '.'; + } + + for (na = daemon->naptr; na; na = na->next) + if (in_zone(zone, na->name, &cut)) + { + if (cut) + *cut = 0; + + if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl, + NULL, T_NAPTR, C_IN, "sszzzd", cut ? na->name : NULL, + na->order, na->pref, na->flags, na->services, na->regexp, na->replace)) + anscount++; + + /* restore config data */ + if (cut) + *cut = '.'; + } + + for (intr = daemon->int_names; intr; intr = intr->next) + if (in_zone(zone, intr->name, &cut)) + { + struct addrlist *addrlist; + + if (cut) + *cut = 0; + + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if (!(addrlist->flags & ADDRLIST_IPV6) && + (local_query || filter_zone(zone, F_IPV4, &addrlist->addr)) && + add_resource_record(header, limit, &trunc, -axfroffset, &ansp, + daemon->auth_ttl, NULL, T_A, C_IN, "4", cut ? intr->name : NULL, &addrlist->addr)) + anscount++; + +#ifdef HAVE_IPV6 + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if ((addrlist->flags & ADDRLIST_IPV6) && + (local_query || filter_zone(zone, F_IPV6, &addrlist->addr)) && + add_resource_record(header, limit, &trunc, -axfroffset, &ansp, + daemon->auth_ttl, NULL, T_AAAA, C_IN, "6", cut ? intr->name : NULL, &addrlist->addr)) + anscount++; +#endif + + /* restore config data */ + if (cut) + *cut = '.'; + } + + for (a = daemon->cnames; a; a = a->next) + if (in_zone(zone, a->alias, &cut)) + { + strcpy(name, a->target); + if (!strchr(name, '.')) + { + strcat(name, "."); + strcat(name, zone->domain); + } + + if (cut) + *cut = 0; + + if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, + daemon->auth_ttl, NULL, + T_CNAME, C_IN, "d", cut ? a->alias : NULL, name)) + anscount++; + } + + cache_enumerate(1); + while ((crecp = cache_enumerate(0))) + { + if ((crecp->flags & (F_IPV4 | F_IPV6)) && + !(crecp->flags & (F_NEG | F_NXDOMAIN)) && + (crecp->flags & F_FORWARD)) + { + if ((crecp->flags & F_DHCP) && !option_bool(OPT_DHCP_FQDN)) + { + char *cache_name = cache_get_name(crecp); + if (!strchr(cache_name, '.') && + (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr.addr)))) + { + qtype = T_A; +#ifdef HAVE_IPV6 + if (crecp->flags & F_IPV6) + qtype = T_AAAA; +#endif + if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, + daemon->auth_ttl, NULL, qtype, C_IN, + (crecp->flags & F_IPV4) ? "4" : "6", cache_name, &crecp->addr)) + anscount++; + } + } + + if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN)))) + { + strcpy(name, cache_get_name(crecp)); + if (in_zone(zone, name, &cut) && + (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr.addr)))) + { + qtype = T_A; +#ifdef HAVE_IPV6 + if (crecp->flags & F_IPV6) + qtype = T_AAAA; +#endif + if (cut) + *cut = 0; + + if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, + daemon->auth_ttl, NULL, qtype, C_IN, + (crecp->flags & F_IPV4) ? "4" : "6", cut ? name : NULL, &crecp->addr)) + anscount++; + } + } + } + } + + /* repeat SOA as last record */ + if (add_resource_record(header, limit, &trunc, axfroffset, &ansp, + daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll", + daemon->authserver, daemon->hostmaster, + daemon->soa_sn, daemon->soa_refresh, + daemon->soa_retry, daemon->soa_expiry, + daemon->auth_ttl)) + anscount++; + + } + + } + + /* done all questions, set up header and return length of result */ + /* clear authoritative and truncated flags, set QR flag */ + header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR; + + if (local_query) + { + /* set RA flag */ + header->hb4 |= HB4_RA; + } + else + { + /* clear RA flag */ + header->hb4 &= ~HB4_RA; + } + + /* authoritive */ + if (auth) + header->hb3 |= HB3_AA; + + /* truncation */ + if (trunc) + header->hb3 |= HB3_TC; + + if ((auth || local_query) && nxdomain) + SET_RCODE(header, NXDOMAIN); + else + SET_RCODE(header, NOERROR); /* no error */ + header->ancount = htons(anscount); + header->nscount = htons(authcount); + header->arcount = htons(0); + return ansp - (unsigned char *)header; +} + +#endif + + + diff --git a/src/blockdata.c b/src/blockdata.c new file mode 100644 index 0000000..c8f5eae --- /dev/null +++ b/src/blockdata.c @@ -0,0 +1,151 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_DNSSEC + +static struct blockdata *keyblock_free; +static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced; + +static void blockdata_expand(int n) +{ + struct blockdata *new = whine_malloc(n * sizeof(struct blockdata)); + + if (n > 0 && new) + { + int i; + + new[n-1].next = keyblock_free; + keyblock_free = new; + + for (i = 0; i < n - 1; i++) + new[i].next = &new[i+1]; + + blockdata_alloced += n; + } +} + +/* Preallocate some blocks, proportional to cachesize, to reduce heap fragmentation. */ +void blockdata_init(void) +{ + keyblock_free = NULL; + blockdata_alloced = 0; + blockdata_count = 0; + blockdata_hwm = 0; + + /* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */ + if (option_bool(OPT_DNSSEC_VALID)) + blockdata_expand((daemon->cachesize * 100) / sizeof(struct blockdata)); +} + +void blockdata_report(void) +{ + if (option_bool(OPT_DNSSEC_VALID)) + my_syslog(LOG_INFO, _("DNSSEC memory in use %u, max %u, allocated %u"), + blockdata_count * sizeof(struct blockdata), + blockdata_hwm * sizeof(struct blockdata), + blockdata_alloced * sizeof(struct blockdata)); +} + +struct blockdata *blockdata_alloc(char *data, size_t len) +{ + struct blockdata *block, *ret = NULL; + struct blockdata **prev = &ret; + size_t blen; + + while (len > 0) + { + if (!keyblock_free) + blockdata_expand(50); + + if (keyblock_free) + { + block = keyblock_free; + keyblock_free = block->next; + blockdata_count++; + } + else + { + /* failed to alloc, free partial chain */ + blockdata_free(ret); + return NULL; + } + + if (blockdata_hwm < blockdata_count) + blockdata_hwm = blockdata_count; + + blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; + memcpy(block->key, data, blen); + data += blen; + len -= blen; + *prev = block; + prev = &block->next; + block->next = NULL; + } + + return ret; +} + +void blockdata_free(struct blockdata *blocks) +{ + struct blockdata *tmp; + + if (blocks) + { + for (tmp = blocks; tmp->next; tmp = tmp->next) + blockdata_count--; + tmp->next = keyblock_free; + keyblock_free = blocks; + blockdata_count--; + } +} + +/* if data == NULL, return pointer to static block of sufficient size */ +void *blockdata_retrieve(struct blockdata *block, size_t len, void *data) +{ + size_t blen; + struct blockdata *b; + void *new, *d; + + static unsigned int buff_len = 0; + static unsigned char *buff = NULL; + + if (!data) + { + if (len > buff_len) + { + if (!(new = whine_malloc(len))) + return NULL; + if (buff) + free(buff); + buff = new; + } + data = buff; + } + + for (d = data, b = block; len > 0 && b; b = b->next) + { + blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len; + memcpy(d, b->key, blen); + d += blen; + len -= blen; + } + + return data; +} + +#endif @@ -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 @@ -17,23 +17,35 @@ #include "dnsmasq.h" #if defined(HAVE_BSD_NETWORK) || defined(HAVE_SOLARIS_NETWORK) +#include <ifaddrs.h> -static struct iovec ifconf = { - .iov_base = NULL, - .iov_len = 0 -}; - -static struct iovec ifreq = { - .iov_base = NULL, - .iov_len = 0 -}; - -#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) - +#include <sys/param.h> #include <sys/sysctl.h> +#include <net/if.h> #include <net/route.h> #include <net/if_dl.h> #include <netinet/if_ether.h> +#if defined(__FreeBSD__) +# include <net/if_var.h> +#endif +#include <netinet/in_var.h> +#ifdef HAVE_IPV6 +# include <netinet6/in6_var.h> +#endif + +#ifndef SA_SIZE +#define SA_SIZE(sa) \ + ( (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ? \ + sizeof(long) : \ + 1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(long) - 1) ) ) +#endif + +#ifdef HAVE_BSD_NETWORK +static int del_family = 0; +static struct all_addr del_addr; +#endif + +#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) int arp_enumerate(void *parm, int (*callback)()) { @@ -43,8 +55,12 @@ int arp_enumerate(void *parm, int (*callback)()) struct rt_msghdr *rtm; struct sockaddr_inarp *sin2; struct sockaddr_dl *sdl; + struct iovec buff; int rc; - + + buff.iov_base = NULL; + buff.iov_len = 0; + mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; @@ -60,9 +76,9 @@ int arp_enumerate(void *parm, int (*callback)()) while (1) { - if (!expand_buf(&ifconf, needed)) + if (!expand_buf(&buff, needed)) return 0; - if ((rc = sysctl(mib, 6, ifconf.iov_base, &needed, NULL, 0)) == 0 || + if ((rc = sysctl(mib, 6, buff.iov_base, &needed, NULL, 0)) == 0 || errno != ENOMEM) break; needed += needed / 8; @@ -70,7 +86,7 @@ int arp_enumerate(void *parm, int (*callback)()) if (rc == -1) return 0; - for (next = ifconf.iov_base ; next < (char *)ifconf.iov_base + needed; next += rtm->rtm_msglen) + for (next = buff.iov_base ; next < (char *)buff.iov_base + needed; next += rtm->rtm_msglen) { rtm = (struct rt_msghdr *)next; sin2 = (struct sockaddr_inarp *)(rtm + 1); @@ -81,19 +97,14 @@ int arp_enumerate(void *parm, int (*callback)()) return 1; } - -#endif +#endif /* defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) */ int iface_enumerate(int family, void *parm, int (*callback)()) { - char *ptr; - struct ifreq *ifr; - struct ifconf ifc; - int fd, errsav, ret = 0; - int lastlen = 0; - size_t len = 0; - + struct ifaddrs *head, *addrs; + int errsav, fd = -1, ret = 0; + if (family == AF_UNSPEC) #if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) return arp_enumerate(parm, callback); @@ -101,87 +112,123 @@ int iface_enumerate(int family, void *parm, int (*callback)()) return 0; /* need code for Solaris and MacOS*/ #endif - if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + /* AF_LINK doesn't exist in Linux, so we can't use it in our API */ + if (family == AF_LOCAL) + family = AF_LINK; + + if (getifaddrs(&head) == -1) return 0; + +#if defined(HAVE_BSD_NETWORK) && defined(HAVE_IPV6) + if (family == AF_INET6) + fd = socket(PF_INET6, SOCK_DGRAM, 0); +#endif - while(1) - { - len += 10*sizeof(struct ifreq); - - if (!expand_buf(&ifconf, len)) - goto err; - - ifc.ifc_len = len; - ifc.ifc_buf = ifconf.iov_base; - - if (ioctl(fd, SIOCGIFCONF, &ifc) == -1) - { - if (errno != EINVAL || lastlen != 0) - goto err; - } - else - { - if (ifc.ifc_len == lastlen) - break; /* got a big enough buffer now */ - lastlen = ifc.ifc_len; - } - } - - for (ptr = ifc.ifc_buf; ptr < (char *)(ifc.ifc_buf + ifc.ifc_len); ptr += len) + for (addrs = head; addrs; addrs = addrs->ifa_next) { - /* subsequent entries may not be aligned, so copy into - an aligned buffer to avoid nasty complaints about - unaligned accesses. */ - - len = sizeof(struct ifreq); - -#ifdef HAVE_SOCKADDR_SA_LEN - ifr = (struct ifreq *)ptr; - if (ifr->ifr_addr.sa_len > sizeof(ifr->ifr_ifru)) - len = ifr->ifr_addr.sa_len + offsetof(struct ifreq, ifr_ifru); -#endif - - if (!expand_buf(&ifreq, len)) - goto err; - - ifr = (struct ifreq *)ifreq.iov_base; - memcpy(ifr, ptr, len); - - if (ifr->ifr_addr.sa_family == family) + if (addrs->ifa_addr->sa_family == family) { + int iface_index = if_nametoindex(addrs->ifa_name); + + if (iface_index == 0 || !addrs->ifa_addr || + (!addrs->ifa_netmask && family != AF_LINK)) + continue; + if (family == AF_INET) { struct in_addr addr, netmask, broadcast; - broadcast.s_addr = 0; - addr = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr; - if (ioctl(fd, SIOCGIFNETMASK, ifr) == -1) + addr = ((struct sockaddr_in *) addrs->ifa_addr)->sin_addr; +#ifdef HAVE_BSD_NETWORK + if (del_family == AF_INET && del_addr.addr.addr4.s_addr == addr.s_addr) continue; - netmask = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr; - if (ioctl(fd, SIOCGIFBRDADDR, ifr) != -1) - broadcast = ((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr; - if (!((*callback)(addr, - (int)if_nametoindex(ifr->ifr_name), - netmask, broadcast, - parm))) +#endif + netmask = ((struct sockaddr_in *) addrs->ifa_netmask)->sin_addr; + if (addrs->ifa_broadaddr) + broadcast = ((struct sockaddr_in *) addrs->ifa_broadaddr)->sin_addr; + else + broadcast.s_addr = 0; + if (!((*callback)(addr, iface_index, NULL, netmask, broadcast, parm))) goto err; } #ifdef HAVE_IPV6 else if (family == AF_INET6) { - struct in6_addr *addr = &((struct sockaddr_in6 *)&ifr->ifr_addr)->sin6_addr; + struct in6_addr *addr = &((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_addr; + unsigned char *netmask = (unsigned char *) &((struct sockaddr_in6 *) addrs->ifa_netmask)->sin6_addr; + int scope_id = ((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_scope_id; + int i, j, prefix = 0; + u32 valid = 0xffffffff, preferred = 0xffffffff; + int flags = 0; +#ifdef HAVE_BSD_NETWORK + if (del_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&del_addr.addr.addr6, addr)) + continue; +#endif +#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) + struct in6_ifreq ifr6; + + memset(&ifr6, 0, sizeof(ifr6)); + strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name)); + + ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr); + if (fd != -1 && ioctl(fd, SIOCGIFAFLAG_IN6, &ifr6) != -1) + { + if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE) + flags |= IFACE_TENTATIVE; + + if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DEPRECATED) + flags |= IFACE_DEPRECATED; + +#ifdef IN6_IFF_TEMPORARY + if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_TEMPORARY))) + flags |= IFACE_PERMANENT; +#endif + +#ifdef IN6_IFF_PRIVACY + if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_PRIVACY))) + flags |= IFACE_PERMANENT; +#endif + } + + ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr); + if (fd != -1 && ioctl(fd, SIOCGIFALIFETIME_IN6, &ifr6) != -1) + { + valid = ifr6.ifr_ifru.ifru_lifetime.ia6t_vltime; + preferred = ifr6.ifr_ifru.ifru_lifetime.ia6t_pltime; + } +#endif + + for (i = 0; i < IN6ADDRSZ; i++, prefix += 8) + if (netmask[i] != 0xff) + break; + + if (i != IN6ADDRSZ && netmask[i]) + for (j = 7; j > 0; j--, prefix++) + if ((netmask[i] & (1 << j)) == 0) + break; + /* voodoo to clear interface field in address */ if (!option_bool(OPT_NOWILD) && IN6_IS_ADDR_LINKLOCAL(addr)) { addr->s6_addr[2] = 0; addr->s6_addr[3] = 0; - } - if (!((*callback)(addr, - (int)((struct sockaddr_in6 *)&ifr->ifr_addr)->sin6_scope_id, - (int)if_nametoindex(ifr->ifr_name), - parm))) + } + + if (!((*callback)(addr, prefix, scope_id, iface_index, flags, + (int) preferred, (int)valid, parm))) + goto err; + } +#endif /* HAVE_IPV6 */ + +#ifdef HAVE_DHCP6 + else if (family == AF_LINK) + { + /* Assume ethernet again here */ + struct sockaddr_dl *sdl = (struct sockaddr_dl *) addrs->ifa_addr; + if (sdl->sdl_alen != 0 && + !((*callback)(iface_index, ARPHRD_ETHER, LLADDR(sdl), sdl->sdl_alen, parm))) goto err; } -#endif +#endif } } @@ -189,12 +236,14 @@ int iface_enumerate(int family, void *parm, int (*callback)()) err: errsav = errno; - close(fd); + freeifaddrs(head); + if (fd != -1) + close(fd); errno = errsav; return ret; } -#endif +#endif /* defined(HAVE_BSD_NETWORK) || defined(HAVE_SOLARIS_NETWORK) */ #if defined(HAVE_BSD_NETWORK) && defined(HAVE_DHCP) @@ -206,13 +255,10 @@ void init_bpf(void) while (1) { - /* useful size which happens to be sufficient */ - if (expand_buf(&ifreq, sizeof(struct ifreq))) - { - sprintf(ifreq.iov_base, "/dev/bpf%d", i++); - if ((daemon->dhcp_raw_fd = open(ifreq.iov_base, O_RDWR, 0)) != -1) - return; - } + sprintf(daemon->dhcp_buff, "/dev/bpf%d", i++); + if ((daemon->dhcp_raw_fd = open(daemon->dhcp_buff, O_RDWR, 0)) != -1) + return; + if (errno != EBUSY) die(_("cannot create DHCP BPF socket: %s"), NULL, EC_BADNET); } @@ -313,9 +359,90 @@ void send_via_bpf(struct dhcp_packet *mess, size_t len, iov[3].iov_base = mess; iov[3].iov_len = len; - while (writev(daemon->dhcp_raw_fd, iov, 4) == -1 && retry_send()); + while (retry_send(writev(daemon->dhcp_raw_fd, iov, 4))); } +#endif /* defined(HAVE_BSD_NETWORK) && defined(HAVE_DHCP) */ + + +#ifdef HAVE_BSD_NETWORK + +void route_init(void) +{ + /* AF_UNSPEC: all addr families */ + daemon->routefd = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); + + if (daemon->routefd == -1 || !fix_fd(daemon->routefd)) + die(_("cannot create PF_ROUTE socket: %s"), NULL, EC_BADNET); +} + +void route_sock(void) +{ + struct if_msghdr *msg; + int rc = recv(daemon->routefd, daemon->packet, daemon->packet_buff_sz, 0); + + if (rc < 4) + return; + + msg = (struct if_msghdr *)daemon->packet; + + if (rc < msg->ifm_msglen) + return; + + if (msg->ifm_version != RTM_VERSION) + { + static int warned = 0; + if (!warned) + { + my_syslog(LOG_WARNING, _("Unknown protocol version from route socket")); + warned = 1; + } + } + else if (msg->ifm_type == RTM_NEWADDR) + { + del_family = 0; + queue_event(EVENT_NEWADDR); + } + else if (msg->ifm_type == RTM_DELADDR) + { + /* There's a race in the kernel, such that if we run iface_enumerate() immediately + we get a DELADDR event, the deleted address still appears. Here we store the deleted address + in a static variable, and omit it from the set returned by iface_enumerate() */ + int mask = ((struct ifa_msghdr *)msg)->ifam_addrs; + int maskvec[] = { RTA_DST, RTA_GATEWAY, RTA_NETMASK, RTA_GENMASK, + RTA_IFP, RTA_IFA, RTA_AUTHOR, RTA_BRD }; + int of; + unsigned int i; + + for (i = 0, of = sizeof(struct ifa_msghdr); of < rc && i < sizeof(maskvec)/sizeof(maskvec[0]); i++) + if (mask & maskvec[i]) + { + struct sockaddr *sa = (struct sockaddr *)((char *)msg + of); + size_t diff = (sa->sa_len != 0) ? sa->sa_len : sizeof(long); + + if (maskvec[i] == RTA_IFA) + { + del_family = sa->sa_family; + if (del_family == AF_INET) + del_addr.addr.addr4 = ((struct sockaddr_in *)sa)->sin_addr; +#ifdef HAVE_IPV6 + else if (del_family == AF_INET6) + del_addr.addr.addr6 = ((struct sockaddr_in6 *)sa)->sin6_addr; #endif + else + del_family = 0; + } + + of += diff; + /* round up as needed */ + if (diff & (sizeof(long) - 1)) + of += sizeof(long) - (diff & (sizeof(long) - 1)); + } + + queue_event(EVENT_NEWADDR); + } +} + +#endif /* HAVE_BSD_NETWORK */ diff --git a/src/cache.c b/src/cache.c index 77c1972..178d654 100644 --- a/src/cache.c +++ b/src/cache.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 @@ -24,8 +24,6 @@ static struct crec *new_chain = NULL; static int cache_inserted = 0, cache_live_freed = 0, insert_error; static union bigname *big_free = NULL; static int bignames_left, hash_size; -static int uid = 0; -static char *addrbuff = NULL; /* type->string mapping: this is also used by the name-hash function as a mixing table. */ static const struct { @@ -54,7 +52,11 @@ static const struct { { 38, "A6" }, { 39, "DNAME" }, { 41, "OPT" }, + { 43, "DS" }, + { 46, "RRSIG" }, + { 47, "NSEC" }, { 48, "DNSKEY" }, + { 50, "NSEC3" }, { 249, "TKEY" }, { 250, "TSIG" }, { 251, "IXFR" }, @@ -70,14 +72,24 @@ static void cache_link(struct crec *crecp); static void rehash(int size); static void cache_hash(struct crec *crecp); +static unsigned int next_uid(void) +{ + static unsigned int uid = 0; + + uid++; + + /* uid == 0 used to indicate CNAME to interface name. */ + if (uid == SRC_INTERFACE) + uid++; + + return uid; +} + void cache_init(void) { struct crec *crecp; int i; - - if (option_bool(OPT_LOG)) - addrbuff = safe_malloc(ADDRSTRLEN); - + bignames_left = daemon->cachesize/10; if (daemon->cachesize > 0) @@ -88,7 +100,7 @@ void cache_init(void) { cache_link(crecp); crecp->flags = 0; - crecp->uid = uid++; + crecp->uid = next_uid(); } } @@ -172,13 +184,28 @@ static void cache_hash(struct crec *crecp) crecp->hash_next = *up; *up = crecp; } - + +#ifdef HAVE_DNSSEC +static void cache_blockdata_free(struct crec *crecp) +{ + if (crecp->flags & F_DNSKEY) + { + if (crecp->flags & F_DS) + blockdata_free(crecp->addr.sig.keydata); + else + blockdata_free(crecp->addr.key.keydata); + } + else if ((crecp->flags & F_DS) && !(crecp->flags & F_NEG)) + blockdata_free(crecp->addr.ds.keydata); +} +#endif + static void cache_free(struct crec *crecp) { crecp->flags &= ~F_FORWARD; crecp->flags &= ~F_REVERSE; - crecp->uid = uid++; /* invalidate CNAMES pointing to this. */ - + crecp->uid = next_uid(); /* invalidate CNAMES pointing to this. */ + if (cache_tail) cache_tail->next = crecp; else @@ -194,6 +221,10 @@ static void cache_free(struct crec *crecp) big_free = crecp->name.bname; crecp->flags &= ~F_BIGNAME; } + +#ifdef HAVE_DNSSEC + cache_blockdata_free(crecp); +#endif } /* insert a new cache entry at the head of the list (youngest entry) */ @@ -232,12 +263,49 @@ char *cache_get_name(struct crec *crecp) return crecp->name.sname; } +char *cache_get_cname_target(struct crec *crecp) +{ + if (crecp->addr.cname.uid != SRC_INTERFACE) + return cache_get_name(crecp->addr.cname.target.cache); + + return crecp->addr.cname.target.int_name->name; +} + + + +struct crec *cache_enumerate(int init) +{ + static int bucket; + static struct crec *cache; + + if (init) + { + bucket = 0; + cache = NULL; + } + else if (cache && cache->hash_next) + cache = cache->hash_next; + else + { + cache = NULL; + while (bucket < hash_size) + if ((cache = hash_table[bucket++])) + break; + } + + return cache; +} + static int is_outdated_cname_pointer(struct crec *crecp) { - if (!(crecp->flags & F_CNAME)) + if (!(crecp->flags & F_CNAME) || crecp->addr.cname.uid == SRC_INTERFACE) return 0; - if (crecp->addr.cname.cache && crecp->addr.cname.uid == crecp->addr.cname.cache->uid) + /* NB. record may be reused as DS or DNSKEY, where uid is + overloaded for something completely different */ + if (crecp->addr.cname.target.cache && + (crecp->addr.cname.target.cache->flags & (F_IPV4 | F_IPV6 | F_CNAME)) && + crecp->addr.cname.uid == crecp->addr.cname.target.cache->uid) return 0; return 1; @@ -254,7 +322,7 @@ static int is_expired(time_t now, struct crec *crecp) return 1; } -static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags) +static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags) { /* Scan and remove old entries. If (flags & F_FORWARD) then remove any forward entries for name and any expired @@ -263,8 +331,8 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign entries in the whole cache. If (flags == 0) remove any expired entries in the whole cache. - In the flags & F_FORWARD case, the return code is valid, and returns zero if the - name exists in the cache as a HOSTS or DHCP entry (these are never deleted) + In the flags & F_FORWARD case, the return code is valid, and returns a non-NULL pointer + to a cache entry if the name exists in the cache as a HOSTS or DHCP entry (these are never deleted) We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal> so that when we hit an entry which isn't reverse and is immortal, we're done. */ @@ -274,27 +342,52 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign if (flags & F_FORWARD) { for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next) - if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp)) - { - *up = crecp->hash_next; - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - { - cache_unlink(crecp); - cache_free(crecp); - } - } - else if ((crecp->flags & F_FORWARD) && - ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || ((crecp->flags | flags) & F_CNAME)) && - hostname_isequal(cache_get_name(crecp), name)) - { - if (crecp->flags & (F_HOSTS | F_DHCP)) - return 0; - *up = crecp->hash_next; - cache_unlink(crecp); - cache_free(crecp); - } - else + { + if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp)) + { + *up = crecp->hash_next; + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) + { + cache_unlink(crecp); + cache_free(crecp); + } + continue; + } + + if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name)) + { + /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */ + if ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || + (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS)))) + { + if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) + return crecp; + *up = crecp->hash_next; + cache_unlink(crecp); + cache_free(crecp); + continue; + } + +#ifdef HAVE_DNSSEC + /* Deletion has to be class-sensitive for DS, DNSKEY, RRSIG, also + type-covered sensitive for RRSIG */ + if ((flags & (F_DNSKEY | F_DS)) && + (flags & (F_DNSKEY | F_DS)) == (crecp->flags & (F_DNSKEY | F_DS)) && + crecp->uid == addr->addr.dnssec.class && + (!((flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) || + crecp->addr.sig.type_covered == addr->addr.dnssec.type)) + { + if (crecp->flags & F_CONFIG) + return crecp; + *up = crecp->hash_next; + cache_unlink(crecp); + cache_free(crecp); + continue; + } +#endif + } up = &crecp->hash_next; + } } else { @@ -311,13 +404,13 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign if (is_expired(now, crecp)) { *up = crecp->hash_next; - if (!(crecp->flags & (F_HOSTS | F_DHCP))) + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) { cache_unlink(crecp); cache_free(crecp); } } - else if (!(crecp->flags & (F_HOSTS | F_DHCP)) && + else if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && (flags & crecp->flags & F_REVERSE) && (flags & crecp->flags & (F_IPV4 | F_IPV6)) && memcmp(&crecp->addr.addr, addr, addrlen) == 0) @@ -330,7 +423,7 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign up = &crecp->hash_next; } - return 1; + return NULL; } /* Note: The normal calling sequence is @@ -364,17 +457,42 @@ struct crec *cache_insert(char *name, struct all_addr *addr, int freed_all = flags & F_REVERSE; int free_avail = 0; - log_query(flags | F_UPSTREAM, name, addr, NULL); + /* Don't log DNSSEC records here, done elsewhere */ + if (flags & (F_IPV4 | F_IPV6 | F_CNAME)) + { + log_query(flags | F_UPSTREAM, name, addr, NULL); + /* Don't mess with TTL for DNSSEC records. */ + if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl) + ttl = daemon->max_cache_ttl; + if (daemon->min_cache_ttl != 0 && daemon->min_cache_ttl > ttl) + ttl = daemon->min_cache_ttl; + } /* if previous insertion failed give up now. */ if (insert_error) return NULL; - + /* First remove any expired entries and entries for the name/address we - are currently inserting. Fail is we attempt to delete a name from - /etc/hosts or DHCP. */ - if (!cache_scan_free(name, addr, now, flags)) + are currently inserting. */ + if ((new = cache_scan_free(name, addr, now, flags))) { + /* We're trying to insert a record over one from + /etc/hosts or DHCP, or other config. If the + existing record is for an A or AAAA and + the record we're trying to insert is the same, + just drop the insert, but don't error the whole process. */ + if ((flags & (F_IPV4 | F_IPV6)) && (flags & F_FORWARD)) + { + if ((flags & F_IPV4) && (new->flags & F_IPV4) && + new->addr.addr.addr.addr4.s_addr == addr->addr.addr4.s_addr) + return new; +#ifdef HAVE_IPV6 + else if ((flags & F_IPV6) && (new->flags & F_IPV6) && + IN6_ARE_ADDR_EQUAL(&new->addr.addr.addr.addr6, &addr->addr.addr6)) + return new; +#endif + } + insert_error = 1; return NULL; } @@ -399,14 +517,32 @@ struct crec *cache_insert(char *name, struct all_addr *addr, insert. Once in this state, all inserts will probably fail. */ if (free_avail) { + static int warned = 0; + if (!warned) + { + my_syslog(LOG_ERR, _("Internal error in cache.")); + warned = 1; + } insert_error = 1; return NULL; } if (freed_all) { + struct all_addr free_addr = new->addr.addr;; + +#ifdef HAVE_DNSSEC + /* For DNSSEC records, addr holds class and type_covered for RRSIG */ + if (new->flags & (F_DS | F_DNSKEY)) + { + free_addr.addr.dnssec.class = new->uid; + if ((new->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) + free_addr.addr.dnssec.type = new->addr.sig.type_covered; + } +#endif + free_avail = 1; /* Must be free space now. */ - cache_scan_free(cache_get_name(new), &new->addr.addr, now, new->flags); + cache_scan_free(cache_get_name(new), &free_addr, now, new->flags); cache_live_freed++; } else @@ -418,7 +554,7 @@ struct crec *cache_insert(char *name, struct all_addr *addr, } /* Check if we need to and can allocate extra memory for a long name. - If that fails, give up now. */ + If that fails, give up now, always succeed for DNSSEC records. */ if (name && (strlen(name) > SMALLDNAME-1)) { if (big_free) @@ -426,13 +562,13 @@ struct crec *cache_insert(char *name, struct all_addr *addr, big_name = big_free; big_free = big_free->next; } - else if (!bignames_left || + else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) || !(big_name = (union bigname *)whine_malloc(sizeof(union bigname)))) { insert_error = 1; return NULL; } - else + else if (bignames_left != 0) bignames_left--; } @@ -455,10 +591,15 @@ struct crec *cache_insert(char *name, struct all_addr *addr, *cache_get_name(new) = 0; if (addr) - new->addr.addr = *addr; - else - new->addr.cname.cache = NULL; - + { +#ifdef HAVE_DNSSEC + if (flags & (F_DS | F_DNSKEY)) + new->uid = addr->addr.dnssec.class; + else +#endif + new->addr.addr = *addr; + } + new->ttd = now + (time_t)ttl; new->next = new_chain; new_chain = new; @@ -489,10 +630,13 @@ void cache_end_insert(void) new_chain = NULL; } -struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned short prot) +struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot) { struct crec *ans; + int no_rr = prot & F_NO_RR; + prot &= ~F_NO_RR; + if (crecp) /* iterating */ ans = crecp->next; else @@ -509,10 +653,13 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp)) { if ((crecp->flags & F_FORWARD) && +#ifdef HAVE_DNSSEC + (((crecp->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) && +#endif (crecp->flags & prot) && hostname_isequal(cache_get_name(crecp), name)) { - if (crecp->flags & (F_HOSTS | F_DHCP)) + if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) { *chainp = crecp; chainp = &crecp->next; @@ -537,7 +684,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi } else { - if (!insert) + if (!insert && !no_rr) { insert = up; ins_flags = crecp->flags & (F_REVERSE | F_IMMORTAL); @@ -553,7 +700,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi { /* expired entry, free it */ *up = crecp->hash_next; - if (!(crecp->flags & (F_HOSTS | F_DHCP))) + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) { cache_unlink(crecp); cache_free(crecp); @@ -566,7 +713,10 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi if (ans && (ans->flags & F_FORWARD) && - (ans->flags & prot) && +#ifdef HAVE_DNSSEC + (((ans->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) && +#endif + (ans->flags & prot) && hostname_isequal(cache_get_name(ans), name)) return ans; @@ -574,7 +724,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi } struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, - time_t now, unsigned short prot) + time_t now, unsigned int prot) { struct crec *ans; #ifdef HAVE_IPV6 @@ -603,7 +753,7 @@ struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, if ((crecp->flags & prot) && memcmp(&crecp->addr.addr, addr, addrlen) == 0) { - if (crecp->flags & (F_HOSTS | F_DHCP)) + if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) { *chainp = crecp; chainp = &crecp->next; @@ -619,7 +769,7 @@ struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, else { *up = crecp->hash_next; - if (!(crecp->flags & (F_HOSTS | F_DHCP))) + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) { cache_unlink(crecp); cache_free(crecp); @@ -638,12 +788,31 @@ struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, return NULL; } +static void add_hosts_cname(struct crec *target) +{ + struct crec *crec; + struct cname *a; + + for (a = daemon->cnames; a; a = a->next) + if (hostname_isequal(cache_get_name(target), a->target) && + (crec = whine_malloc(sizeof(struct crec)))) + { + crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME; + crec->name.namep = a->alias; + crec->addr.cname.target.cache = target; + crec->addr.cname.uid = target->uid; + crec->uid = next_uid(); + cache_hash(crec); + add_hosts_cname(crec); /* handle chains */ + } +} + static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrlen, - unsigned short flags, int index, int addr_dup) + unsigned int index, struct crec **rhash, int hashsz) { - struct crec *lookup = cache_find_by_name(NULL, cache->name.sname, 0, flags & (F_IPV4 | F_IPV6)); + struct crec *lookup = cache_find_by_name(NULL, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6)); int i, nameexists = 0; - struct cname *a; + unsigned int j; /* Remove duplicates in hosts files. */ if (lookup && (lookup->flags & F_HOSTS)) @@ -657,47 +826,58 @@ static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrl } /* Ensure there is only one address -> name mapping (first one trumps) - We do this by steam here, first we see if the address is the same as - the last one we saw, which eliminates most in the case of an ad-block - file with thousands of entries for the same address. - Then we search and bail at the first matching address that came from - a HOSTS file. Since the first host entry gets reverse, we know - then that it must exist without searching exhaustively for it. */ + We do this by steam here, The entries are kept in hash chains, linked + by ->next (which is unused at this point) held in hash buckets in + the array rhash, hashed on address. Note that rhash and the values + in ->next are only valid whilst reading hosts files: the buckets are + then freed, and the ->next pointer used for other things. + + Only insert each unique address once into this hashing structure. + + This complexity avoids O(n^2) divergent CPU use whilst reading + large (10000 entry) hosts files. + + Note that we only do this process when bulk-reading hosts files, + for incremental reads, rhash is NULL, and we use cache lookups + instead. + */ - if (addr_dup) - flags &= ~F_REVERSE; + if (rhash) + { + /* hash address */ + for (j = 0, i = 0; i < addrlen; i++) + j = (j*2 +((unsigned char *)addr)[i]) % hashsz; + + for (lookup = rhash[j]; lookup; lookup = lookup->next) + if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) && + memcmp(&lookup->addr.addr, addr, addrlen) == 0) + { + cache->flags &= ~F_REVERSE; + break; + } + + /* maintain address hash chain, insert new unique address */ + if (!lookup) + { + cache->next = rhash[j]; + rhash[j] = cache; + } + } else - for (i=0; i<hash_size; i++) - { - for (lookup = hash_table[i]; lookup; lookup = lookup->hash_next) - if ((lookup->flags & F_HOSTS) && - (lookup->flags & flags & (F_IPV4 | F_IPV6)) && - memcmp(&lookup->addr.addr, addr, addrlen) == 0) - { - flags &= ~F_REVERSE; - break; - } - if (lookup) - break; - } - - cache->flags = flags; + { + /* incremental read, lookup in cache */ + lookup = cache_find_by_addr(NULL, addr, 0, cache->flags & (F_IPV4 | F_IPV6)); + if (lookup && lookup->flags & F_HOSTS) + cache->flags &= ~F_REVERSE; + } + cache->uid = index; - memcpy(&cache->addr.addr, addr, addrlen); + memcpy(&cache->addr.addr, addr, addrlen); cache_hash(cache); /* don't need to do alias stuff for second and subsequent addresses. */ if (!nameexists) - for (a = daemon->cnames; a; a = a->next) - if (hostname_isequal(cache->name.sname, a->target) && - (lookup = whine_malloc(sizeof(struct crec)))) - { - lookup->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_HOSTS | F_CNAME; - lookup->name.namep = a->alias; - lookup->addr.cname.cache = cache; - lookup->addr.cname.uid = index; - cache_hash(lookup); - } + add_hosts_cname(cache); } static int eatspace(FILE *f) @@ -747,14 +927,14 @@ static int gettok(FILE *f, char *token) } } -static int read_hostsfile(char *filename, int index, int cache_size) +int read_hostsfile(char *filename, unsigned int index, int cache_size, struct crec **rhash, int hashsz) { FILE *f = fopen(filename, "r"); char *token = daemon->namebuff, *domain_suffix = NULL; int addr_count = 0, name_count = cache_size, lineno = 0; - unsigned short flags = 0, saved_flags = 0; - struct all_addr addr, saved_addr; - int atnl, addrlen = 0, addr_dup; + unsigned short flags = 0; + struct all_addr addr; + int atnl, addrlen = 0; if (!f) { @@ -766,28 +946,20 @@ static int read_hostsfile(char *filename, int index, int cache_size) while ((atnl = gettok(f, token)) != EOF) { - addr_dup = 0; lineno++; -#ifdef HAVE_IPV6 if (inet_pton(AF_INET, token, &addr) > 0) { flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4; addrlen = INADDRSZ; domain_suffix = get_domain(addr.addr.addr4); } +#ifdef HAVE_IPV6 else if (inet_pton(AF_INET6, token, &addr) > 0) { flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6; addrlen = IN6ADDRSZ; - domain_suffix = daemon->domain_suffix; - } -#else - if ((addr.addr.addr4.s_addr = inet_addr(token)) != (in_addr_t) -1) - { - flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4; - addrlen = INADDRSZ; - domain_suffix = get_domain(addr.addr.addr4); + domain_suffix = get_domain6(&addr.addr.addr6); } #endif else @@ -798,18 +970,10 @@ static int read_hostsfile(char *filename, int index, int cache_size) continue; } - if (saved_flags == flags && memcmp(&addr, &saved_addr, addrlen) == 0) - addr_dup = 1; - else - { - saved_flags = flags; - saved_addr = addr; - } - addr_count++; /* rehash every 1000 names. */ - if ((name_count - cache_size) > 1000) + if (rhash && ((name_count - cache_size) > 1000)) { rehash(name_count); cache_size = name_count; @@ -836,14 +1000,15 @@ static int read_hostsfile(char *filename, int index, int cache_size) strcpy(cache->name.sname, canon); strcat(cache->name.sname, "."); strcat(cache->name.sname, domain_suffix); - add_hosts_entry(cache, &addr, addrlen, flags, index, addr_dup); - addr_dup = 1; + cache->flags = flags; + add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); name_count++; } if ((cache = whine_malloc(sizeof(struct crec) + strlen(canon)+1-SMALLDNAME))) { strcpy(cache->name.sname, canon); - add_hosts_entry(cache, &addr, addrlen, flags, index, addr_dup); + cache->flags = flags; + add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); name_count++; } free(canon); @@ -855,7 +1020,9 @@ static int read_hostsfile(char *filename, int index, int cache_size) } fclose(f); - rehash(name_count); + + if (rhash) + rehash(name_count); my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count); @@ -865,16 +1032,26 @@ static int read_hostsfile(char *filename, int index, int cache_size) void cache_reload(void) { struct crec *cache, **up, *tmp; - int i, total_size = daemon->cachesize; + int revhashsz, i, total_size = daemon->cachesize; struct hostsfile *ah; + struct host_record *hr; + struct name_list *nl; + struct cname *a; + struct interface_name *intr; +#ifdef HAVE_DNSSEC + struct ds_config *ds; +#endif cache_inserted = cache_live_freed = 0; for (i=0; i<hash_size; i++) for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp) { +#ifdef HAVE_DNSSEC + cache_blockdata_free(cache); +#endif tmp = cache->hash_next; - if (cache->flags & F_HOSTS) + if (cache->flags & (F_HOSTS | F_CONFIG)) { *up = cache->hash_next; free(cache); @@ -893,35 +1070,103 @@ void cache_reload(void) up = &cache->hash_next; } + /* Add CNAMEs to interface_names to the cache */ + for (a = daemon->cnames; a; a = a->next) + for (intr = daemon->int_names; intr; intr = intr->next) + if (hostname_isequal(a->target, intr->name) && + ((cache = whine_malloc(sizeof(struct crec))))) + { + cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG; + cache->name.namep = a->alias; + cache->addr.cname.target.int_name = intr; + cache->addr.cname.uid = SRC_INTERFACE; + cache->uid = next_uid(); + cache_hash(cache); + add_hosts_cname(cache); /* handle chains */ + } + +#ifdef HAVE_DNSSEC + for (ds = daemon->ds; ds; ds = ds->next) + if ((cache = whine_malloc(sizeof(struct crec))) && + (cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen))) + { + cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP; + cache->name.namep = ds->name; + cache->addr.ds.keylen = ds->digestlen; + cache->addr.ds.algo = ds->algo; + cache->addr.ds.keytag = ds->keytag; + cache->addr.ds.digest = ds->digest_type; + cache->uid = ds->class; + cache_hash(cache); + } +#endif + + /* borrow the packet buffer for a temporary by-address hash */ + memset(daemon->packet, 0, daemon->packet_buff_sz); + revhashsz = daemon->packet_buff_sz / sizeof(struct crec *); + /* we overwrote the buffer... */ + daemon->srv_save = NULL; + + /* Do host_records in config. */ + for (hr = daemon->host_records; hr; hr = hr->next) + for (nl = hr->names; nl; nl = nl->next) + { + if (hr->addr.s_addr != 0 && + (cache = whine_malloc(sizeof(struct crec)))) + { + cache->name.namep = nl->name; + cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG; + add_hosts_entry(cache, (struct all_addr *)&hr->addr, INADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz); + } +#ifdef HAVE_IPV6 + if (!IN6_IS_ADDR_UNSPECIFIED(&hr->addr6) && + (cache = whine_malloc(sizeof(struct crec)))) + { + cache->name.namep = nl->name; + cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG; + add_hosts_entry(cache, (struct all_addr *)&hr->addr6, IN6ADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz); + } +#endif + } + if (option_bool(OPT_NO_HOSTS) && !daemon->addn_hosts) { if (daemon->cachesize > 0) my_syslog(LOG_INFO, _("cleared cache")); - return; + } + else + { + if (!option_bool(OPT_NO_HOSTS)) + total_size = read_hostsfile(HOSTSFILE, SRC_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz); + + daemon->addn_hosts = expand_filelist(daemon->addn_hosts); + for (ah = daemon->addn_hosts; ah; ah = ah->next) + if (!(ah->flags & AH_INACTIVE)) + total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz); } - if (!option_bool(OPT_NO_HOSTS)) - total_size = read_hostsfile(HOSTSFILE, 0, total_size); - - daemon->addn_hosts = expand_filelist(daemon->addn_hosts); - for (ah = daemon->addn_hosts; ah; ah = ah->next) - if (!(ah->flags & AH_INACTIVE)) - total_size = read_hostsfile(ah->fname, ah->index, total_size); +#ifdef HAVE_INOTIFY + set_dynamic_inotify(AH_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz); +#endif + } -char *get_domain(struct in_addr addr) +#ifdef HAVE_DHCP +struct in_addr a_record_from_hosts(char *name, time_t now) { - struct cond_domain *c; - - for (c = daemon->cond_domain; c; c = c->next) - if (ntohl(addr.s_addr) >= ntohl(c->start.s_addr) && - ntohl(addr.s_addr) <= ntohl(c->end.s_addr)) - return c->domain; + struct crec *crecp = NULL; + struct in_addr ret; + + while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4))) + if (crecp->flags & F_HOSTS) + return *(struct in_addr *)&crecp->addr; - return daemon->domain_suffix; + my_syslog(MS_DHCP | LOG_WARNING, _("No IPv4 address found for %s"), name); + + ret.s_addr = 0; + return ret; } -#ifdef HAVE_DHCP void cache_unhash_dhcp(void) { struct crec *cache, **up; @@ -939,111 +1184,251 @@ void cache_unhash_dhcp(void) up = &cache->hash_next; } -void cache_add_dhcp_entry(char *host_name, - struct in_addr *host_address, time_t ttd) +static void add_dhcp_cname(struct crec *target, time_t ttd) { - struct crec *crec = NULL, *aliasc; - unsigned short flags = F_NAMEP | F_DHCP | F_FORWARD | F_IPV4 | F_REVERSE; - int in_hosts = 0; + struct crec *aliasc; struct cname *a; - while ((crec = cache_find_by_name(crec, host_name, 0, F_IPV4 | F_CNAME))) + for (a = daemon->cnames; a; a = a->next) + if (hostname_isequal(cache_get_name(target), a->target)) + { + if ((aliasc = dhcp_spare)) + dhcp_spare = dhcp_spare->next; + else /* need new one */ + aliasc = whine_malloc(sizeof(struct crec)); + + if (aliasc) + { + aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME | F_CONFIG; + if (ttd == 0) + aliasc->flags |= F_IMMORTAL; + else + aliasc->ttd = ttd; + aliasc->name.namep = a->alias; + aliasc->addr.cname.target.cache = target; + aliasc->addr.cname.uid = target->uid; + aliasc->uid = next_uid(); + cache_hash(aliasc); + add_dhcp_cname(aliasc, ttd); + } + } +} + +void cache_add_dhcp_entry(char *host_name, int prot, + struct all_addr *host_address, time_t ttd) +{ + struct crec *crec = NULL, *fail_crec = NULL; + unsigned short flags = F_IPV4; + int in_hosts = 0; + size_t addrlen = sizeof(struct in_addr); + +#ifdef HAVE_IPV6 + if (prot == AF_INET6) + { + flags = F_IPV6; + addrlen = sizeof(struct in6_addr); + } +#endif + + inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN); + + while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME))) { /* check all addresses associated with name */ - if (crec->flags & F_HOSTS) + if (crec->flags & (F_HOSTS | F_CONFIG)) { - /* if in hosts, don't need DHCP record */ - in_hosts = 1; - if (crec->flags & F_CNAME) my_syslog(MS_DHCP | LOG_WARNING, _("%s is a CNAME, not giving it to the DHCP lease of %s"), - host_name, inet_ntoa(*host_address)); - else if (crec->addr.addr.addr.addr4.s_addr != host_address->s_addr) - { - strcpy(daemon->namebuff, inet_ntoa(crec->addr.addr.addr.addr4)); - my_syslog(MS_DHCP | LOG_WARNING, - _("not giving name %s to the DHCP lease of %s because " - "the name exists in %s with address %s"), - host_name, inet_ntoa(*host_address), - record_source(crec->uid), daemon->namebuff); - } + host_name, daemon->addrbuff); + else if (memcmp(&crec->addr.addr, host_address, addrlen) == 0) + in_hosts = 1; + else + fail_crec = crec; } else if (!(crec->flags & F_DHCP)) { - cache_scan_free(host_name, NULL, 0, crec->flags & (F_IPV4 | F_CNAME | F_FORWARD)); + cache_scan_free(host_name, NULL, 0, crec->flags & (flags | F_CNAME | F_FORWARD)); /* scan_free deletes all addresses associated with name */ break; } } - if (in_hosts) + /* if in hosts, don't need DHCP record */ + if (in_hosts) return; - - if ((crec = cache_find_by_addr(NULL, (struct all_addr *)host_address, 0, F_IPV4))) - { - if (crec->flags & F_NEG) - cache_scan_free(NULL, (struct all_addr *)host_address, 0, F_IPV4 | F_REVERSE); - else - /* avoid multiple reverse mappings */ - flags &= ~F_REVERSE; - } - - if ((crec = dhcp_spare)) + + /* Name in hosts, address doesn't match */ + if (fail_crec) + { + inet_ntop(prot, &fail_crec->addr.addr, daemon->namebuff, MAXDNAME); + my_syslog(MS_DHCP | LOG_WARNING, + _("not giving name %s to the DHCP lease of %s because " + "the name exists in %s with address %s"), + host_name, daemon->addrbuff, + record_source(fail_crec->uid), daemon->namebuff); + return; + } + + if ((crec = cache_find_by_addr(NULL, (struct all_addr *)host_address, 0, flags))) + { + if (crec->flags & F_NEG) + { + flags |= F_REVERSE; + cache_scan_free(NULL, (struct all_addr *)host_address, 0, flags); + } + } + else + flags |= F_REVERSE; + + if ((crec = dhcp_spare)) dhcp_spare = dhcp_spare->next; else /* need new one */ crec = whine_malloc(sizeof(struct crec)); if (crec) /* malloc may fail */ { - crec->flags = flags; + crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD; if (ttd == 0) crec->flags |= F_IMMORTAL; else crec->ttd = ttd; - crec->addr.addr.addr.addr4 = *host_address; + crec->addr.addr = *host_address; crec->name.namep = host_name; - crec->uid = uid++; + crec->uid = next_uid(); cache_hash(crec); - for (a = daemon->cnames; a; a = a->next) - if (hostname_isequal(host_name, a->target)) + add_dhcp_cname(crec, ttd); + } +} +#endif + +int cache_make_stat(struct txt_record *t) +{ + static char *buff = NULL; + static int bufflen = 60; + int len; + struct server *serv, *serv1; + char *p; + + if (!buff && !(buff = whine_malloc(60))) + return 0; + + p = buff; + + switch (t->stat) + { + case TXT_STAT_CACHESIZE: + sprintf(buff+1, "%d", daemon->cachesize); + break; + + case TXT_STAT_INSERTS: + sprintf(buff+1, "%d", cache_inserted); + break; + + case TXT_STAT_EVICTIONS: + sprintf(buff+1, "%d", cache_live_freed); + break; + + case TXT_STAT_MISSES: + sprintf(buff+1, "%u", daemon->queries_forwarded); + break; + + case TXT_STAT_HITS: + sprintf(buff+1, "%u", daemon->local_answer); + break; + +#ifdef HAVE_AUTH + case TXT_STAT_AUTH: + sprintf(buff+1, "%u", daemon->auth_answer); + break; +#endif + + case TXT_STAT_SERVERS: + /* sum counts from different records for same server */ + for (serv = daemon->servers; serv; serv = serv->next) + serv->flags &= ~SERV_COUNTED; + + for (serv = daemon->servers; serv; serv = serv->next) + if (!(serv->flags & + (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND))) { - if ((aliasc = dhcp_spare)) - dhcp_spare = dhcp_spare->next; - else /* need new one */ - aliasc = whine_malloc(sizeof(struct crec)); - - if (aliasc) + char *new, *lenp; + int port, newlen, bytes_avail, bytes_needed; + unsigned int queries = 0, failed_queries = 0; + for (serv1 = serv; serv1; serv1 = serv1->next) + if (!(serv1->flags & + (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)) && + sockaddr_isequal(&serv->addr, &serv1->addr)) + { + serv1->flags |= SERV_COUNTED; + queries += serv1->queries; + failed_queries += serv1->failed_queries; + } + port = prettyprint_addr(&serv->addr, daemon->addrbuff); + lenp = p++; /* length */ + bytes_avail = bufflen - (p - buff ); + bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries); + if (bytes_needed >= bytes_avail) { - aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME; - if (ttd == 0) - aliasc->flags |= F_IMMORTAL; - else - aliasc->ttd = ttd; - aliasc->name.namep = a->alias; - aliasc->addr.cname.cache = crec; - aliasc->addr.cname.uid = crec->uid; - cache_hash(aliasc); + /* expand buffer if necessary */ + newlen = bytes_needed + 1 + bufflen - bytes_avail; + if (!(new = whine_malloc(newlen))) + return 0; + memcpy(new, buff, bufflen); + free(buff); + p = new + (p - buff); + lenp = p - 1; + buff = new; + bufflen = newlen; + bytes_avail = bufflen - (p - buff ); + bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries); } + *lenp = bytes_needed; + p += bytes_needed; } + t->txt = (unsigned char *)buff; + t->len = p - buff; + return 1; } + + len = strlen(buff+1); + t->txt = (unsigned char *)buff; + t->len = len + 1; + *buff = len; + return 1; +} + +/* There can be names in the cache containing control chars, don't + mess up logging or open security holes. */ +static char *sanitise(char *name) +{ + unsigned char *r; + if (name) + for (r = (unsigned char *)name; *r; r++) + if (!isprint((int)*r)) + return "<name unprintable>"; + + return name; } -#endif void dump_cache(time_t now) { struct server *serv, *serv1; + char *t = ""; my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now); my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."), daemon->cachesize, cache_live_freed, cache_inserted); my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"), daemon->queries_forwarded, daemon->local_answer); - - if (!addrbuff && !(addrbuff = whine_malloc(ADDRSTRLEN))) - return; +#ifdef HAVE_AUTH + my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->auth_answer); +#endif +#ifdef HAVE_DNSSEC + blockdata_report(); +#endif /* sum counts from different records for same server */ for (serv = daemon->servers; serv; serv = serv->next) @@ -1064,53 +1449,75 @@ void dump_cache(time_t now) queries += serv1->queries; failed_queries += serv1->failed_queries; } - port = prettyprint_addr(&serv->addr, addrbuff); - my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried or failed %u"), addrbuff, port, queries, failed_queries); + port = prettyprint_addr(&serv->addr, daemon->addrbuff); + my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried or failed %u"), daemon->addrbuff, port, queries, failed_queries); } if (option_bool(OPT_DEBUG) || option_bool(OPT_LOG)) { struct crec *cache ; int i; - my_syslog(LOG_INFO, "Host Address Flags Expires"); + my_syslog(LOG_INFO, "Host Address Flags Expires"); for (i=0; i<hash_size; i++) for (cache = hash_table[i]; cache; cache = cache->hash_next) { - char *a, *p = daemon->namebuff; - p += sprintf(p, "%-40.40s ", cache_get_name(cache)); - if ((cache->flags & F_NEG) && (cache->flags & F_FORWARD)) - a = ""; - else if (cache->flags & F_CNAME) + char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache); + *a = 0; + if (strlen(n) == 0 && !(cache->flags & F_REVERSE)) + n = "<Root>"; + p += sprintf(p, "%-30.30s ", sanitise(n)); + if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache)) + a = sanitise(cache_get_cname_target(cache)); +#ifdef HAVE_DNSSEC + else if (cache->flags & F_DS) { - a = ""; - if (!is_outdated_cname_pointer(cache)) - a = cache_get_name(cache->addr.cname.cache); + if (cache->flags & F_DNSKEY) + /* RRSIG */ + sprintf(a, "%5u %3u %s", cache->addr.sig.keytag, + cache->addr.sig.algo, querystr("", cache->addr.sig.type_covered)); + else if (!(cache->flags & F_NEG)) + sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag, + cache->addr.ds.algo, cache->addr.ds.digest); } -#ifdef HAVE_IPV6 - else + else if (cache->flags & F_DNSKEY) + sprintf(a, "%5u %3u %3u", cache->addr.key.keytag, + cache->addr.key.algo, cache->addr.key.flags); +#endif + else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD)) { - a = addrbuff; + a = daemon->addrbuff; if (cache->flags & F_IPV4) - inet_ntop(AF_INET, &cache->addr.addr, addrbuff, ADDRSTRLEN); + inet_ntop(AF_INET, &cache->addr.addr, a, ADDRSTRLEN); +#ifdef HAVE_IPV6 else if (cache->flags & F_IPV6) - inet_ntop(AF_INET6, &cache->addr.addr, addrbuff, ADDRSTRLEN); + inet_ntop(AF_INET6, &cache->addr.addr, a, ADDRSTRLEN); +#endif } -#else - else - a = inet_ntoa(cache->addr.addr.addr.addr4); + + if (cache->flags & F_IPV4) + t = "4"; + else if (cache->flags & F_IPV6) + t = "6"; + else if (cache->flags & F_CNAME) + t = "C"; +#ifdef HAVE_DNSSEC + else if ((cache->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) + t = "G"; /* DNSKEY and DS set -> RRISG */ + else if (cache->flags & F_DS) + t = "S"; + else if (cache->flags & F_DNSKEY) + t = "K"; #endif - p += sprintf(p, "%-30.30s %s%s%s%s%s%s%s%s%s%s ", a, - cache->flags & F_IPV4 ? "4" : "", - cache->flags & F_IPV6 ? "6" : "", - cache->flags & F_CNAME ? "C" : "", + p += sprintf(p, "%-40.40s %s%s%s%s%s%s%s%s%s ", a, t, cache->flags & F_FORWARD ? "F" : " ", cache->flags & F_REVERSE ? "R" : " ", cache->flags & F_IMMORTAL ? "I" : " ", cache->flags & F_DHCP ? "D" : " ", cache->flags & F_NEG ? "N" : " ", cache->flags & F_NXDOMAIN ? "X" : " ", - cache->flags & F_HOSTS ? "H" : " "); + cache->flags & F_HOSTS ? "H" : " ", + cache->flags & F_DNSSECOK ? "V" : " "); #ifdef HAVE_BROKEN_RTC p += sprintf(p, "%lu", cache->flags & F_IMMORTAL ? 0: (unsigned long)(cache->ttd - now)); #else @@ -1123,65 +1530,106 @@ void dump_cache(time_t now) } } -char *record_source(int index) +char *record_source(unsigned int index) { struct hostsfile *ah; - if (index == 0) + if (index == SRC_CONFIG) + return "config"; + else if (index == SRC_HOSTS) return HOSTSFILE; for (ah = daemon->addn_hosts; ah; ah = ah->next) if (ah->index == index) return ah->fname; - + +#ifdef HAVE_INOTIFY + for (ah = daemon->dynamic_dirs; ah; ah = ah->next) + if (ah->index == index) + return ah->fname; +#endif + return "<unknown>"; } -void querystr(char *str, unsigned short type) +char *querystr(char *desc, unsigned short type) { unsigned int i; - - sprintf(str, "query[type=%d]", type); + int len = 10; /* strlen("type=xxxxx") */ + const char *types = NULL; + static char *buff = NULL; + static int bufflen = 0; + for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++) if (typestr[i].type == type) - sprintf(str,"query[%s]", typestr[i].name); + { + types = typestr[i].name; + len = strlen(types); + break; + } + + len += 3; /* braces, terminator */ + len += strlen(desc); + + if (!buff || bufflen < len) + { + if (buff) + free(buff); + else if (len < 20) + len = 20; + + buff = whine_malloc(len); + bufflen = len; + } + + if (buff) + { + if (types) + sprintf(buff, "%s[%s]", desc, types); + else + sprintf(buff, "%s[type=%d]", desc, type); + } + + return buff ? buff : ""; } void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) { - char *source, *dest = addrbuff; + char *source, *dest = daemon->addrbuff; char *verb = "is"; if (!option_bool(OPT_LOG)) return; + name = sanitise(name); + if (addr) { + if (flags & F_KEYTAG) + sprintf(daemon->addrbuff, arg, addr->addr.keytag); + else + { #ifdef HAVE_IPV6 - inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, - addr, addrbuff, ADDRSTRLEN); + inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, + addr, daemon->addrbuff, ADDRSTRLEN); #else - strncpy(addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN); + strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN); #endif + } } + else + dest = arg; if (flags & F_REVERSE) { dest = name; - name = addrbuff; + name = daemon->addrbuff; } if (flags & F_NEG) { if (flags & F_NXDOMAIN) - { - if (flags & F_IPV4) - dest = "NXDOMAIN-IPv4"; - else if (flags & F_IPV6) - dest = "NXDOMAIN-IPv6"; - else - dest = "NXDOMAIN"; - } + dest = "NXDOMAIN"; else { if (flags & F_IPV4) @@ -1205,6 +1653,10 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) source = arg; else if (flags & F_UPSTREAM) source = "reply"; + else if (flags & F_SECSTAT) + source = "validation"; + else if (flags & F_AUTH) + source = "auth"; else if (flags & F_SERVER) { source = "forwarded"; @@ -1215,12 +1667,34 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) source = arg; verb = "from"; } + else if (flags & F_DNSSEC) + { + source = arg; + verb = "to"; + } + else if (flags & F_IPSET) + { + source = "ipset add"; + dest = name; + name = arg; + verb = daemon->addrbuff; + } else source = "cached"; if (strlen(name) == 0) name = "."; - my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest); + if (option_bool(OPT_EXTRALOG)) + { + int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2); + if (flags & F_NOEXTRA) + my_syslog(LOG_INFO, "* %s/%u %s %s %s %s", daemon->addrbuff2, port, source, name, verb, dest); + else + my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest); + } + else + my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest); } + diff --git a/src/config.h b/src/config.h index 0039c3e..71fba89 100644 --- a/src/config.h +++ b/src/config.h @@ -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 @@ -14,81 +14,49 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#define VERSION "2.57" +#define VERSION "2.74" #define FTABSIZ 150 /* max number of outstanding requests (default) */ #define MAX_PROCS 20 /* max no children for TCP requests */ #define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */ +#define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */ #define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */ +#define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */ +#define KEYBLOCK_LEN 40 /* choose to mininise fragmentation when storing DNSSEC keys */ +#define DNSSEC_WORK 50 /* Max number of queries to validate one question */ #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */ #define FORWARD_TEST 50 /* try all servers every 50 queries */ #define FORWARD_TIME 20 /* or 20 seconds */ #define RANDOM_SOCKS 64 /* max simultaneous random ports */ #define LEASE_RETRY 60 /* on error, retry writing leasefile after LEASE_RETRY seconds */ #define CACHESIZ 150 /* default cache size */ +#define TTL_FLOOR_LIMIT 3600 /* don't allow --min-cache-ttl to raise TTL above this under any circumstances */ #define MAXLEASES 1000 /* maximum number of DHCP leases */ #define PING_WAIT 3 /* wait for ping address-in-use test */ #define PING_CACHE_TIME 30 /* Ping test assumed to be valid this long. */ #define DECLINE_BACKOFF 600 /* disable DECLINEd static addresses for this long */ #define DHCP_PACKET_MAX 16384 /* hard limit on DHCP packet size */ -#define SMALLDNAME 40 /* most domain names are smaller than this */ +#define SMALLDNAME 50 /* most domain names are smaller than this */ +#define CNAME_CHAIN 10 /* chains longer than this atr dropped for loop protection */ #define HOSTSFILE "/etc/hosts" #define ETHERSFILE "/etc/ethers" -#ifdef __uClinux__ -# define RESOLVFILE "/etc/config/resolv.conf" -#else -# define RESOLVFILE "/etc/resolv.conf" -#endif -#define RUNFILE "/var/run/dnsmasq.pid" - -#ifndef LEASEFILE -# if defined(__FreeBSD__) || defined (__OpenBSD__) || defined(__DragonFly__) || defined(__NetBSD__) -# define LEASEFILE "/var/db/dnsmasq.leases" -# elif defined(__sun__) || defined (__sun) -# define LEASEFILE "/var/cache/dnsmasq.leases" -# elif defined(__ANDROID__) -# define LEASEFILE "/data/misc/dhcp/dnsmasq.leases" -# else -# define LEASEFILE "/var/lib/misc/dnsmasq.leases" -# endif -#endif - -#ifndef CONFFILE -# if defined(__FreeBSD__) -# define CONFFILE "/usr/local/etc/dnsmasq.conf" -# else -# define CONFFILE "/etc/dnsmasq.conf" -# endif -#endif - #define DEFLEASE 3600 /* default lease time, 1 hour */ #define CHUSER "nobody" #define CHGRP "dip" -#define NAMESERVER_PORT 53 -#define DHCP_SERVER_PORT 67 -#define DHCP_CLIENT_PORT 68 -#define DHCP_SERVER_ALTPORT 1067 -#define DHCP_CLIENT_ALTPORT 1068 -#define PXE_PORT 4011 -#define TFTP_PORT 69 #define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */ #define LOG_MAX 5 /* log-queue length */ #define RANDFILE "/dev/urandom" -#define DAD_WAIT 20 /* retry binding IPv6 sockets for this long */ -#define EDNS0_OPTION_MAC 5 /* dyndns.org temporary assignment */ - -/* DBUS interface specifics */ -#define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" +#define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" /* Default - may be overridden by config */ #define DNSMASQ_PATH "/uk/org/thekelleys/dnsmasq" - -/* Follows system specific switches. If you run on a - new system, you may want to edit these. - May replace this with Autoconf one day. - -HAVE_LINUX_NETWORK -HAVE_BSD_NETWORK -HAVE_SOLARIS_NETWORK - define exactly one of these to alter interaction with kernel networking. +#define AUTH_TTL 600 /* default TTL for auth DNS */ +#define SOA_REFRESH 1200 /* SOA refresh default */ +#define SOA_RETRY 180 /* SOA retry default */ +#define SOA_EXPIRY 1209600 /* SOA expiry default */ +#define LOOP_TEST_DOMAIN "test" /* domain for loop testing, "test" is reserved by RFC 2606 and won't therefore clash */ +#define LOOP_TEST_TYPE T_TXT + +/* compile-time options: uncomment below to enable or do eg. + make COPTS=-DHAVE_BROKEN_RTC HAVE_BROKEN_RTC define this on embedded systems which don't have an RTC @@ -108,20 +76,16 @@ HAVE_TFTP define this to get dnsmasq's built-in TFTP server. HAVE_DHCP - define this to get dnsmasq's DHCP server. + define this to get dnsmasq's DHCPv4 server. -HAVE_SCRIPT - define this to get the ability to call scripts on lease-change +HAVE_DHCP6 + define this to get dnsmasq's DHCPv6 server. (implies HAVE_DHCP). -HAVE_GETOPT_LONG - define this if you have GNU libc or GNU getopt. - -HAVE_ARC4RANDOM - define this if you have arc4random() to get better security from DNS spoofs - by using really random ids (OpenBSD) +HAVE_SCRIPT + define this to get the ability to call scripts on lease-change. -HAVE_SOCKADDR_SA_LEN - define this if struct sockaddr has sa_len field (*BSD) +HAVE_LUASCRIPT + define this to get the ability to call Lua script on lease-change. (implies HAVE_SCRIPT) HAVE_DBUS define this if you want to link against libdbus, and have dnsmasq @@ -134,56 +98,142 @@ HAVE_IDN included when internationalisation support is built, using the *-i18n makefile targets, even if HAVE_IDN is not explicitly set. -NOTES: - For Linux you should define - HAVE_LINUX_NETWORK - HAVE_GETOPT_LONG - you should NOT define - HAVE_ARC4RANDOM - HAVE_SOCKADDR_SA_LEN - - For *BSD systems you should define - HAVE_BSD_NETWORK - HAVE_SOCKADDR_SA_LEN - and you MAY define - HAVE_ARC4RANDOM - OpenBSD and FreeBSD and NetBSD version 2.0 or later - HAVE_GETOPT_LONG - NetBSD, later FreeBSD - (FreeBSD and OpenBSD only if you link GNU getopt) +HAVE_CONNTRACK + define this to include code which propogates conntrack marks from + incoming DNS queries to the corresponding upstream queries. This adds + a build-dependency on libnetfilter_conntrack, but the resulting binary will + still run happily on a kernel without conntrack support. + +HAVE_IPSET + define this to include the ability to selectively add resolved ip addresses + to given ipsets. + +HAVE_AUTH + define this to include the facility to act as an authoritative DNS + server for one or more zones. + +HAVE_DNSSEC + include DNSSEC validator. + +HAVE_LOOP + include functionality to probe for and remove DNS forwarding loops. + +HAVE_INOTIFY + use the Linux inotify facility to efficiently re-read configuration files. + +NO_IPV6 +NO_TFTP +NO_DHCP +NO_DHCP6 +NO_SCRIPT +NO_LARGEFILE +NO_AUTH +NO_INOTIFY + these are avilable to explictly disable compile time options which would + otherwise be enabled automatically (HAVE_IPV6, >2Gb file sizes) or + which are enabled by default in the distributed source tree. Building dnsmasq + with something like "make COPTS=-DNO_SCRIPT" will do the trick. + +NO_NETTLE_ECC + Don't include the ECDSA cypher in DNSSEC validation. Needed for older Nettle versions. +NO_GMP + Don't use and link against libgmp, Useful if nettle is built with --enable-mini-gmp. + +LEASEFILE +CONFFILE +RESOLVFILE + the default locations of these files are determined below, but may be overridden + in a build command line using COPTS. */ -/* platform independent options- uncomment to enable */ +/* Defining this builds a binary which handles time differently and works better on a system without a + stable RTC (it uses uptime, not epoch time) and writes the DHCP leases file less often to avoid flash wear. +*/ + +/* #define HAVE_BROKEN_RTC */ + +/* The default set of options to build. Built with these options, dnsmasq + has no library dependencies other than libc */ + #define HAVE_DHCP +#define HAVE_DHCP6 #define HAVE_TFTP #define HAVE_SCRIPT -/* #define HAVE_BROKEN_RTC */ -#define HAVE_DBUS +#define HAVE_AUTH +#define HAVE_IPSET +#define HAVE_LOOP + +/* Build options which require external libraries. + + Defining HAVE_<opt>_STATIC as _well_ as HAVE_<opt> will link the library statically. + + You can use "make COPTS=-DHAVE_<opt>" instead of editing these. +*/ + +/* #define HAVE_LUASCRIPT */ +/* #define HAVE_DBUS */ /* #define HAVE_IDN */ +/* #define HAVE_CONNTRACK */ +/* #define HAVE_DNSSEC */ -/* Allow TFTP to be disabled with COPTS=-DNO_TFTP */ -#ifdef NO_TFTP -#undef HAVE_TFTP + +/* Default locations for important system files. */ + +#ifndef LEASEFILE +# if defined(__FreeBSD__) || defined (__OpenBSD__) || defined(__DragonFly__) || defined(__NetBSD__) +# define LEASEFILE "/var/db/dnsmasq.leases" +# elif defined(__sun__) || defined (__sun) +# define LEASEFILE "/var/cache/dnsmasq.leases" +# elif defined(__ANDROID__) +# define LEASEFILE "/data/misc/dhcp/dnsmasq.leases" +# else +# define LEASEFILE "/var/lib/misc/dnsmasq.leases" +# endif #endif -/* Allow DHCP to be disabled with COPTS=-DNO_DHCP */ -#ifdef NO_DHCP -#undef HAVE_DHCP +#ifndef CONFFILE +# if defined(__FreeBSD__) +# define CONFFILE "/usr/local/etc/dnsmasq.conf" +# else +# define CONFFILE "/etc/dnsmasq.conf" +# endif #endif -/* Allow scripts to be disabled with COPTS=-DNO_SCRIPT */ -#ifdef NO_SCRIPT -#undef HAVE_SCRIPT +#ifndef RESOLVFILE +# if defined(__uClinux__) +# define RESOLVFILE "/etc/config/resolv.conf" +# else +# define RESOLVFILE "/etc/resolv.conf" +# endif #endif +#ifndef RUNFILE +# if defined(__ANDROID__) +# define RUNFILE "/data/dnsmasq.pid" +# else +# define RUNFILE "/var/run/dnsmasq.pid" +# endif +#endif +/* platform dependent options: these are determined automatically below -/* platform dependent options. */ +HAVE_LINUX_NETWORK +HAVE_BSD_NETWORK +HAVE_SOLARIS_NETWORK + define exactly one of these to alter interaction with kernel networking. + +HAVE_GETOPT_LONG + defined when GNU-style getopt_long available. + +HAVE_SOCKADDR_SA_LEN + defined if struct sockaddr has sa_len field (*BSD) +*/ /* Must preceed __linux__ since uClinux defines __linux__ too. */ #if defined(__uClinux__) #define HAVE_LINUX_NETWORK #define HAVE_GETOPT_LONG -#undef HAVE_ARC4RANDOM #undef HAVE_SOCKADDR_SA_LEN /* Never use fork() on uClinux. Note that this is subtly different from the --keep-in-foreground option, since it also suppresses forking new @@ -197,7 +247,6 @@ NOTES: ((__UCLIBC_MAJOR__==0) && (__UCLIBC_MINOR__==9) && (__UCLIBC_SUBLEVEL__<21)) # define HAVE_GETOPT_LONG #endif -#undef HAVE_ARC4RANDOM #undef HAVE_SOCKADDR_SA_LEN #if !defined(__ARCH_HAS_MMU__) && !defined(__UCLIBC_HAS_MMU__) # define NO_FORK @@ -212,7 +261,6 @@ NOTES: #elif defined(__linux__) #define HAVE_LINUX_NETWORK #define HAVE_GETOPT_LONG -#undef HAVE_ARC4RANDOM #undef HAVE_SOCKADDR_SA_LEN #elif defined(__FreeBSD__) || \ @@ -224,57 +272,180 @@ NOTES: #if defined(optional_argument) && defined(required_argument) # define HAVE_GETOPT_LONG #endif -#if !defined(__FreeBSD_kernel__) -# define HAVE_ARC4RANDOM -#endif #define HAVE_SOCKADDR_SA_LEN #elif defined(__APPLE__) #define HAVE_BSD_NETWORK #define HAVE_GETOPT_LONG -#define HAVE_ARC4RANDOM #define HAVE_SOCKADDR_SA_LEN /* Define before sys/socket.h is included so we get socklen_t */ #define _BSD_SOCKLEN_T_ - +/* Select the RFC_3542 version of the IPv6 socket API. + Define before netinet6/in6.h is included. */ +#define __APPLE_USE_RFC_3542 +#define NO_IPSET + #elif defined(__NetBSD__) #define HAVE_BSD_NETWORK #define HAVE_GETOPT_LONG -#undef HAVE_ARC4RANDOM #define HAVE_SOCKADDR_SA_LEN #elif defined(__sun) || defined(__sun__) #define HAVE_SOLARIS_NETWORK #define HAVE_GETOPT_LONG -#undef HAVE_ARC4RANDOM #undef HAVE_SOCKADDR_SA_LEN #define ETHER_ADDR_LEN 6 #endif /* Decide if we're going to support IPv6 */ -/* IPv6 can be forced off with "make COPTS=-DNO_IPV6" */ /* We assume that systems which don't have IPv6 headers don't have ntop and pton either */ -#if defined(INET6_ADDRSTRLEN) && defined(IPV6_V6ONLY) && !defined(NO_IPV6) +#if defined(INET6_ADDRSTRLEN) && defined(IPV6_V6ONLY) # define HAVE_IPV6 # define ADDRSTRLEN INET6_ADDRSTRLEN -# if defined(SOL_IPV6) -# define IPV6_LEVEL SOL_IPV6 -# else -# define IPV6_LEVEL IPPROTO_IPV6 +#else +# if !defined(INET_ADDRSTRLEN) +# define INET_ADDRSTRLEN 16 /* 4*3 + 3 dots + NULL */ # endif -#elif defined(INET_ADDRSTRLEN) # undef HAVE_IPV6 # define ADDRSTRLEN INET_ADDRSTRLEN -#else -# undef HAVE_IPV6 -# define ADDRSTRLEN 16 /* 4*3 + 3 dots + NULL */ #endif -/* Can't do scripts without fork */ -#ifdef NOFORK -# undef HAVE_SCRIPT + +/* rules to implement compile-time option dependencies and + the NO_XXX flags */ + +#ifdef NO_IPV6 +#undef HAVE_IPV6 +#endif + +#ifdef NO_TFTP +#undef HAVE_TFTP +#endif + +#ifdef NO_DHCP +#undef HAVE_DHCP +#undef HAVE_DHCP6 +#endif + +#if defined(NO_DHCP6) || !defined(HAVE_IPV6) +#undef HAVE_DHCP6 +#endif + +/* DHCP6 needs DHCP too */ +#ifdef HAVE_DHCP6 +#define HAVE_DHCP #endif +#if defined(NO_SCRIPT) || !defined(HAVE_DHCP) || defined(NO_FORK) +#undef HAVE_SCRIPT +#undef HAVE_LUASCRIPT +#endif + +/* Must HAVE_SCRIPT to HAVE_LUASCRIPT */ +#ifdef HAVE_LUASCRIPT +#define HAVE_SCRIPT +#endif + +#ifdef NO_AUTH +#undef HAVE_AUTH +#endif + +#if defined(NO_IPSET) +#undef HAVE_IPSET +#endif + +#ifdef NO_LOOP +#undef HAVE_LOOP +#endif + +#if defined (HAVE_LINUX_NETWORK) && !defined(NO_INOTIFY) +#define HAVE_INOTIFY +#endif + +/* Define a string indicating which options are in use. + DNSMASQP_COMPILE_OPTS is only defined in dnsmasq.c */ + +#ifdef DNSMASQ_COMPILE_OPTS + +static char *compile_opts = +#ifndef HAVE_IPV6 +"no-" +#endif +"IPv6 " +#ifndef HAVE_GETOPT_LONG +"no-" +#endif +"GNU-getopt " +#ifdef HAVE_BROKEN_RTC +"no-RTC " +#endif +#ifdef NO_FORK +"no-MMU " +#endif +#ifndef HAVE_DBUS +"no-" +#endif +"DBus " +#ifndef LOCALEDIR +"no-" +#endif +"i18n " +#if !defined(LOCALEDIR) && !defined(HAVE_IDN) +"no-" +#endif +"IDN " +#ifndef HAVE_DHCP +"no-" +#endif +"DHCP " +#if defined(HAVE_DHCP) +# if !defined (HAVE_DHCP6) + "no-" +# endif + "DHCPv6 " +# if !defined(HAVE_SCRIPT) + "no-scripts " +# else +# if !defined(HAVE_LUASCRIPT) + "no-" +# endif + "Lua " +# endif +#endif +#ifndef HAVE_TFTP +"no-" +#endif +"TFTP " +#ifndef HAVE_CONNTRACK +"no-" +#endif +"conntrack " +#ifndef HAVE_IPSET +"no-" +#endif +"ipset " +#ifndef HAVE_AUTH +"no-" +#endif +"auth " +#ifndef HAVE_DNSSEC +"no-" +#endif +"DNSSEC " +#ifndef HAVE_LOOP +"no-" +#endif +"loop-detect " +#ifndef HAVE_INOTIFY +"no-" +#endif +"inotify"; + + +#endif + + + diff --git a/src/conntrack.c b/src/conntrack.c new file mode 100644 index 0000000..0fa2da9 --- /dev/null +++ b/src/conntrack.c @@ -0,0 +1,90 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_CONNTRACK + +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> + +static int gotit = 0; /* yuck */ + +static int callback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data); + +int get_incoming_mark(union mysockaddr *peer_addr, struct all_addr *local_addr, int istcp, unsigned int *markp) +{ + struct nf_conntrack *ct; + struct nfct_handle *h; + + gotit = 0; + + if ((ct = nfct_new())) + { + nfct_set_attr_u8(ct, ATTR_L4PROTO, istcp ? IPPROTO_TCP : IPPROTO_UDP); + nfct_set_attr_u16(ct, ATTR_PORT_DST, htons(daemon->port)); + +#ifdef HAVE_IPV6 + if (peer_addr->sa.sa_family == AF_INET6) + { + nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(ct, ATTR_IPV6_SRC, peer_addr->in6.sin6_addr.s6_addr); + nfct_set_attr_u16(ct, ATTR_PORT_SRC, peer_addr->in6.sin6_port); + nfct_set_attr(ct, ATTR_IPV6_DST, local_addr->addr.addr6.s6_addr); + } + else +#endif + { + nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(ct, ATTR_IPV4_SRC, peer_addr->in.sin_addr.s_addr); + nfct_set_attr_u16(ct, ATTR_PORT_SRC, peer_addr->in.sin_port); + nfct_set_attr_u32(ct, ATTR_IPV4_DST, local_addr->addr.addr4.s_addr); + } + + + if ((h = nfct_open(CONNTRACK, 0))) + { + nfct_callback_register(h, NFCT_T_ALL, callback, (void *)markp); + if (nfct_query(h, NFCT_Q_GET, ct) == -1) + { + static int warned = 0; + if (!warned) + { + my_syslog(LOG_ERR, _("Conntrack connection mark retrieval failed: %s"), strerror(errno)); + warned = 1; + } + } + nfct_close(h); + } + nfct_destroy(ct); + } + + return gotit; +} + +static int callback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) +{ + unsigned int *ret = (unsigned int *)data; + *ret = nfct_get_attr_u32(ct, ATTR_MARK); + (void)type; /* eliminate warning */ + gotit = 1; + + return NFCT_CB_CONTINUE; +} + +#endif + + + @@ -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 @@ -20,7 +20,7 @@ #include <dbus/dbus.h> -const char* introspection_xml = +const char* introspection_xml_template = "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" "<node name=\"" DNSMASQ_PATH "\">\n" @@ -29,15 +29,32 @@ const char* introspection_xml = " <arg name=\"data\" direction=\"out\" type=\"s\"/>\n" " </method>\n" " </interface>\n" -" <interface name=\"" DNSMASQ_SERVICE "\">\n" +" <interface name=\"%s\">\n" " <method name=\"ClearCache\">\n" " </method>\n" " <method name=\"GetVersion\">\n" " <arg name=\"version\" direction=\"out\" type=\"s\"/>\n" " </method>\n" +#ifdef HAVE_LOOP +" <method name=\"GetLoopServers\">\n" +" <arg name=\"server\" direction=\"out\" type=\"as\"/>\n" +" </method>\n" +#endif " <method name=\"SetServers\">\n" " <arg name=\"servers\" direction=\"in\" type=\"av\"/>\n" " </method>\n" +" <method name=\"SetDomainServers\">\n" +" <arg name=\"servers\" direction=\"in\" type=\"as\"/>\n" +" </method>\n" +" <method name=\"SetServersEx\">\n" +" <arg name=\"servers\" direction=\"in\" type=\"aas\"/>\n" +" </method>\n" +" <method name=\"SetFilterWin2KOption\">\n" +" <arg name=\"filterwin2k\" direction=\"in\" type=\"b\"/>\n" +" </method>\n" +" <method name=\"SetBogusPrivOption\">\n" +" <arg name=\"boguspriv\" direction=\"in\" type=\"b\"/>\n" +" </method>\n" " <signal name=\"DhcpLeaseAdded\">\n" " <arg name=\"ipaddr\" type=\"s\"/>\n" " <arg name=\"hwaddr\" type=\"s\"/>\n" @@ -53,9 +70,26 @@ const char* introspection_xml = " <arg name=\"hwaddr\" type=\"s\"/>\n" " <arg name=\"hostname\" type=\"s\"/>\n" " </signal>\n" +#ifdef HAVE_DHCP +" <method name=\"AddDhcpLease\">\n" +" <arg name=\"ipaddr\" type=\"s\"/>\n" +" <arg name=\"hwaddr\" type=\"s\"/>\n" +" <arg name=\"hostname\" type=\"ay\"/>\n" +" <arg name=\"clid\" type=\"ay\"/>\n" +" <arg name=\"lease_duration\" type=\"u\"/>\n" +" <arg name=\"ia_id\" type=\"u\"/>\n" +" <arg name=\"is_temporary\" type=\"b\"/>\n" +" </method>\n" +" <method name=\"DeleteDhcpLease\">\n" +" <arg name=\"ipaddr\" type=\"s\"/>\n" +" <arg name=\"success\" type=\"b\" direction=\"out\"/>\n" +" </method>\n" +#endif " </interface>\n" "</node>\n"; +static char *introspection_xml = NULL; + struct watch { DBusWatch *watch; struct watch *next; @@ -83,33 +117,32 @@ static dbus_bool_t add_watch(DBusWatch *watch, void *data) static void remove_watch(DBusWatch *watch, void *data) { - struct watch **up, *w; + struct watch **up, *w, *tmp; - for (up = &(daemon->watches), w = daemon->watches; w; w = w->next) - if (w->watch == watch) - { - *up = w->next; - free(w); - } - else - up = &(w->next); + for (up = &(daemon->watches), w = daemon->watches; w; w = tmp) + { + tmp = w->next; + if (w->watch == watch) + { + *up = tmp; + free(w); + } + else + up = &(w->next); + } w = data; /* no warning */ } static void dbus_read_servers(DBusMessage *message) { - struct server *serv, *tmp, **up; DBusMessageIter iter; union mysockaddr addr, source_addr; char *domain; dbus_message_iter_init(message, &iter); - - /* mark everything from DBUS */ - for (serv = daemon->servers; serv; serv = serv->next) - if (serv->flags & SERV_FROM_DBUS) - serv->flags |= SERV_MARK; + + mark_servers(SERV_FROM_DBUS); while (1) { @@ -143,13 +176,16 @@ static void dbus_read_servers(DBusMessage *message) dbus_message_iter_get_basic(&iter, &p[i]); dbus_message_iter_next (&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BYTE) - break; + { + i++; + break; + } } #ifndef HAVE_IPV6 my_syslog(LOG_WARNING, _("attempt to set an IPv6 server address via DBus - no IPv6 support")); #else - if (i == sizeof(struct in6_addr)-1) + if (i == sizeof(struct in6_addr)) { memcpy(&addr.in6.sin6_addr, p, sizeof(struct in6_addr)); #ifdef HAVE_SOCKADDR_SA_LEN @@ -169,6 +205,7 @@ static void dbus_read_servers(DBusMessage *message) /* At the end */ break; + /* process each domain */ do { if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { @@ -179,123 +216,500 @@ static void dbus_read_servers(DBusMessage *message) domain = NULL; if (!skip) - { - /* See if this is already there, and unmark */ - for (serv = daemon->servers; serv; serv = serv->next) - if ((serv->flags & SERV_FROM_DBUS) && - (serv->flags & SERV_MARK)) + add_update_server(SERV_FROM_DBUS, &addr, &source_addr, NULL, domain); + + } while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING); + } + + /* unlink and free anything still marked. */ + cleanup_servers(); +} + +#ifdef HAVE_LOOP +static DBusMessage *dbus_reply_server_loop(DBusMessage *message) +{ + DBusMessageIter args, args_iter; + struct server *serv; + DBusMessage *reply = dbus_message_new_method_return(message); + + dbus_message_iter_init_append (reply, &args); + dbus_message_iter_open_container (&args, DBUS_TYPE_ARRAY,DBUS_TYPE_STRING_AS_STRING, &args_iter); + + for (serv = daemon->servers; serv; serv = serv->next) + if (serv->flags & SERV_LOOP) + { + prettyprint_addr(&serv->addr, daemon->addrbuff); + dbus_message_iter_append_basic (&args_iter, DBUS_TYPE_STRING, &daemon->addrbuff); + } + + dbus_message_iter_close_container (&args, &args_iter); + + return reply; +} +#endif + +static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) +{ + DBusMessageIter iter, array_iter, string_iter; + DBusMessage *error = NULL; + const char *addr_err; + char *dup = NULL; + + if (!dbus_message_iter_init(message, &iter)) + { + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Failed to initialize dbus message iter"); + } + + /* check that the message contains an array of arrays */ + if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) || + (dbus_message_iter_get_element_type(&iter) != (strings ? DBUS_TYPE_STRING : DBUS_TYPE_ARRAY))) + { + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + strings ? "Expected array of string" : "Expected array of string arrays"); + } + + mark_servers(SERV_FROM_DBUS); + + /* array_iter points to each "as" element in the outer array */ + dbus_message_iter_recurse(&iter, &array_iter); + while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) + { + const char *str = NULL; + union mysockaddr addr, source_addr; + int flags = 0; + char interface[IF_NAMESIZE]; + char *str_addr, *str_domain = NULL; + + if (strings) + { + dbus_message_iter_get_basic(&array_iter, &str); + if (!str || !strlen (str)) + { + error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Empty string"); + break; + } + + /* dup the string because it gets modified during parsing */ + if (dup) + free(dup); + if (!(dup = str_domain = whine_malloc(strlen(str)+1))) + break; + + strcpy(str_domain, str); + + /* point to address part of old string for error message */ + if ((str_addr = strrchr(str, '/'))) + str = str_addr+1; + + if ((str_addr = strrchr(str_domain, '/'))) + { + if (*str_domain != '/' || str_addr == str_domain) { - if (!(serv->flags & SERV_HAS_DOMAIN) && !domain) - { - serv->flags &= ~SERV_MARK; - break; - } - if ((serv->flags & SERV_HAS_DOMAIN) && - domain && - hostname_isequal(domain, serv->domain)) - { - serv->flags &= ~SERV_MARK; - break; - } + error = dbus_message_new_error_printf(message, + DBUS_ERROR_INVALID_ARGS, + "No domain terminator '%s'", + str); + break; } - - if (!serv && (serv = whine_malloc(sizeof (struct server)))) - { - /* Not found, create a new one. */ - memset(serv, 0, sizeof(struct server)); - - if (domain) - serv->domain = whine_malloc(strlen(domain)+1); - - if (domain && !serv->domain) - { - free(serv); - serv = NULL; - } - else - { - serv->next = daemon->servers; - daemon->servers = serv; - serv->flags = SERV_FROM_DBUS; - if (domain) - { - strcpy(serv->domain, domain); - serv->flags |= SERV_HAS_DOMAIN; - } - } - } + *str_addr++ = 0; + str_domain++; + } + else + { + str_addr = str_domain; + str_domain = NULL; + } + + + } + else + { + /* check the types of the struct and its elements */ + if ((dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_ARRAY) || + (dbus_message_iter_get_element_type(&array_iter) != DBUS_TYPE_STRING)) + { + error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected inner array of strings"); + break; + } + + /* string_iter points to each "s" element in the inner array */ + dbus_message_iter_recurse(&array_iter, &string_iter); + if (dbus_message_iter_get_arg_type(&string_iter) != DBUS_TYPE_STRING) + { + /* no IP address given */ + error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected IP address"); + break; + } + + dbus_message_iter_get_basic(&string_iter, &str); + if (!str || !strlen (str)) + { + error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Empty IP address"); + break; + } + + /* dup the string because it gets modified during parsing */ + if (dup) + free(dup); + if (!(dup = str_addr = whine_malloc(strlen(str)+1))) + break; + + strcpy(str_addr, str); + } + + memset(&addr, 0, sizeof(addr)); + memset(&source_addr, 0, sizeof(source_addr)); + memset(&interface, 0, sizeof(interface)); - if (serv) + /* parse the IP address */ + if ((addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, &flags))) + { + error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, + "Invalid IP address '%s': %s", + str, addr_err); + break; + } + + /* 0.0.0.0 for server address == NULL, for Dbus */ + if (addr.in.sin_family == AF_INET && + addr.in.sin_addr.s_addr == 0) + flags |= SERV_NO_ADDR; + + if (strings) + { + char *p; + + do { + if (str_domain) { - if (source_addr.in.sin_family == AF_INET && - addr.in.sin_addr.s_addr == 0 && - serv->domain) - serv->flags |= SERV_NO_ADDR; - else - { - serv->flags &= ~SERV_NO_ADDR; - serv->addr = addr; - serv->source_addr = source_addr; - } + if ((p = strchr(str_domain, '/'))) + *p++ = 0; } - } - } while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING); + else + p = NULL; + + add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain); + } while ((str_domain = p)); + } + else + { + /* jump past the address to the domain list (if any) */ + dbus_message_iter_next (&string_iter); + + /* parse domains and add each server/domain pair to the list */ + do { + str = NULL; + if (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING) + dbus_message_iter_get_basic(&string_iter, &str); + dbus_message_iter_next (&string_iter); + + add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str); + } while (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING); + } + + /* jump to next element in outer array */ + dbus_message_iter_next(&array_iter); } + + cleanup_servers(); + + if (dup) + free(dup); + + return error; +} + +static DBusMessage *dbus_set_bool(DBusMessage *message, int flag, char *name) +{ + DBusMessageIter iter; + dbus_bool_t enabled; + + if (!dbus_message_iter_init(message, &iter) || dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, "Expected boolean argument"); - /* unlink and free anything still marked. */ - for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) + dbus_message_iter_get_basic(&iter, &enabled); + + if (enabled) + { + my_syslog(LOG_INFO, _("Enabling --%s option from D-Bus"), name); + set_option_bool(flag); + } + else { - tmp = serv->next; - if (serv->flags & SERV_MARK) - { - server_gone(serv); - *up = serv->next; - free(serv); - } - else - up = &serv->next; + my_syslog(LOG_INFO, _("Disabling --%s option from D-Bus"), name); + reset_option_bool(flag); + } + + return NULL; +} + +#ifdef HAVE_DHCP +static DBusMessage *dbus_add_lease(DBusMessage* message) +{ + struct dhcp_lease *lease; + const char *ipaddr, *hwaddr, *hostname, *tmp; + const unsigned char* clid; + int clid_len, hostname_len, hw_len, hw_type; + dbus_uint32_t expires, ia_id; + dbus_bool_t is_temporary; + struct all_addr addr; + time_t now = dnsmasq_time(); + unsigned char dhcp_chaddr[DHCP_CHADDR_MAX]; + + DBusMessageIter iter, array_iter; + if (!dbus_message_iter_init(message, &iter)) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Failed to initialize dbus message iter"); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected string as first argument"); + + dbus_message_iter_get_basic(&iter, &ipaddr); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected string as second argument"); + + dbus_message_iter_get_basic(&iter, &hwaddr); + dbus_message_iter_next(&iter); + + if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) || + (dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE)) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected byte array as third argument"); + + dbus_message_iter_recurse(&iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &hostname, &hostname_len); + tmp = memchr(hostname, '\0', hostname_len); + if (tmp) + { + if (tmp == &hostname[hostname_len - 1]) + hostname_len--; + else + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Hostname contains an embedded NUL character"); + } + dbus_message_iter_next(&iter); + + if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) || + (dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE)) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected byte array as fourth argument"); + + dbus_message_iter_recurse(&iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &clid, &clid_len); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected uint32 as fifth argument"); + + dbus_message_iter_get_basic(&iter, &expires); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected uint32 as sixth argument"); + + dbus_message_iter_get_basic(&iter, &ia_id); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected uint32 as sixth argument"); + + dbus_message_iter_get_basic(&iter, &is_temporary); + + if (inet_pton(AF_INET, ipaddr, &addr.addr.addr4)) + { + if (ia_id != 0 || is_temporary) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "ia_id and is_temporary must be zero for IPv4 lease"); + + if (!(lease = lease_find_by_addr(addr.addr.addr4))) + lease = lease4_allocate(addr.addr.addr4); } +#ifdef HAVE_DHCP6 + else if (inet_pton(AF_INET6, ipaddr, &addr.addr.addr6)) + { + if (!(lease = lease6_find_by_addr(&addr.addr.addr6, 128, 0))) + lease = lease6_allocate(&addr.addr.addr6, + is_temporary ? LEASE_TA : LEASE_NA); + lease_set_iaid(lease, ia_id); + } +#endif + else + return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, + "Invalid IP address '%s'", ipaddr); + + hw_len = parse_hex((char*)hwaddr, dhcp_chaddr, DHCP_CHADDR_MAX, NULL, + &hw_type); + if (hw_type == 0 && hw_len != 0) + hw_type = ARPHRD_ETHER; + + lease_set_hwaddr(lease, dhcp_chaddr, clid, hw_len, hw_type, + clid_len, now, 0); + lease_set_expires(lease, expires, now); + if (hostname_len != 0) + lease_set_hostname(lease, hostname, 0, get_domain(lease->addr), NULL); + + lease_update_file(now); + lease_update_dns(0); + + return NULL; +} + +static DBusMessage *dbus_del_lease(DBusMessage* message) +{ + struct dhcp_lease *lease; + DBusMessageIter iter; + const char *ipaddr; + DBusMessage *reply; + struct all_addr addr; + dbus_bool_t ret = 1; + time_t now = dnsmasq_time(); + + if (!dbus_message_iter_init(message, &iter)) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Failed to initialize dbus message iter"); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, + "Expected string as first argument"); + + dbus_message_iter_get_basic(&iter, &ipaddr); + if (inet_pton(AF_INET, ipaddr, &addr.addr.addr4)) + lease = lease_find_by_addr(addr.addr.addr4); +#ifdef HAVE_DHCP6 + else if (inet_pton(AF_INET6, ipaddr, &addr.addr.addr6)) + lease = lease6_find_by_addr(&addr.addr.addr6, 128, 0); +#endif + else + return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, + "Invalid IP address '%s'", ipaddr); + + if (lease) + { + lease_prune(lease, now); + lease_update_file(now); + lease_update_dns(0); + } + else + ret = 0; + + if ((reply = dbus_message_new_method_return(message))) + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &ret, + DBUS_TYPE_INVALID); + + + return reply; } +#endif DBusHandlerResult message_handler(DBusConnection *connection, DBusMessage *message, void *user_data) { char *method = (char *)dbus_message_get_member(message); - + DBusMessage *reply = NULL; + int clear_cache = 0, new_servers = 0; + if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { - DBusMessage *reply = dbus_message_new_method_return(message); - - dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_xml, DBUS_TYPE_INVALID); - dbus_connection_send (connection, reply, NULL); - dbus_message_unref (reply); + /* string length: "%s" provides space for termination zero */ + if (!introspection_xml && + (introspection_xml = whine_malloc(strlen(introspection_xml_template) + strlen(daemon->dbus_name)))) + sprintf(introspection_xml, introspection_xml_template, daemon->dbus_name); + + if (introspection_xml) + { + reply = dbus_message_new_method_return(message); + dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_xml, DBUS_TYPE_INVALID); + } } else if (strcmp(method, "GetVersion") == 0) { char *v = VERSION; - DBusMessage *reply = dbus_message_new_method_return(message); + reply = dbus_message_new_method_return(message); dbus_message_append_args(reply, DBUS_TYPE_STRING, &v, DBUS_TYPE_INVALID); - dbus_connection_send (connection, reply, NULL); - dbus_message_unref (reply); } +#ifdef HAVE_LOOP + else if (strcmp(method, "GetLoopServers") == 0) + { + reply = dbus_reply_server_loop(message); + } +#endif else if (strcmp(method, "SetServers") == 0) { - my_syslog(LOG_INFO, _("setting upstream servers from DBus")); dbus_read_servers(message); - check_servers(); + new_servers = 1; + } + else if (strcmp(method, "SetServersEx") == 0) + { + reply = dbus_read_servers_ex(message, 0); + new_servers = 1; + } + else if (strcmp(method, "SetDomainServers") == 0) + { + reply = dbus_read_servers_ex(message, 1); + new_servers = 1; + } + else if (strcmp(method, "SetFilterWin2KOption") == 0) + { + reply = dbus_set_bool(message, OPT_FILTER, "filterwin2k"); + } + else if (strcmp(method, "SetBogusPrivOption") == 0) + { + reply = dbus_set_bool(message, OPT_BOGUSPRIV, "bogus-priv"); + } +#ifdef HAVE_DHCP + else if (strcmp(method, "AddDhcpLease") == 0) + { + reply = dbus_add_lease(message); + } + else if (strcmp(method, "DeleteDhcpLease") == 0) + { + reply = dbus_del_lease(message); } +#endif else if (strcmp(method, "ClearCache") == 0) - clear_cache_and_reload(dnsmasq_time()); + clear_cache = 1; else return (DBUS_HANDLER_RESULT_NOT_YET_HANDLED); + + if (new_servers) + { + my_syslog(LOG_INFO, _("setting upstream servers from DBus")); + check_servers(); + if (option_bool(OPT_RELOAD)) + clear_cache = 1; + } + + if (clear_cache) + clear_cache_and_reload(dnsmasq_time()); method = user_data; /* no warning */ + /* If no reply or no error, return nothing */ + if (!reply) + reply = dbus_message_new_method_return(message); + + if (reply) + { + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); + } + return (DBUS_HANDLER_RESULT_HANDLED); - } @@ -315,7 +729,7 @@ char *dbus_init(void) dbus_connection_set_watch_functions(connection, add_watch, remove_watch, NULL, NULL, NULL); dbus_error_init (&dbus_error); - dbus_bus_request_name (connection, DNSMASQ_SERVICE, 0, &dbus_error); + dbus_bus_request_name (connection, daemon->dbus_name, 0, &dbus_error); if (dbus_error_is_set (&dbus_error)) return (char *)dbus_error.message; @@ -325,7 +739,7 @@ char *dbus_init(void) daemon->dbus = connection; - if ((message = dbus_message_new_signal(DNSMASQ_PATH, DNSMASQ_SERVICE, "Up"))) + if ((message = dbus_message_new_signal(DNSMASQ_PATH, daemon->dbus_name, "Up"))) { dbus_connection_send(connection, message, NULL); dbus_message_unref(message); @@ -335,8 +749,7 @@ char *dbus_init(void) } -void set_dbus_listeners(int *maxfdp, - fd_set *rset, fd_set *wset, fd_set *eset) +void set_dbus_listeners(void) { struct watch *w; @@ -346,19 +759,17 @@ void set_dbus_listeners(int *maxfdp, unsigned int flags = dbus_watch_get_flags(w->watch); int fd = dbus_watch_get_unix_fd(w->watch); - bump_maxfd(fd, maxfdp); - if (flags & DBUS_WATCH_READABLE) - FD_SET(fd, rset); + poll_listen(fd, POLLIN); if (flags & DBUS_WATCH_WRITABLE) - FD_SET(fd, wset); + poll_listen(fd, POLLOUT); - FD_SET(fd, eset); + poll_listen(fd, POLLERR); } } -void check_dbus_listeners(fd_set *rset, fd_set *wset, fd_set *eset) +void check_dbus_listeners() { DBusConnection *connection = (DBusConnection *)daemon->dbus; struct watch *w; @@ -369,13 +780,13 @@ void check_dbus_listeners(fd_set *rset, fd_set *wset, fd_set *eset) unsigned int flags = 0; int fd = dbus_watch_get_unix_fd(w->watch); - if (FD_ISSET(fd, rset)) + if (poll_check(fd, POLLIN)) flags |= DBUS_WATCH_READABLE; - if (FD_ISSET(fd, wset)) + if (poll_check(fd, POLLOUT)) flags |= DBUS_WATCH_WRITABLE; - if (FD_ISSET(fd, eset)) + if (poll_check(fd, POLLERR)) flags |= DBUS_WATCH_ERROR; if (flags != 0) @@ -396,7 +807,7 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname) DBusConnection *connection = (DBusConnection *)daemon->dbus; DBusMessage* message = NULL; DBusMessageIter args; - char *action_str, *addr, *mac = daemon->namebuff; + char *action_str, *mac = daemon->namebuff; unsigned char *p; int i; @@ -406,29 +817,36 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname) if (!hostname) hostname = ""; - p = extended_hwaddr(lease->hwaddr_type, lease->hwaddr_len, - lease->hwaddr, lease->clid_len, lease->clid, &i); - print_mac(mac, p, i); - +#ifdef HAVE_DHCP6 + if (lease->flags & (LEASE_TA | LEASE_NA)) + { + print_mac(mac, lease->clid, lease->clid_len); + inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN); + } + else +#endif + { + p = extended_hwaddr(lease->hwaddr_type, lease->hwaddr_len, + lease->hwaddr, lease->clid_len, lease->clid, &i); + print_mac(mac, p, i); + inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN); + } + if (action == ACTION_DEL) action_str = "DhcpLeaseDeleted"; else if (action == ACTION_ADD) action_str = "DhcpLeaseAdded"; else if (action == ACTION_OLD) action_str = "DhcpLeaseUpdated"; - else if (action == ACTION_CONNECT) - action_str = "DhcpConnected"; else return; - addr = inet_ntoa(lease->addr); - - if (!(message = dbus_message_new_signal(DNSMASQ_PATH, DNSMASQ_SERVICE, action_str))) + if (!(message = dbus_message_new_signal(DNSMASQ_PATH, daemon->dbus_name, action_str))) return; dbus_message_iter_init_append(message, &args); - - if (dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &addr) && + + if (dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &daemon->addrbuff) && dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &mac) && dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &hostname)) dbus_connection_send(connection, message, NULL); diff --git a/src/dhcp-common.c b/src/dhcp-common.c new file mode 100644 index 0000000..bc48f41 --- /dev/null +++ b/src/dhcp-common.c @@ -0,0 +1,905 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_DHCP + +void dhcp_common_init(void) +{ + /* These each hold a DHCP option max size 255 + and get a terminating zero added */ + daemon->dhcp_buff = safe_malloc(256); + daemon->dhcp_buff2 = safe_malloc(256); + daemon->dhcp_buff3 = safe_malloc(256); + + /* dhcp_packet is used by v4 and v6, outpacket only by v6 + sizeof(struct dhcp_packet) is as good an initial size as any, + even for v6 */ + expand_buf(&daemon->dhcp_packet, sizeof(struct dhcp_packet)); +#ifdef HAVE_DHCP6 + if (daemon->dhcp6) + expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet)); +#endif +} + +ssize_t recv_dhcp_packet(int fd, struct msghdr *msg) +{ + ssize_t sz; + + while (1) + { + msg->msg_flags = 0; + while ((sz = recvmsg(fd, msg, MSG_PEEK | MSG_TRUNC)) == -1 && errno == EINTR); + + if (sz == -1) + return -1; + + if (!(msg->msg_flags & MSG_TRUNC)) + break; + + /* Very new Linux kernels return the actual size needed, + older ones always return truncated size */ + if ((size_t)sz == msg->msg_iov->iov_len) + { + if (!expand_buf(msg->msg_iov, sz + 100)) + return -1; + } + else + { + expand_buf(msg->msg_iov, sz); + break; + } + } + + while ((sz = recvmsg(fd, msg, 0)) == -1 && errno == EINTR); + + return (msg->msg_flags & MSG_TRUNC) ? -1 : sz; +} + +struct dhcp_netid *run_tag_if(struct dhcp_netid *tags) +{ + struct tag_if *exprs; + struct dhcp_netid_list *list; + + for (exprs = daemon->tag_if; exprs; exprs = exprs->next) + if (match_netid(exprs->tag, tags, 1)) + for (list = exprs->set; list; list = list->next) + { + list->list->next = tags; + tags = list->list; + } + + return tags; +} + + +struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags, struct dhcp_opt *opts) +{ + struct dhcp_netid *tagif = run_tag_if(tags); + struct dhcp_opt *opt; + struct dhcp_opt *tmp; + + /* flag options which are valid with the current tag set (sans context tags) */ + for (opt = opts; opt; opt = opt->next) + { + opt->flags &= ~DHOPT_TAGOK; + if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)) && + match_netid(opt->netid, tagif, 0)) + opt->flags |= DHOPT_TAGOK; + } + + /* now flag options which are valid, including the context tags, + otherwise valid options are inhibited if we found a higher priority one above */ + if (context_tags) + { + struct dhcp_netid *last_tag; + + for (last_tag = context_tags; last_tag->next; last_tag = last_tag->next); + last_tag->next = tags; + tagif = run_tag_if(context_tags); + + /* reset stuff with tag:!<tag> which now matches. */ + for (opt = opts; opt; opt = opt->next) + if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)) && + (opt->flags & DHOPT_TAGOK) && + !match_netid(opt->netid, tagif, 0)) + opt->flags &= ~DHOPT_TAGOK; + + for (opt = opts; opt; opt = opt->next) + if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925 | DHOPT_TAGOK)) && + match_netid(opt->netid, tagif, 0)) + { + struct dhcp_opt *tmp; + for (tmp = opts; tmp; tmp = tmp->next) + if (tmp->opt == opt->opt && opt->netid && (tmp->flags & DHOPT_TAGOK)) + break; + if (!tmp) + opt->flags |= DHOPT_TAGOK; + } + } + + /* now flag untagged options which are not overridden by tagged ones */ + for (opt = opts; opt; opt = opt->next) + if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925 | DHOPT_TAGOK)) && !opt->netid) + { + for (tmp = opts; tmp; tmp = tmp->next) + if (tmp->opt == opt->opt && (tmp->flags & DHOPT_TAGOK)) + break; + if (!tmp) + opt->flags |= DHOPT_TAGOK; + else if (!tmp->netid) + my_syslog(MS_DHCP | LOG_WARNING, _("Ignoring duplicate dhcp-option %d"), tmp->opt); + } + + /* Finally, eliminate duplicate options later in the chain, and therefore earlier in the config file. */ + for (opt = opts; opt; opt = opt->next) + if (opt->flags & DHOPT_TAGOK) + for (tmp = opt->next; tmp; tmp = tmp->next) + if (tmp->opt == opt->opt) + tmp->flags &= ~DHOPT_TAGOK; + + return tagif; +} + +/* Is every member of check matched by a member of pool? + If tagnotneeded, untagged is OK */ +int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int tagnotneeded) +{ + struct dhcp_netid *tmp1; + + if (!check && !tagnotneeded) + return 0; + + for (; check; check = check->next) + { + /* '#' for not is for backwards compat. */ + if (check->net[0] != '!' && check->net[0] != '#') + { + for (tmp1 = pool; tmp1; tmp1 = tmp1->next) + if (strcmp(check->net, tmp1->net) == 0) + break; + if (!tmp1) + return 0; + } + else + for (tmp1 = pool; tmp1; tmp1 = tmp1->next) + if (strcmp((check->net)+1, tmp1->net) == 0) + return 0; + } + return 1; +} + +/* return domain or NULL if none. */ +char *strip_hostname(char *hostname) +{ + char *dot = strchr(hostname, '.'); + + if (!dot) + return NULL; + + *dot = 0; /* truncate */ + if (strlen(dot+1) != 0) + return dot+1; + + return NULL; +} + +void log_tags(struct dhcp_netid *netid, u32 xid) +{ + if (netid && option_bool(OPT_LOG_OPTS)) + { + char *s = daemon->namebuff; + for (*s = 0; netid; netid = netid->next) + { + /* kill dupes. */ + struct dhcp_netid *n; + + 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"), xid, s); + } +} + +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; +} + +int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type) +{ + struct hwaddr_config *conf_addr; + + for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) + if (conf_addr->wildcard_mask == 0 && + conf_addr->hwaddr_len == len && + (conf_addr->hwaddr_type == type || conf_addr->hwaddr_type == 0) && + memcmp(conf_addr->hwaddr, hwaddr, len) == 0) + return 1; + + return 0; +} + +static int is_config_in_context(struct dhcp_context *context, struct dhcp_config *config) +{ + if (!context) /* called via find_config() from lease_update_from_configs() */ + return 1; + + if (!(config->flags & (CONFIG_ADDR | CONFIG_ADDR6))) + return 1; + +#ifdef HAVE_DHCP6 + if ((context->flags & CONTEXT_V6) && (config->flags & CONFIG_WILDCARD)) + return 1; +#endif + + for (; context; context = context->current) +#ifdef HAVE_DHCP6 + if (context->flags & CONTEXT_V6) + { + if ((config->flags & CONFIG_ADDR6) && is_same_net6(&config->addr6, &context->start6, context->prefix)) + return 1; + } + else +#endif + if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) + return 1; + + return 0; +} + +struct dhcp_config *find_config(struct dhcp_config *configs, + struct dhcp_context *context, + unsigned char *clid, int clid_len, + unsigned char *hwaddr, int hw_len, + int hw_type, char *hostname) +{ + int count, new; + struct dhcp_config *config, *candidate; + struct hwaddr_config *conf_addr; + + if (clid) + for (config = configs; config; config = config->next) + if (config->flags & CONFIG_CLID) + { + if (config->clid_len == clid_len && + memcmp(config->clid, clid, clid_len) == 0 && + is_config_in_context(context, config)) + return config; + + /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and + cope with that here. This is IPv4 only. context==NULL implies IPv4, + see lease_update_from_configs() */ + if ((!context || !(context->flags & CONTEXT_V6)) && *clid == 0 && config->clid_len == clid_len-1 && + memcmp(config->clid, clid+1, clid_len-1) == 0 && + is_config_in_context(context, config)) + return config; + } + + + if (hwaddr) + for (config = configs; config; config = config->next) + if (config_has_mac(config, hwaddr, hw_len, hw_type) && + is_config_in_context(context, config)) + return config; + + if (hostname && context) + for (config = configs; config; config = config->next) + if ((config->flags & CONFIG_NAME) && + hostname_isequal(config->hostname, hostname) && + is_config_in_context(context, config)) + return config; + + + if (!hwaddr) + return NULL; + + /* use match with fewest wildcard octets */ + for (candidate = NULL, count = 0, config = configs; config; config = config->next) + if (is_config_in_context(context, config)) + for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) + if (conf_addr->wildcard_mask != 0 && + conf_addr->hwaddr_len == hw_len && + (conf_addr->hwaddr_type == hw_type || conf_addr->hwaddr_type == 0) && + (new = memcmp_masked(conf_addr->hwaddr, hwaddr, hw_len, conf_addr->wildcard_mask)) > count) + { + count = new; + candidate = config; + } + + return candidate; +} + +void dhcp_update_configs(struct dhcp_config *configs) +{ + /* Some people like to keep all static IP addresses in /etc/hosts. + This goes through /etc/hosts and sets static addresses for any DHCP config + records which don't have an address and whose name matches. + We take care to maintain the invariant that any IP address can appear + in at most one dhcp-host. Since /etc/hosts can be re-read by SIGHUP, + restore the status-quo ante first. */ + + struct dhcp_config *config, *conf_tmp; + struct crec *crec; + int prot = AF_INET; + + for (config = configs; config; config = config->next) + if (config->flags & CONFIG_ADDR_HOSTS) + config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR6 | CONFIG_ADDR_HOSTS); + +#ifdef HAVE_DHCP6 + again: +#endif + + if (daemon->port != 0) + for (config = configs; config; config = config->next) + { + int conflags = CONFIG_ADDR; + int cacheflags = F_IPV4; + +#ifdef HAVE_DHCP6 + if (prot == AF_INET6) + { + conflags = CONFIG_ADDR6; + cacheflags = F_IPV6; + } +#endif + if (!(config->flags & conflags) && + (config->flags & CONFIG_NAME) && + (crec = cache_find_by_name(NULL, config->hostname, 0, cacheflags)) && + (crec->flags & F_HOSTS)) + { + if (cache_find_by_name(crec, config->hostname, 0, cacheflags)) + { + /* use primary (first) address */ + while (crec && !(crec->flags & F_REVERSE)) + crec = cache_find_by_name(crec, config->hostname, 0, cacheflags); + if (!crec) + continue; /* should be never */ + inet_ntop(prot, &crec->addr.addr, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_WARNING, _("%s has more than one address in hostsfile, using %s for DHCP"), + config->hostname, daemon->addrbuff); + } + + if (prot == AF_INET && + (!(conf_tmp = config_find_by_address(configs, crec->addr.addr.addr.addr4)) || conf_tmp == config)) + { + config->addr = crec->addr.addr.addr.addr4; + config->flags |= CONFIG_ADDR | CONFIG_ADDR_HOSTS; + continue; + } + +#ifdef HAVE_DHCP6 + if (prot == AF_INET6 && + (!(conf_tmp = config_find_by_address6(configs, &crec->addr.addr.addr.addr6, 128, 0)) || conf_tmp == config)) + { + memcpy(&config->addr6, &crec->addr.addr.addr.addr6, IN6ADDRSZ); + config->flags |= CONFIG_ADDR6 | CONFIG_ADDR_HOSTS; + continue; + } +#endif + + inet_ntop(prot, &crec->addr.addr, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_WARNING, _("duplicate IP address %s (%s) in dhcp-config directive"), + daemon->addrbuff, config->hostname); + + + } + } + +#ifdef HAVE_DHCP6 + if (prot == AF_INET) + { + prot = AF_INET6; + goto again; + } +#endif + +} + +#ifdef HAVE_LINUX_NETWORK +char *whichdevice(void) +{ + /* If we are doing DHCP on exactly one interface, and running linux, do SO_BINDTODEVICE + to that device. This is for the use case of (eg) OpenStack, which runs a new + dnsmasq instance for each VLAN interface it creates. Without the BINDTODEVICE, + individual processes don't always see the packets they should. + SO_BINDTODEVICE is only available Linux. + + Note that if wildcards are used in --interface, or --interface is not used at all, + or a configured interface doesn't yet exist, then more interfaces may arrive later, + so we can't safely assert there is only one interface and proceed. +*/ + + struct irec *iface, *found; + struct iname *if_tmp; + + if (!daemon->if_names) + return NULL; + + for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next) + if (if_tmp->name && (!if_tmp->used || strchr(if_tmp->name, '*'))) + return NULL; + + for (found = NULL, iface = daemon->interfaces; iface; iface = iface->next) + if (iface->dhcp_ok) + { + if (!found) + found = iface; + else if (strcmp(found->name, iface->name) != 0) + return NULL; /* more than one. */ + } + + if (found) + return found->name; + + return NULL; +} + +void bindtodevice(char *device, int fd) +{ + struct ifreq ifr; + + strcpy(ifr.ifr_name, device); + /* only allowed by root. */ + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) == -1 && + errno != EPERM) + die(_("failed to set SO_BINDTODEVICE on DHCP socket: %s"), NULL, EC_BADNET); +} +#endif + +static const struct opttab_t { + char *name; + u16 val, size; +} opttab[] = { + { "netmask", 1, OT_ADDR_LIST }, + { "time-offset", 2, 4 }, + { "router", 3, OT_ADDR_LIST }, + { "dns-server", 6, OT_ADDR_LIST }, + { "log-server", 7, OT_ADDR_LIST }, + { "lpr-server", 9, OT_ADDR_LIST }, + { "hostname", 12, OT_INTERNAL | OT_NAME }, + { "boot-file-size", 13, 2 | OT_DEC }, + { "domain-name", 15, OT_NAME }, + { "swap-server", 16, OT_ADDR_LIST }, + { "root-path", 17, OT_NAME }, + { "extension-path", 18, OT_NAME }, + { "ip-forward-enable", 19, 1 }, + { "non-local-source-routing", 20, 1 }, + { "policy-filter", 21, OT_ADDR_LIST }, + { "max-datagram-reassembly", 22, 2 | OT_DEC }, + { "default-ttl", 23, 1 | OT_DEC }, + { "mtu", 26, 2 | OT_DEC }, + { "all-subnets-local", 27, 1 }, + { "broadcast", 28, OT_INTERNAL | OT_ADDR_LIST }, + { "router-discovery", 31, 1 }, + { "router-solicitation", 32, OT_ADDR_LIST }, + { "static-route", 33, OT_ADDR_LIST }, + { "trailer-encapsulation", 34, 1 }, + { "arp-timeout", 35, 4 | OT_DEC }, + { "ethernet-encap", 36, 1 }, + { "tcp-ttl", 37, 1 }, + { "tcp-keepalive", 38, 4 | OT_DEC }, + { "nis-domain", 40, OT_NAME }, + { "nis-server", 41, OT_ADDR_LIST }, + { "ntp-server", 42, OT_ADDR_LIST }, + { "vendor-encap", 43, OT_INTERNAL }, + { "netbios-ns", 44, OT_ADDR_LIST }, + { "netbios-dd", 45, OT_ADDR_LIST }, + { "netbios-nodetype", 46, 1 }, + { "netbios-scope", 47, 0 }, + { "x-windows-fs", 48, OT_ADDR_LIST }, + { "x-windows-dm", 49, OT_ADDR_LIST }, + { "requested-address", 50, OT_INTERNAL | OT_ADDR_LIST }, + { "lease-time", 51, OT_INTERNAL | OT_TIME }, + { "option-overload", 52, OT_INTERNAL }, + { "message-type", 53, OT_INTERNAL | OT_DEC }, + { "server-identifier", 54, OT_INTERNAL | OT_ADDR_LIST }, + { "parameter-request", 55, OT_INTERNAL }, + { "message", 56, OT_INTERNAL }, + { "max-message-size", 57, OT_INTERNAL }, + { "T1", 58, OT_TIME}, + { "T2", 59, OT_TIME}, + { "vendor-class", 60, 0 }, + { "client-id", 61, OT_INTERNAL }, + { "nis+-domain", 64, OT_NAME }, + { "nis+-server", 65, OT_ADDR_LIST }, + { "tftp-server", 66, OT_NAME }, + { "bootfile-name", 67, OT_NAME }, + { "mobile-ip-home", 68, OT_ADDR_LIST }, + { "smtp-server", 69, OT_ADDR_LIST }, + { "pop3-server", 70, OT_ADDR_LIST }, + { "nntp-server", 71, OT_ADDR_LIST }, + { "irc-server", 74, OT_ADDR_LIST }, + { "user-class", 77, 0 }, + { "FQDN", 81, OT_INTERNAL }, + { "agent-id", 82, OT_INTERNAL }, + { "client-arch", 93, 2 | OT_DEC }, + { "client-interface-id", 94, 0 }, + { "client-machine-id", 97, 0 }, + { "subnet-select", 118, OT_INTERNAL }, + { "domain-search", 119, OT_RFC1035_NAME }, + { "sip-server", 120, 0 }, + { "classless-static-route", 121, 0 }, + { "vendor-id-encap", 125, 0 }, + { "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */ + { NULL, 0, 0 } +}; + +#ifdef HAVE_DHCP6 +static const struct opttab_t opttab6[] = { + { "client-id", 1, OT_INTERNAL }, + { "server-id", 2, OT_INTERNAL }, + { "ia-na", 3, OT_INTERNAL }, + { "ia-ta", 4, OT_INTERNAL }, + { "iaaddr", 5, OT_INTERNAL }, + { "oro", 6, OT_INTERNAL }, + { "preference", 7, OT_INTERNAL | OT_DEC }, + { "unicast", 12, OT_INTERNAL }, + { "status", 13, OT_INTERNAL }, + { "rapid-commit", 14, OT_INTERNAL }, + { "user-class", 15, OT_INTERNAL | OT_CSTRING }, + { "vendor-class", 16, OT_INTERNAL | OT_CSTRING }, + { "vendor-opts", 17, OT_INTERNAL }, + { "sip-server-domain", 21, OT_RFC1035_NAME }, + { "sip-server", 22, OT_ADDR_LIST }, + { "dns-server", 23, OT_ADDR_LIST }, + { "domain-search", 24, OT_RFC1035_NAME }, + { "nis-server", 27, OT_ADDR_LIST }, + { "nis+-server", 28, OT_ADDR_LIST }, + { "nis-domain", 29, OT_RFC1035_NAME }, + { "nis+-domain", 30, OT_RFC1035_NAME }, + { "sntp-server", 31, OT_ADDR_LIST }, + { "information-refresh-time", 32, OT_TIME }, + { "FQDN", 39, OT_INTERNAL | OT_RFC1035_NAME }, + { "ntp-server", 56, OT_ADDR_LIST }, + { "bootfile-url", 59, OT_NAME }, + { "bootfile-param", 60, OT_CSTRING }, + { NULL, 0, 0 } +}; +#endif + + + +void display_opts(void) +{ + int i; + + printf(_("Known DHCP options:\n")); + + for (i = 0; opttab[i].name; i++) + if (!(opttab[i].size & OT_INTERNAL)) + printf("%3d %s\n", opttab[i].val, opttab[i].name); +} + +#ifdef HAVE_DHCP6 +void display_opts6(void) +{ + int i; + printf(_("Known DHCPv6 options:\n")); + + for (i = 0; opttab6[i].name; i++) + if (!(opttab6[i].size & OT_INTERNAL)) + printf("%3d %s\n", opttab6[i].val, opttab6[i].name); +} +#endif + +int lookup_dhcp_opt(int prot, char *name) +{ + const struct opttab_t *t; + int i; + + (void)prot; + +#ifdef HAVE_DHCP6 + if (prot == AF_INET6) + t = opttab6; + else +#endif + t = opttab; + + for (i = 0; t[i].name; i++) + if (strcasecmp(t[i].name, name) == 0) + return t[i].val; + + return -1; +} + +int lookup_dhcp_len(int prot, int val) +{ + const struct opttab_t *t; + int i; + + (void)prot; + +#ifdef HAVE_DHCP6 + if (prot == AF_INET6) + t = opttab6; + else +#endif + t = opttab; + + for (i = 0; t[i].name; i++) + if (val == t[i].val) + return t[i].size & ~OT_DEC; + + return 0; +} + +char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len, char *buf, int buf_len) +{ + int o, i, j, nodecode = 0; + const struct opttab_t *ot = opttab; + +#ifdef HAVE_DHCP6 + if (prot == AF_INET6) + ot = opttab6; +#endif + + for (o = 0; ot[o].name; o++) + if (ot[o].val == opt) + { + if (buf) + { + memset(buf, 0, buf_len); + + if (ot[o].size & OT_ADDR_LIST) + { + struct all_addr addr; + int addr_len = INADDRSZ; + +#ifdef HAVE_DHCP6 + if (prot == AF_INET6) + addr_len = IN6ADDRSZ; +#endif + for (buf[0]= 0, i = 0; i <= opt_len - addr_len; i += addr_len) + { + if (i != 0) + strncat(buf, ", ", buf_len - strlen(buf)); + /* align */ + memcpy(&addr, &val[i], addr_len); + inet_ntop(prot, &val[i], daemon->addrbuff, ADDRSTRLEN); + strncat(buf, daemon->addrbuff, buf_len - strlen(buf)); + } + } + else if (ot[o].size & OT_NAME) + for (i = 0, j = 0; i < opt_len && j < buf_len ; i++) + { + char c = val[i]; + if (isprint((int)c)) + buf[j++] = c; + } +#ifdef HAVE_DHCP6 + /* We don't handle compressed rfc1035 names, so no good in IPv4 land */ + else if ((ot[o].size & OT_RFC1035_NAME) && prot == AF_INET6) + { + i = 0, j = 0; + while (i < opt_len && val[i] != 0) + { + int k, l = i + val[i] + 1; + for (k = i + 1; k < opt_len && k < l && j < buf_len ; k++) + { + char c = val[k]; + if (isprint((int)c)) + buf[j++] = c; + } + i = l; + if (val[i] != 0 && j < buf_len) + buf[j++] = '.'; + } + } + else if ((ot[o].size & OT_CSTRING)) + { + int k, len; + unsigned char *p; + + i = 0, j = 0; + while (1) + { + p = &val[i]; + GETSHORT(len, p); + for (k = 0; k < len && j < buf_len; k++) + { + char c = *p++; + if (isprint((int)c)) + buf[j++] = c; + } + i += len +2; + if (i >= opt_len) + break; + + if (j < buf_len) + buf[j++] = ','; + } + } +#endif + else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0) + { + unsigned int dec = 0; + + for (i = 0; i < opt_len; i++) + dec = (dec << 8) | val[i]; + + if (ot[o].size & OT_TIME) + prettyprint_time(buf, dec); + else + sprintf(buf, "%u", dec); + } + else + nodecode = 1; + } + break; + } + + if (opt_len != 0 && buf && (!ot[o].name || nodecode)) + { + int trunc = 0; + if (opt_len > 14) + { + trunc = 1; + opt_len = 14; + } + print_mac(buf, val, opt_len); + if (trunc) + strncat(buf, "...", buf_len - strlen(buf)); + + + } + + return ot[o].name ? ot[o].name : ""; + +} + +void log_context(int family, struct dhcp_context *context) +{ + /* Cannot use dhcp_buff* for RA contexts */ + + void *start = &context->start; + void *end = &context->end; + char *template = "", *p = daemon->namebuff; + + *p = 0; + +#ifdef HAVE_DHCP6 + if (family == AF_INET6) + { + struct in6_addr subnet = context->start6; + if (!(context->flags & CONTEXT_TEMPLATE)) + setaddr6part(&subnet, 0); + inet_ntop(AF_INET6, &subnet, daemon->addrbuff, ADDRSTRLEN); + start = &context->start6; + end = &context->end6; + } +#endif + + if (family != AF_INET && (context->flags & CONTEXT_DEPRECATE)) + strcpy(daemon->namebuff, _(", prefix deprecated")); + else + { + p += sprintf(p, _(", lease time ")); + prettyprint_time(p, context->lease_time); + p += strlen(p); + } + +#ifdef HAVE_DHCP6 + if (context->flags & CONTEXT_CONSTRUCTED) + { + char ifrn_name[IFNAMSIZ]; + + template = p; + p += sprintf(p, ", "); + + if (indextoname(daemon->icmp6fd, context->if_index, ifrn_name)) + sprintf(p, "%s for %s", (context->flags & CONTEXT_OLD) ? "old prefix" : "constructed", ifrn_name); + } + else if (context->flags & CONTEXT_TEMPLATE && !(context->flags & CONTEXT_RA_STATELESS)) + { + template = p; + p += sprintf(p, ", "); + + sprintf(p, "template for %s", context->template_interface); + } +#endif + + if (!(context->flags & CONTEXT_OLD) && + ((context->flags & CONTEXT_DHCP) || family == AF_INET)) + { +#ifdef HAVE_DHCP6 + if (context->flags & CONTEXT_RA_STATELESS) + { + if (context->flags & CONTEXT_TEMPLATE) + strncpy(daemon->dhcp_buff, context->template_interface, 256); + else + strcpy(daemon->dhcp_buff, daemon->addrbuff); + } + else +#endif + inet_ntop(family, start, daemon->dhcp_buff, 256); + inet_ntop(family, end, daemon->dhcp_buff3, 256); + my_syslog(MS_DHCP | LOG_INFO, + (context->flags & CONTEXT_RA_STATELESS) ? + _("%s stateless on %s%.0s%.0s%s") : + (context->flags & CONTEXT_STATIC) ? + _("%s, static leases only on %.0s%s%s%.0s") : + (context->flags & CONTEXT_PROXY) ? + _("%s, proxy on subnet %.0s%s%.0s%.0s") : + _("%s, IP range %s -- %s%s%.0s"), + (family != AF_INET) ? "DHCPv6" : "DHCP", + daemon->dhcp_buff, daemon->dhcp_buff3, daemon->namebuff, template); + } + +#ifdef HAVE_DHCP6 + if (context->flags & CONTEXT_TEMPLATE) + { + strcpy(daemon->addrbuff, context->template_interface); + template = ""; + } + + if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD)) + my_syslog(MS_DHCP | LOG_INFO, _("DHCPv4-derived IPv6 names on %s%s"), daemon->addrbuff, template); + + if ((context->flags & CONTEXT_RA) || (option_bool(OPT_RA) && (context->flags & CONTEXT_DHCP) && family == AF_INET6)) + my_syslog(MS_DHCP | LOG_INFO, _("router advertisement on %s%s"), daemon->addrbuff, template); +#endif + +} + +void log_relay(int family, struct dhcp_relay *relay) +{ + inet_ntop(family, &relay->local, daemon->addrbuff, ADDRSTRLEN); + inet_ntop(family, &relay->server, daemon->namebuff, ADDRSTRLEN); + + if (relay->interface) + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s via %s"), daemon->addrbuff, daemon->namebuff, relay->interface); + else + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s"), daemon->addrbuff, daemon->namebuff); +} + +#endif diff --git a/src/dhcp_protocol.h b/src/dhcp-protocol.h index e09cad8..701b6cb 100644 --- a/src/dhcp_protocol.h +++ b/src/dhcp-protocol.h @@ -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 @@ -13,6 +13,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#define DHCP_SERVER_PORT 67 +#define DHCP_CLIENT_PORT 68 +#define DHCP_SERVER_ALTPORT 1067 +#define DHCP_CLIENT_ALTPORT 1068 +#define PXE_PORT 4011 #define BOOTREQUEST 1 #define BOOTREPLY 2 @@ -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 @@ -19,13 +19,23 @@ #ifdef HAVE_DHCP struct iface_param { - struct in_addr relay, primary; struct dhcp_context *current; + struct dhcp_relay *relay; + struct in_addr relay_local; int ind; }; -static int complete_context(struct in_addr local, int if_index, +struct match_param { + int ind, matched; + struct in_addr netmask, broadcast, addr; +}; + +static int complete_context(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam); +static int check_listen_addrs(struct in_addr local, int if_index, char *label, + struct in_addr netmask, struct in_addr broadcast, void *vparam); +static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index); +static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface); static int make_fd(int port) { @@ -35,16 +45,22 @@ static int make_fd(int port) #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) int mtu = IP_PMTUDISC_DONT; #endif +#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6) + int tos = IPTOS_CLASS_CS6; +#endif if (fd == -1) die (_("cannot create DHCP socket: %s"), NULL, EC_BADNET); if (!fix_fd(fd) || #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) - setsockopt(fd, SOL_IP, IP_MTU_DISCOVER, &mtu, sizeof(mtu)) == -1 || + setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &mtu, sizeof(mtu)) == -1 || +#endif +#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6) + setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1 || #endif #if defined(HAVE_LINUX_NETWORK) - setsockopt(fd, SOL_IP, IP_PKTINFO, &oneopt, sizeof(oneopt)) == -1 || + setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &oneopt, sizeof(oneopt)) == -1 || #else setsockopt(fd, IPPROTO_IP, IP_RECVIF, &oneopt, sizeof(oneopt)) == -1 || #endif @@ -53,14 +69,22 @@ static int make_fd(int port) /* When bind-interfaces is set, there might be more than one dnmsasq instance binding port 67. That's OK if they serve different networks. - Need to set REUSEADDR to make this posible, or REUSEPORT on *BSD. */ - if (option_bool(OPT_NOWILD)) + Need to set REUSEADDR|REUSEPORT to make this posible. + Handle the case that REUSEPORT is defined, but the kernel doesn't + support it. This handles the introduction of REUSEPORT on Linux. */ + if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)) { + int rc = 0; + #ifdef SO_REUSEPORT - int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt)); -#else - int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt)); + if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt))) == -1 && + errno == ENOPROTOOPT) + rc = 0; #endif + + if (rc != -1) + rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt)); + if (rc == -1) die(_("failed to set SO_REUSE{ADDR|PORT} on DHCP socket: %s"), NULL, EC_BADNET); } @@ -104,19 +128,16 @@ void dhcp_init(void) /* Make BPF raw send socket */ init_bpf(); -#endif - - check_dhcp_hosts(1); - - daemon->dhcp_packet.iov_len = sizeof(struct dhcp_packet); - daemon->dhcp_packet.iov_base = safe_malloc(daemon->dhcp_packet.iov_len); +#endif } - + void dhcp_packet(time_t now, int pxe_fd) { int fd = pxe_fd ? daemon->pxefd : daemon->dhcpfd; struct dhcp_packet *mess; struct dhcp_context *context; + struct dhcp_relay *relay; + int is_relay_reply = 0; struct iname *tmp; struct ifreq ifr; struct msghdr msg; @@ -125,7 +146,7 @@ void dhcp_packet(time_t now, int pxe_fd) struct iovec iov; ssize_t sz; int iface_index = 0, unicast_dest = 0, is_inform = 0; - struct in_addr iface_addr, *addrp = NULL; + struct in_addr iface_addr; struct iface_param parm; #ifdef HAVE_LINUX_NETWORK struct arpreq arp_req; @@ -141,56 +162,23 @@ void dhcp_packet(time_t now, int pxe_fd) char control[CMSG_SPACE(sizeof(struct sockaddr_dl))]; #endif } control_u; - - msg.msg_control = NULL; - msg.msg_controllen = 0; - msg.msg_name = NULL; - msg.msg_namelen = 0; - msg.msg_iov = &daemon->dhcp_packet; - msg.msg_iovlen = 1; - - while (1) - { - msg.msg_flags = 0; - while ((sz = recvmsg(fd, &msg, MSG_PEEK | MSG_TRUNC)) == -1 && errno == EINTR); - - if (sz == -1) - return; - - if (!(msg.msg_flags & MSG_TRUNC)) - break; + struct dhcp_bridge *bridge, *alias; - /* Very new Linux kernels return the actual size needed, - older ones always return truncated size */ - if ((size_t)sz == daemon->dhcp_packet.iov_len) - { - if (!expand_buf(&daemon->dhcp_packet, sz + 100)) - return; - } - else - { - expand_buf(&daemon->dhcp_packet, sz); - break; - } - } - - /* expand_buf may have moved buffer */ - mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base; msg.msg_controllen = sizeof(control_u); msg.msg_control = control_u.control; - msg.msg_flags = 0; msg.msg_name = &dest; msg.msg_namelen = sizeof(dest); - - while ((sz = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR); - - if ((msg.msg_flags & MSG_TRUNC) || sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options))) - return; + msg.msg_iov = &daemon->dhcp_packet; + msg.msg_iovlen = 1; -#if defined (HAVE_LINUX_NETWORK) + if ((sz = recv_dhcp_packet(fd, &msg)) == -1 || + (sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options)))) + return; + + #if defined (HAVE_LINUX_NETWORK) if (msg.msg_controllen >= sizeof(struct cmsghdr)) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) - if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { union { unsigned char *c; @@ -237,74 +225,120 @@ void dhcp_packet(time_t now, int pxe_fd) strncpy(arp_req.arp_dev, ifr.ifr_name, 16); #endif + /* If the interface on which the DHCP request was received is an + alias of some other interface (as specified by the + --bridge-interface option), change ifr.ifr_name so that we look + for DHCP contexts associated with the aliased interface instead + of with the aliasing one. */ + for (bridge = daemon->bridges; bridge; bridge = bridge->next) + { + for (alias = bridge->alias; alias; alias = alias->next) + if (wildcard_matchn(alias->iface, ifr.ifr_name, IF_NAMESIZE)) + { + if (!(iface_index = if_nametoindex(bridge->iface))) + { + my_syslog(MS_DHCP | LOG_WARNING, + _("unknown interface %s in bridge-interface"), + bridge->iface); + return; + } + else + { + strncpy(ifr.ifr_name, bridge->iface, IF_NAMESIZE); + break; + } + } + + if (alias) + break; + } + #ifdef MSG_BCAST /* OpenBSD tells us when a packet was broadcast */ if (!(msg.msg_flags & MSG_BCAST)) unicast_dest = 1; #endif - - ifr.ifr_addr.sa_family = AF_INET; - if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 ) - { - addrp = &iface_addr; - iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; - } - - if (!iface_check(AF_INET, (struct all_addr *)addrp, ifr.ifr_name, &iface_index)) - return; - for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) - if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0)) - return; - - /* weird libvirt-inspired access control */ - for (context = daemon->dhcp; context; context = context->next) - if (!context->interface || strcmp(context->interface, ifr.ifr_name) == 0) - break; - - if (!context) - return; - - /* unlinked contexts are marked by context->current == context */ - for (context = daemon->dhcp; context; context = context->next) - context->current = context; - - parm.relay = mess->giaddr; - parm.primary = iface_addr; - parm.current = NULL; - parm.ind = iface_index; - - /* interface may have been changed by alias in iface_check, make sure it gets priority in case - there is more than one address on the interface in the same subnet */ - if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) == -1) + if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name))) { - my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name); - return; + /* Reply from server, using us as relay. */ + iface_index = relay->iface_index; + if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name)) + return; + is_relay_reply = 1; + iov.iov_len = sz; +#ifdef HAVE_LINUX_NETWORK + strncpy(arp_req.arp_dev, ifr.ifr_name, 16); +#endif } else { - iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; - if (ioctl(daemon->dhcpfd, SIOCGIFNETMASK, &ifr) != -1) + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 ) + iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + else { - struct in_addr netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; - if (ioctl(daemon->dhcpfd, SIOCGIFBRDADDR, &ifr) != -1) - { - struct in_addr broadcast = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; - complete_context(iface_addr, iface_index, netmask, broadcast, &parm); - } + my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name); + return; } - } + + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + return; + + /* unlinked contexts/relays are marked by context->current == context */ + for (context = daemon->dhcp; context; context = context->next) + context->current = context; + + for (relay = daemon->relay4; relay; relay = relay->next) + relay->current = relay; + + parm.current = NULL; + parm.relay = NULL; + parm.relay_local.s_addr = 0; + parm.ind = iface_index; + + if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name, NULL)) + { + /* If we failed to match the primary address of the interface, see if we've got a --listen-address + for a secondary */ + struct match_param match; + + match.matched = 0; + match.ind = iface_index; + + if (!daemon->if_addrs || + !iface_enumerate(AF_INET, &match, check_listen_addrs) || + !match.matched) + return; + + iface_addr = match.addr; + /* make sure secondary address gets priority in case + there is more than one address on the interface in the same subnet */ + complete_context(match.addr, iface_index, NULL, match.netmask, match.broadcast, &parm); + } + + if (!iface_enumerate(AF_INET, &parm, complete_context)) + return; - if (!iface_enumerate(AF_INET, &parm, complete_context)) - return; - lease_prune(NULL, now); /* lose any expired leases */ - iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz, - now, unicast_dest, &is_inform, pxe_fd); - lease_update_file(now); - lease_update_dns(); - - if (iov.iov_len == 0) - return; + /* We're relaying this request */ + if (parm.relay_local.s_addr != 0 && + relay_upstream4(parm.relay, (struct dhcp_packet *)daemon->dhcp_packet.iov_base, (size_t)sz, iface_index)) + return; + + /* May have configured relay, but not DHCP server */ + if (!daemon->dhcp) + return; + + lease_prune(NULL, now); /* lose any expired leases */ + iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz, + now, unicast_dest, &is_inform, pxe_fd, iface_addr); + lease_update_file(now); + lease_update_dns(0); + + if (iov.iov_len == 0) + return; + } msg.msg_name = &dest; msg.msg_namelen = sizeof(dest); @@ -319,13 +353,13 @@ void dhcp_packet(time_t now, int pxe_fd) #ifdef HAVE_SOCKADDR_SA_LEN dest.sin_len = sizeof(struct sockaddr_in); #endif - + if (pxe_fd) { if (mess->ciaddr.s_addr != 0) dest.sin_addr = mess->ciaddr; } - else if (mess->giaddr.s_addr) + else if (mess->giaddr.s_addr && !is_relay_reply) { /* Send to BOOTP relay */ dest.sin_port = htons(daemon->dhcp_server_port); @@ -338,17 +372,16 @@ void dhcp_packet(time_t now, int pxe_fd) source port too, and send back to that. If we're replying to a DHCPINFORM, trust the source address always. */ if ((!is_inform && dest.sin_addr.s_addr != mess->ciaddr.s_addr) || - dest.sin_port == 0 || dest.sin_addr.s_addr == 0) + dest.sin_port == 0 || dest.sin_addr.s_addr == 0 || is_relay_reply) { dest.sin_port = htons(daemon->dhcp_client_port); dest.sin_addr = mess->ciaddr; } } #if defined(HAVE_LINUX_NETWORK) - else if ((ntohs(mess->flags) & 0x8000) || mess->hlen == 0 || - mess->hlen > sizeof(ifr.ifr_addr.sa_data) || mess->htype == 0) + else { - /* broadcast to 255.255.255.255 (or mac address invalid) */ + /* fill cmsg for outbound interface (both broadcast & unicast) */ struct in_pktinfo *pkt; msg.msg_control = control_u.control; msg.msg_controllen = sizeof(control_u); @@ -357,23 +390,30 @@ void dhcp_packet(time_t now, int pxe_fd) pkt->ipi_ifindex = iface_index; pkt->ipi_spec_dst.s_addr = 0; msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - cmptr->cmsg_level = SOL_IP; - cmptr->cmsg_type = IP_PKTINFO; - dest.sin_addr.s_addr = INADDR_BROADCAST; - dest.sin_port = htons(daemon->dhcp_client_port); - } - else - { - /* unicast to unconfigured client. Inject mac address direct into ARP cache. - struct sockaddr limits size to 14 bytes. */ - dest.sin_addr = mess->yiaddr; - dest.sin_port = htons(daemon->dhcp_client_port); - memcpy(&arp_req.arp_pa, &dest, sizeof(struct sockaddr_in)); - arp_req.arp_ha.sa_family = mess->htype; - memcpy(arp_req.arp_ha.sa_data, mess->chaddr, mess->hlen); - /* interface name already copied in */ - arp_req.arp_flags = ATF_COM; - ioctl(daemon->dhcpfd, SIOCSARP, &arp_req); + cmptr->cmsg_level = IPPROTO_IP; + cmptr->cmsg_type = IP_PKTINFO; + + if ((ntohs(mess->flags) & 0x8000) || mess->hlen == 0 || + mess->hlen > sizeof(ifr.ifr_addr.sa_data) || mess->htype == 0) + { + /* broadcast to 255.255.255.255 (or mac address invalid) */ + dest.sin_addr.s_addr = INADDR_BROADCAST; + dest.sin_port = htons(daemon->dhcp_client_port); + } + else + { + /* unicast to unconfigured client. Inject mac address direct into ARP cache. + struct sockaddr limits size to 14 bytes. */ + dest.sin_addr = mess->yiaddr; + dest.sin_port = htons(daemon->dhcp_client_port); + memcpy(&arp_req.arp_pa, &dest, sizeof(struct sockaddr_in)); + arp_req.arp_ha.sa_family = mess->htype; + memcpy(arp_req.arp_ha.sa_data, mess->chaddr, mess->hlen); + /* interface name already copied in */ + arp_req.arp_flags = ATF_COM; + if (ioctl(daemon->dhcpfd, SIOCSARP, &arp_req) == -1) + my_syslog(MS_DHCP | LOG_ERR, _("ARP-cache injection failed: %s"), strerror(errno)); + } } #elif defined(HAVE_SOLARIS_NETWORK) else if ((ntohs(mess->flags) & 0x8000) || mess->hlen != ETHER_ADDR_LEN || mess->htype != ARPHRD_ETHER) @@ -411,9 +451,35 @@ void dhcp_packet(time_t now, int pxe_fd) setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &iface_index, sizeof(iface_index)); #endif - while(sendmsg(fd, &msg, 0) == -1 && retry_send()); + while(retry_send(sendmsg(fd, &msg, 0))); } +/* check against secondary interface addresses */ +static int check_listen_addrs(struct in_addr local, int if_index, char *label, + struct in_addr netmask, struct in_addr broadcast, void *vparam) +{ + struct match_param *param = vparam; + struct iname *tmp; + + (void) label; + + if (if_index == param->ind) + { + for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) + if ( tmp->addr.sa.sa_family == AF_INET && + tmp->addr.in.sin_addr.s_addr == local.s_addr) + { + param->matched = 1; + param->addr = local; + param->netmask = netmask; + param->broadcast = broadcast; + break; + } + } + + return 1; +} + /* This is a complex routine: it gets called with each (address,netmask,broadcast) triple of each interface (and any relay address) and does the following things: @@ -424,11 +490,14 @@ void dhcp_packet(time_t now, int pxe_fd) Note that the current chain may be superceded later for configured hosts or those coming via gateways. */ -static int complete_context(struct in_addr local, int if_index, +static int complete_context(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam) { struct dhcp_context *context; + struct dhcp_relay *relay; struct iface_param *param = vparam; + + (void)label; for (context = daemon->dhcp; context; context = context->next) { @@ -448,40 +517,38 @@ static int complete_context(struct in_addr local, int if_index, context->netmask = netmask; } - if (context->netmask.s_addr) + if (context->netmask.s_addr != 0 && + is_same_net(local, context->start, context->netmask) && + is_same_net(local, context->end, context->netmask)) { - if (is_same_net(local, context->start, context->netmask) && - is_same_net(local, context->end, context->netmask)) + /* link it onto the current chain if we've not seen it before */ + if (if_index == param->ind && context->current == context) { - /* link it onto the current chain if we've not seen it before */ - if (if_index == param->ind && context->current == context) - { - context->router = local; - context->local = local; - context->current = param->current; - param->current = context; - } - - if (!(context->flags & CONTEXT_BRDCAST)) - { - if (is_same_net(broadcast, context->start, context->netmask)) - context->broadcast = broadcast; - else - context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr; - } - } - else if (param->relay.s_addr && is_same_net(param->relay, context->start, context->netmask)) + context->router = local; + context->local = local; + context->current = param->current; + param->current = context; + } + + if (!(context->flags & CONTEXT_BRDCAST)) { - context->router = param->relay; - context->local = param->primary; - /* fill in missing broadcast addresses for relayed ranges */ - if (!(context->flags & CONTEXT_BRDCAST)) + if (is_same_net(broadcast, context->start, context->netmask)) + context->broadcast = broadcast; + else context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr; } - - } + } } + for (relay = daemon->relay4; relay; relay = relay->next) + if (if_index == param->ind && relay->local.addr.addr4.s_addr == local.s_addr && relay->current == relay && + (param->relay_local.s_addr == 0 || param->relay_local.s_addr == local.s_addr)) + { + relay->current = param->relay; + param->relay = relay; + param->relay_local = local; + } + return 1; } @@ -505,7 +572,7 @@ struct dhcp_context *address_available(struct dhcp_context *context, start = ntohl(tmp->start.s_addr); end = ntohl(tmp->end.s_addr); - if (!(tmp->flags & CONTEXT_STATIC) && + if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) && addr >= start && addr <= end && match_netid(tmp->filter, netids, 1)) @@ -540,7 +607,8 @@ struct dhcp_context *narrow_context(struct dhcp_context *context, if (!tmp) for (tmp = context; tmp; tmp = tmp->current) if (match_netid(tmp->filter, netids, 1) && - is_same_net(taddr, tmp->start, tmp->netmask)) + is_same_net(taddr, tmp->start, tmp->netmask) && + !(tmp->flags & CONTEXT_PROXY)) break; } @@ -562,50 +630,6 @@ struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct i return NULL; } -/* Is every member of check matched by a member of pool? - If tagnotneeded, untagged is OK */ -int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int tagnotneeded) -{ - struct dhcp_netid *tmp1; - - if (!check && !tagnotneeded) - return 0; - - for (; check; check = check->next) - { - /* '#' for not is for backwards compat. */ - if (check->net[0] != '!' && check->net[0] != '#') - { - for (tmp1 = pool; tmp1; tmp1 = tmp1->next) - if (strcmp(check->net, tmp1->net) == 0) - break; - if (!tmp1) - return 0; - } - else - for (tmp1 = pool; tmp1; tmp1 = tmp1->next) - if (strcmp((check->net)+1, tmp1->net) == 0) - return 0; - } - return 1; -} - -struct dhcp_netid *run_tag_if(struct dhcp_netid *tags) -{ - struct tag_if *exprs; - struct dhcp_netid_list *list; - - for (exprs = daemon->tag_if; exprs; exprs = exprs->next) - if (match_netid(exprs->tag, tags, 1)) - for (list = exprs->set; list; list = list->next) - { - list->list->next = tags; - tags = list->list; - } - - return tags; -} - int address_allocate(struct dhcp_context *context, struct in_addr *addrp, unsigned char *hwaddr, int hw_len, struct dhcp_netid *netids, time_t now) @@ -626,16 +650,22 @@ int address_allocate(struct dhcp_context *context, for (pass = 0; pass <= 1; pass++) for (c = context; c; c = c->current) - if (c->flags & CONTEXT_STATIC) + if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) continue; else if (!match_netid(c->filter, netids, pass)) continue; else { - /* pick a seed based on hwaddr then iterate until we find a free address. */ - start.s_addr = addr.s_addr = - htonl(ntohl(c->start.s_addr) + - ((j + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr)))); + if (option_bool(OPT_CONSEC_ADDR)) + /* seed is largest extant lease addr in this context */ + start = lease_find_max_addr(c); + else + /* pick a seed based on hwaddr */ + start.s_addr = htonl(ntohl(c->start.s_addr) + + ((j + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr)))); + + /* iterate until we find a free address. */ + addr = start; do { /* eliminate addresses in use by the server. */ @@ -660,9 +690,6 @@ int address_allocate(struct dhcp_context *context, *addrp = addr; - if (option_bool(OPT_NO_PING)) - return 1; - /* check if we failed to ping addr sometime in the last PING_CACHE_TIME seconds. If so, assume the same situation still exists. This avoids problems when a stupid client bangs @@ -672,33 +699,51 @@ int address_allocate(struct dhcp_context *context, for (count = 0, r = daemon->ping_results; r; r = r->next) if (difftime(now, r->time) > (float)PING_CACHE_TIME) victim = r; /* old record */ - else if (++count == max || r->addr.s_addr == addr.s_addr) - return 1; - - if (icmp_ping(addr)) - /* address in use: perturb address selection so that we are - less likely to try this address again. */ - c->addr_epoch++; - else + else + { + count++; + if (r->addr.s_addr == addr.s_addr) + { + /* consec-ip mode: we offered this address for another client + (different hash) recently, don't offer it to this one. */ + if (option_bool(OPT_CONSEC_ADDR) && r->hash != j) + break; + + return 1; + } + } + + if (!r) { - /* at this point victim may hold an expired record */ - if (!victim) + if ((count < max) && !option_bool(OPT_NO_PING) && icmp_ping(addr)) { - if ((victim = whine_malloc(sizeof(struct ping_result)))) - { - victim->next = daemon->ping_results; - daemon->ping_results = victim; - } + /* address in use: perturb address selection so that we are + less likely to try this address again. */ + if (!option_bool(OPT_CONSEC_ADDR)) + c->addr_epoch++; } - - /* record that this address is OK for 30s - without more ping checks */ - if (victim) + else { - victim->addr = addr; - victim->time = now; + /* at this point victim may hold an expired record */ + if (!victim) + { + if ((victim = whine_malloc(sizeof(struct ping_result)))) + { + victim->next = daemon->ping_results; + daemon->ping_results = victim; + } + } + + /* record that this address is OK for 30s + without more ping checks */ + if (victim) + { + victim->addr = addr; + victim->time = now; + victim->hash = j; + } + return 1; } - return 1; } } @@ -709,92 +754,10 @@ int address_allocate(struct dhcp_context *context, } while (addr.s_addr != start.s_addr); } - return 0; -} - -static int is_addr_in_context(struct dhcp_context *context, struct dhcp_config *config) -{ - if (!context) /* called via find_config() from lease_update_from_configs() */ - return 1; - if (!(config->flags & CONFIG_ADDR)) - return 1; - for (; context; context = context->current) - if (is_same_net(config->addr, context->start, context->netmask)) - return 1; - - return 0; -} -int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type) -{ - struct hwaddr_config *conf_addr; - - for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) - if (conf_addr->wildcard_mask == 0 && - conf_addr->hwaddr_len == len && - (conf_addr->hwaddr_type == type || conf_addr->hwaddr_type == 0) && - memcmp(conf_addr->hwaddr, hwaddr, len) == 0) - return 1; - return 0; } -struct dhcp_config *find_config(struct dhcp_config *configs, - struct dhcp_context *context, - unsigned char *clid, int clid_len, - unsigned char *hwaddr, int hw_len, - int hw_type, char *hostname) -{ - int count, new; - struct dhcp_config *config, *candidate; - struct hwaddr_config *conf_addr; - - if (clid) - for (config = configs; config; config = config->next) - if (config->flags & CONFIG_CLID) - { - if (config->clid_len == clid_len && - memcmp(config->clid, clid, clid_len) == 0 && - is_addr_in_context(context, config)) - return config; - - /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and - cope with that here */ - if (*clid == 0 && config->clid_len == clid_len-1 && - memcmp(config->clid, clid+1, clid_len-1) == 0 && - is_addr_in_context(context, config)) - return config; - } - - - for (config = configs; config; config = config->next) - if (config_has_mac(config, hwaddr, hw_len, hw_type) && - is_addr_in_context(context, config)) - return config; - - if (hostname && context) - for (config = configs; config; config = config->next) - if ((config->flags & CONFIG_NAME) && - hostname_isequal(config->hostname, hostname) && - is_addr_in_context(context, config)) - return config; - - /* use match with fewest wildcast octets */ - for (candidate = NULL, count = 0, config = configs; config; config = config->next) - if (is_addr_in_context(context, config)) - for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) - if (conf_addr->wildcard_mask != 0 && - conf_addr->hwaddr_len == hw_len && - (conf_addr->hwaddr_type == hw_type || conf_addr->hwaddr_type == 0) && - (new = memcmp_masked(conf_addr->hwaddr, hwaddr, hw_len, conf_addr->wildcard_mask)) > count) - { - count = new; - candidate = config; - } - - return candidate; -} - void dhcp_read_ethers(void) { FILE *f = fopen(ETHERSFILE, "r"); @@ -956,85 +919,6 @@ void dhcp_read_ethers(void) my_syslog(MS_DHCP | LOG_INFO, _("read %s - %d addresses"), ETHERSFILE, count); } -void check_dhcp_hosts(int fatal) -{ - /* If the same IP appears in more than one host config, then DISCOVER - for one of the hosts will get the address, but REQUEST will be NAKed, - since the address is reserved by the other one -> protocol loop. - Also check that FQDNs match the domain we are using. */ - - struct dhcp_config *configs, *cp; - - for (configs = daemon->dhcp_conf; configs; configs = configs->next) - { - char *domain; - - if ((configs->flags & DHOPT_BANK) || fatal) - { - for (cp = configs->next; cp; cp = cp->next) - if ((configs->flags & cp->flags & CONFIG_ADDR) && configs->addr.s_addr == cp->addr.s_addr) - { - if (fatal) - die(_("duplicate IP address %s in dhcp-config directive."), - inet_ntoa(cp->addr), EC_BADCONF); - else - my_syslog(MS_DHCP | LOG_ERR, _("duplicate IP address %s in %s."), - inet_ntoa(cp->addr), daemon->dhcp_hosts_file); - configs->flags &= ~CONFIG_ADDR; - } - - /* split off domain part */ - if ((configs->flags & CONFIG_NAME) && (domain = strip_hostname(configs->hostname))) - configs->domain = domain; - } - } -} - -void dhcp_update_configs(struct dhcp_config *configs) -{ - /* Some people like to keep all static IP addresses in /etc/hosts. - This goes through /etc/hosts and sets static addresses for any DHCP config - records which don't have an address and whose name matches. - We take care to maintain the invariant that any IP address can appear - in at most one dhcp-host. Since /etc/hosts can be re-read by SIGHUP, - restore the status-quo ante first. */ - - struct dhcp_config *config; - struct crec *crec; - - for (config = configs; config; config = config->next) - if (config->flags & CONFIG_ADDR_HOSTS) - config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR_HOSTS); - - - if (daemon->port != 0) - for (config = configs; config; config = config->next) - if (!(config->flags & CONFIG_ADDR) && - (config->flags & CONFIG_NAME) && - (crec = cache_find_by_name(NULL, config->hostname, 0, F_IPV4)) && - (crec->flags & F_HOSTS)) - { - if (cache_find_by_name(crec, config->hostname, 0, F_IPV4)) - { - /* use primary (first) address */ - while (crec && !(crec->flags & F_REVERSE)) - crec = cache_find_by_name(crec, config->hostname, 0, F_IPV4); - if (!crec) - continue; /* should be never */ - my_syslog(MS_DHCP | LOG_WARNING, _("%s has more than one address in hostsfile, using %s for DHCP"), - config->hostname, inet_ntoa(crec->addr.addr.addr.addr4)); - } - - if (config_find_by_address(configs, crec->addr.addr.addr.addr4)) - my_syslog(MS_DHCP | LOG_WARNING, _("duplicate IP address %s (%s) in dhcp-config directive"), - inet_ntoa(crec->addr.addr.addr.addr4), config->hostname); - else - { - config->addr = crec->addr.addr.addr.addr4; - config->flags |= CONFIG_ADDR | CONFIG_ADDR_HOSTS; - } - } -} /* If we've not found a hostname any other way, try and see if there's one in /etc/hosts for this address. If it has a domain part, that must match the set domain and @@ -1075,20 +959,74 @@ char *host_from_dns(struct in_addr addr) return NULL; } -/* return domain or NULL if none. */ -char *strip_hostname(char *hostname) +static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index) { - char *dot = strchr(hostname, '.'); - - if (!dot) - return NULL; + /* ->local is same value for all relays on ->current chain */ + struct all_addr from; - *dot = 0; /* truncate */ - if (strlen(dot+1) != 0) - return dot+1; + if (mess->op != BOOTREQUEST) + return 0; + + /* source address == relay address */ + from.addr.addr4 = relay->local.addr.addr4; - return NULL; + /* already gatewayed ? */ + if (mess->giaddr.s_addr) + { + /* if so check if by us, to stomp on loops. */ + if (mess->giaddr.s_addr == relay->local.addr.addr4.s_addr) + return 1; + } + else + { + /* plug in our address */ + mess->giaddr.s_addr = relay->local.addr.addr4.s_addr; + } + + if ((mess->hops++) > 20) + return 1; + + for (; relay; relay = relay->current) + { + union mysockaddr to; + + to.sa.sa_family = AF_INET; + to.in.sin_addr = relay->server.addr.addr4; + to.in.sin_port = htons(daemon->dhcp_server_port); + + send_from(daemon->dhcpfd, 0, (char *)mess, sz, &to, &from, 0); + + if (option_bool(OPT_LOG_OPTS)) + { + inet_ntop(AF_INET, &relay->local, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, inet_ntoa(relay->server.addr.addr4)); + } + + /* Save this for replies */ + relay->iface_index = iface_index; + } + + return 1; } -#endif +static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface) +{ + struct dhcp_relay *relay; + + if (mess->giaddr.s_addr == 0 || mess->op != BOOTREPLY) + return NULL; + + for (relay = daemon->relay4; relay; relay = relay->next) + { + if (mess->giaddr.s_addr == relay->local.addr.addr4.s_addr) + { + if (!relay->interface || wildcard_match(relay->interface, arrival_interface)) + return relay->iface_index != 0 ? relay : NULL; + } + } + + return NULL; +} + +#endif diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h new file mode 100644 index 0000000..928a2fa --- /dev/null +++ b/src/dhcp6-protocol.h @@ -0,0 +1,75 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define DHCPV6_SERVER_PORT 547 +#define DHCPV6_CLIENT_PORT 546 + +#define ALL_SERVERS "FF05::1:3" +#define ALL_RELAY_AGENTS_AND_SERVERS "FF02::1:2" + +#define DHCP6SOLICIT 1 +#define DHCP6ADVERTISE 2 +#define DHCP6REQUEST 3 +#define DHCP6CONFIRM 4 +#define DHCP6RENEW 5 +#define DHCP6REBIND 6 +#define DHCP6REPLY 7 +#define DHCP6RELEASE 8 +#define DHCP6DECLINE 9 +#define DHCP6RECONFIGURE 10 +#define DHCP6IREQ 11 +#define DHCP6RELAYFORW 12 +#define DHCP6RELAYREPL 13 + +#define OPTION6_CLIENT_ID 1 +#define OPTION6_SERVER_ID 2 +#define OPTION6_IA_NA 3 +#define OPTION6_IA_TA 4 +#define OPTION6_IAADDR 5 +#define OPTION6_ORO 6 +#define OPTION6_PREFERENCE 7 +#define OPTION6_ELAPSED_TIME 8 +#define OPTION6_RELAY_MSG 9 +#define OPTION6_AUTH 11 +#define OPTION6_UNICAST 12 +#define OPTION6_STATUS_CODE 13 +#define OPTION6_RAPID_COMMIT 14 +#define OPTION6_USER_CLASS 15 +#define OPTION6_VENDOR_CLASS 16 +#define OPTION6_VENDOR_OPTS 17 +#define OPTION6_INTERFACE_ID 18 +#define OPTION6_RECONFIGURE_MSG 19 +#define OPTION6_RECONF_ACCEPT 20 +#define OPTION6_DNS_SERVER 23 +#define OPTION6_DOMAIN_SEARCH 24 +#define OPTION6_REFRESH_TIME 32 +#define OPTION6_REMOTE_ID 37 +#define OPTION6_SUBSCRIBER_ID 38 +#define OPTION6_FQDN 39 +#define OPTION6_CLIENT_MAC 79 + +/* replace this with the real number when allocated. + defining this also enables the relevant code. */ +/* #define OPTION6_PREFIX_CLASS 99 */ + + +#define DHCP6SUCCESS 0 +#define DHCP6UNSPEC 1 +#define DHCP6NOADDRS 2 +#define DHCP6NOBINDING 3 +#define DHCP6NOTONLINK 4 +#define DHCP6USEMULTI 5 + diff --git a/src/dhcp6.c b/src/dhcp6.c new file mode 100644 index 0000000..8286ff4 --- /dev/null +++ b/src/dhcp6.c @@ -0,0 +1,806 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_DHCP6 + +#include <netinet/icmp6.h> + +struct iface_param { + struct dhcp_context *current; + struct dhcp_relay *relay; + struct in6_addr fallback, relay_local, ll_addr, ula_addr; + int ind, addr_match; +}; + +struct mac_param { + struct in6_addr *target; + unsigned char *mac; + unsigned int maclen; +}; + + +static int complete_context6(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, + unsigned int preferred, unsigned int valid, void *vparam); +static int find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv); +static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm); + +void dhcp6_init(void) +{ + int fd; + struct sockaddr_in6 saddr; +#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6) + int class = IPTOS_CLASS_CS6; +#endif + int oneopt = 1; + + if ((fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1 || +#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6) + setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 || +#endif + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &oneopt, sizeof(oneopt)) == -1 || + !fix_fd(fd) || + !set_ipv6pktinfo(fd)) + die (_("cannot create DHCPv6 socket: %s"), NULL, EC_BADNET); + + /* When bind-interfaces is set, there might be more than one dnmsasq + instance binding port 547. That's OK if they serve different networks. + Need to set REUSEADDR|REUSEPORT to make this posible. + Handle the case that REUSEPORT is defined, but the kernel doesn't + support it. This handles the introduction of REUSEPORT on Linux. */ + if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)) + { + int rc = 0; + +#ifdef SO_REUSEPORT + if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt))) == -1 && + errno == ENOPROTOOPT) + rc = 0; +#endif + + if (rc != -1) + rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt)); + + if (rc == -1) + die(_("failed to set SO_REUSE{ADDR|PORT} on DHCPv6 socket: %s"), NULL, EC_BADNET); + } + + memset(&saddr, 0, sizeof(saddr)); +#ifdef HAVE_SOCKADDR_SA_LEN + saddr.sin6_len = sizeof(struct sockaddr_in6); +#endif + saddr.sin6_family = AF_INET6; + saddr.sin6_addr = in6addr_any; + saddr.sin6_port = htons(DHCPV6_SERVER_PORT); + + if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6))) + die(_("failed to bind DHCPv6 server socket: %s"), NULL, EC_BADNET); + + daemon->dhcp6fd = fd; +} + +void dhcp6_packet(time_t now) +{ + struct dhcp_context *context; + struct dhcp_relay *relay; + struct iface_param parm; + struct cmsghdr *cmptr; + struct msghdr msg; + int if_index = 0; + union { + struct cmsghdr align; /* this ensures alignment */ + char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } control_u; + struct sockaddr_in6 from; + ssize_t sz; + struct ifreq ifr; + struct iname *tmp; + unsigned short port; + struct in6_addr dst_addr; + + memset(&dst_addr, 0, sizeof(dst_addr)); + + msg.msg_control = control_u.control6; + msg.msg_controllen = sizeof(control_u); + msg.msg_flags = 0; + msg.msg_name = &from; + msg.msg_namelen = sizeof(from); + msg.msg_iov = &daemon->dhcp_packet; + msg.msg_iovlen = 1; + + if ((sz = recv_dhcp_packet(daemon->dhcp6fd, &msg)) == -1) + return; + + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) + { + union { + unsigned char *c; + struct in6_pktinfo *p; + } p; + p.c = CMSG_DATA(cmptr); + + if_index = p.p->ipi6_ifindex; + dst_addr = p.p->ipi6_addr; + } + + if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name)) + return; + + if ((port = relay_reply6(&from, sz, ifr.ifr_name)) == 0) + { + struct dhcp_bridge *bridge, *alias; + + for (tmp = daemon->if_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + return; + + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + return; + + parm.current = NULL; + parm.relay = NULL; + memset(&parm.relay_local, 0, IN6ADDRSZ); + parm.ind = if_index; + parm.addr_match = 0; + memset(&parm.fallback, 0, IN6ADDRSZ); + memset(&parm.ll_addr, 0, IN6ADDRSZ); + memset(&parm.ula_addr, 0, IN6ADDRSZ); + + /* If the interface on which the DHCPv6 request was received is + an alias of some other interface (as specified by the + --bridge-interface option), change parm.ind so that we look + for DHCPv6 contexts associated with the aliased interface + instead of with the aliasing one. */ + for (bridge = daemon->bridges; bridge; bridge = bridge->next) + { + for (alias = bridge->alias; alias; alias = alias->next) + if (wildcard_matchn(alias->iface, ifr.ifr_name, IF_NAMESIZE)) + { + parm.ind = if_nametoindex(bridge->iface); + if (!parm.ind) + { + my_syslog(MS_DHCP | LOG_WARNING, + _("unknown interface %s in bridge-interface"), + bridge->iface); + return; + } + break; + } + if (alias) + break; + } + + for (context = daemon->dhcp6; context; context = context->next) + if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0) + { + /* wildcard context for DHCP-stateless only */ + parm.current = context; + context->current = NULL; + } + else + { + /* unlinked contexts are marked by context->current == context */ + context->current = context; + memset(&context->local6, 0, IN6ADDRSZ); + } + + for (relay = daemon->relay6; relay; relay = relay->next) + relay->current = relay; + + if (!iface_enumerate(AF_INET6, &parm, complete_context6)) + return; + + if (daemon->if_names || daemon->if_addrs) + { + + for (tmp = daemon->if_names; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + break; + + if (!tmp && !parm.addr_match) + return; + } + + if (parm.relay) + { + /* Ignore requests sent to the ALL_SERVERS multicast address for relay when + we're listening there for DHCPv6 server reasons. */ + struct in6_addr all_servers; + + inet_pton(AF_INET6, ALL_SERVERS, &all_servers); + + if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers)) + relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id); + return; + } + + /* May have configured relay, but not DHCP server */ + if (!daemon->doing_dhcp6) + return; + + lease_prune(NULL, now); /* lose any expired leases */ + + port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, + &parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now); + + lease_update_file(now); + lease_update_dns(0); + } + + /* The port in the source address of the original request should + be correct, but at least once client sends from the server port, + so we explicitly send to the client port to a client, and the + server port to a relay. */ + if (port != 0) + { + from.sin6_port = htons(port); + while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, + save_counter(0), 0, (struct sockaddr *)&from, + sizeof(from)))); + } +} + +void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep) +{ + /* Recieving a packet from a host does not populate the neighbour + cache, so we send a neighbour discovery request if we can't + find the sender. Repeat a few times in case of packet loss. */ + + struct neigh_packet neigh; + struct sockaddr_in6 addr; + struct mac_param mac_param; + int i; + + neigh.type = ND_NEIGHBOR_SOLICIT; + neigh.code = 0; + neigh.reserved = 0; + neigh.target = *client; + /* RFC4443 section-2.3: checksum has to be zero to be calculated */ + neigh.checksum = 0; + + memset(&addr, 0, sizeof(addr)); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.sin6_len = sizeof(struct sockaddr_in6); +#endif + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(IPPROTO_ICMPV6); + addr.sin6_addr = *client; + addr.sin6_scope_id = iface; + + mac_param.target = client; + mac_param.maclen = 0; + mac_param.mac = mac; + + for (i = 0; i < 5; i++) + { + struct timespec ts; + + iface_enumerate(AF_UNSPEC, &mac_param, find_mac); + + if (mac_param.maclen != 0) + break; + + sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, (struct sockaddr *)&addr, sizeof(addr)); + + ts.tv_sec = 0; + ts.tv_nsec = 100000000; /* 100ms */ + nanosleep(&ts, NULL); + } + + *maclenp = mac_param.maclen; + *mactypep = ARPHRD_ETHER; +} + +static int find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) +{ + struct mac_param *parm = parmv; + + if (family == AF_INET6 && IN6_ARE_ADDR_EQUAL(parm->target, (struct in6_addr *)addrp)) + { + if (maclen <= DHCP_CHADDR_MAX) + { + parm->maclen = maclen; + memcpy(parm->mac, mac, maclen); + } + + return 0; /* found, abort */ + } + + return 1; +} + +static int complete_context6(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, unsigned int preferred, + unsigned int valid, void *vparam) +{ + struct dhcp_context *context; + struct dhcp_relay *relay; + struct iface_param *param = vparam; + struct iname *tmp; + + (void)scope; /* warning */ + + if (if_index == param->ind) + { + if (IN6_IS_ADDR_LINKLOCAL(local)) + param->ll_addr = *local; + else if (IN6_IS_ADDR_ULA(local)) + param->ula_addr = *local; + + if (!IN6_IS_ADDR_LOOPBACK(local) && + !IN6_IS_ADDR_LINKLOCAL(local) && + !IN6_IS_ADDR_MULTICAST(local)) + { + /* if we have --listen-address config, see if the + arrival interface has a matching address. */ + for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) + if (tmp->addr.sa.sa_family == AF_INET6 && + IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local)) + param->addr_match = 1; + + /* Determine a globally address on the arrival interface, even + if we have no matching dhcp-context, because we're only + allocating on remote subnets via relays. This + is used as a default for the DNS server option. */ + param->fallback = *local; + + for (context = daemon->dhcp6; context; context = context->next) + { + if ((context->flags & CONTEXT_DHCP) && + !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && + prefix <= context->prefix && + is_same_net6(local, &context->start6, context->prefix) && + is_same_net6(local, &context->end6, context->prefix)) + { + + + /* link it onto the current chain if we've not seen it before */ + if (context->current == context) + { + struct dhcp_context *tmp, **up; + + /* use interface values only for contructed contexts */ + if (!(context->flags & CONTEXT_CONSTRUCTED)) + preferred = valid = 0xffffffff; + else if (flags & IFACE_DEPRECATED) + preferred = 0; + + if (context->flags & CONTEXT_DEPRECATE) + preferred = 0; + + /* order chain, longest preferred time first */ + for (up = ¶m->current, tmp = param->current; tmp; tmp = tmp->current) + if (tmp->preferred <= preferred) + break; + else + up = &tmp->current; + + context->current = *up; + *up = context; + context->local6 = *local; + context->preferred = preferred; + context->valid = valid; + } + } + } + } + + for (relay = daemon->relay6; relay; relay = relay->next) + if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr.addr6) && relay->current == relay && + (IN6_IS_ADDR_UNSPECIFIED(¶m->relay_local) || IN6_ARE_ADDR_EQUAL(local, ¶m->relay_local))) + { + relay->current = param->relay; + param->relay = relay; + param->relay_local = *local; + } + + } + + return 1; +} + +struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr) +{ + struct dhcp_config *config; + + for (config = configs; config; config = config->next) + if ((config->flags & CONFIG_ADDR6) && + is_same_net6(&config->addr6, net, prefix) && + (prefix == 128 || addr6part(&config->addr6) == addr)) + return config; + + return NULL; +} + +struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, int temp_addr, + int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans) +{ + /* Find a free address: exclude anything in use and anything allocated to + a particular hwaddr/clientid/hostname in our configuration. + Try to return from contexts which match netids first. + + Note that we assume the address prefix lengths are 64 or greater, so we can + get by with 64 bit arithmetic. +*/ + + u64 start, addr; + struct dhcp_context *c, *d; + int i, pass; + u64 j; + + /* hash hwaddr: use the SDBM hashing algorithm. This works + for MAC addresses, let's see how it manages with client-ids! + For temporary addresses, we generate a new random one each time. */ + if (temp_addr) + j = rand64(); + else + for (j = iaid, i = 0; i < clid_len; i++) + j += clid[i] + (j << 6) + (j << 16) - j; + + for (pass = 0; pass <= plain_range ? 1 : 0; pass++) + for (c = context; c; c = c->current) + if (c->flags & (CONTEXT_DEPRECATE | CONTEXT_STATIC | CONTEXT_RA_STATELESS | CONTEXT_USED)) + continue; + else if (!match_netid(c->filter, netids, pass)) + continue; + else + { + if (!temp_addr && option_bool(OPT_CONSEC_ADDR)) + /* seed is largest extant lease addr in this context */ + start = lease_find_max_addr6(c) + serial; + else + start = addr6part(&c->start6) + ((j + c->addr_epoch) % (1 + addr6part(&c->end6) - addr6part(&c->start6))); + + /* iterate until we find a free address. */ + addr = start; + + do { + /* eliminate addresses in use by the server. */ + for (d = context; d; d = d->current) + if (addr == addr6part(&d->local6)) + break; + + if (!d && + !lease6_find_by_addr(&c->start6, c->prefix, addr) && + !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, addr)) + { + *ans = c->start6; + setaddr6part (ans, addr); + return c; + } + + addr++; + + if (addr == addr6part(&c->end6) + 1) + addr = addr6part(&c->start6); + + } while (addr != start); + } + + return NULL; +} + +/* can dynamically allocate addr */ +struct dhcp_context *address6_available(struct dhcp_context *context, + struct in6_addr *taddr, + struct dhcp_netid *netids, + int plain_range) +{ + u64 start, end, addr = addr6part(taddr); + struct dhcp_context *tmp; + + for (tmp = context; tmp; tmp = tmp->current) + { + start = addr6part(&tmp->start6); + end = addr6part(&tmp->end6); + + if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_RA_STATELESS)) && + is_same_net6(&tmp->start6, taddr, tmp->prefix) && + is_same_net6(&tmp->end6, taddr, tmp->prefix) && + addr >= start && + addr <= end && + match_netid(tmp->filter, netids, plain_range)) + return tmp; + } + + return NULL; +} + +/* address OK if configured */ +struct dhcp_context *address6_valid(struct dhcp_context *context, + struct in6_addr *taddr, + struct dhcp_netid *netids, + int plain_range) +{ + struct dhcp_context *tmp; + + for (tmp = context; tmp; tmp = tmp->current) + if (is_same_net6(&tmp->start6, taddr, tmp->prefix) && + match_netid(tmp->filter, netids, plain_range)) + return tmp; + + return NULL; +} + +int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr) +{ + if (!config || !(config->flags & CONFIG_ADDR6)) + return 0; + + if ((config->flags & CONFIG_WILDCARD) && context->prefix == 64) + { + *addr = context->start6; + setaddr6part(addr, addr6part(&config->addr6)); + return 1; + } + + if (is_same_net6(&context->start6, &config->addr6, context->prefix)) + { + *addr = config->addr6; + return 1; + } + + return 0; +} + +void make_duid(time_t now) +{ + (void)now; + + if (daemon->duid_config) + { + unsigned char *p; + + daemon->duid = p = safe_malloc(daemon->duid_config_len + 6); + daemon->duid_len = daemon->duid_config_len + 6; + PUTSHORT(2, p); /* DUID_EN */ + PUTLONG(daemon->duid_enterprise, p); + memcpy(p, daemon->duid_config, daemon->duid_config_len); + } + else + { + time_t newnow = 0; + + /* If we have no persistent lease database, or a non-stable RTC, use DUID_LL (newnow == 0) */ +#ifndef HAVE_BROKEN_RTC + /* rebase epoch to 1/1/2000 */ + if (!option_bool(OPT_LEASE_RO) || daemon->lease_change_command) + newnow = now - 946684800; +#endif + + iface_enumerate(AF_LOCAL, &newnow, make_duid1); + + if(!daemon->duid) + die("Cannot create DHCPv6 server DUID: %s", NULL, EC_MISC); + } +} + +static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm) +{ + /* create DUID as specified in RFC3315. We use the MAC of the + first interface we find that isn't loopback or P-to-P and + has address-type < 256. Address types above 256 are things like + tunnels which don't have usable MAC addresses. */ + + unsigned char *p; + (void)index; + (void)parm; + time_t newnow = *((time_t *)parm); + + if (type >= 256) + return 1; + + if (newnow == 0) + { + daemon->duid = p = safe_malloc(maclen + 4); + daemon->duid_len = maclen + 4; + PUTSHORT(3, p); /* DUID_LL */ + PUTSHORT(type, p); /* address type */ + } + else + { + daemon->duid = p = safe_malloc(maclen + 8); + daemon->duid_len = maclen + 8; + PUTSHORT(1, p); /* DUID_LLT */ + PUTSHORT(type, p); /* address type */ + PUTLONG(*((time_t *)parm), p); /* time */ + } + + memcpy(p, mac, maclen); + + return 0; +} + +struct cparam { + time_t now; + int newone, newname; +}; + +static int construct_worker(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, + int preferred, int valid, void *vparam) +{ + char ifrn_name[IFNAMSIZ]; + struct in6_addr start6, end6; + struct dhcp_context *template, *context; + + (void)scope; + (void)flags; + (void)valid; + (void)preferred; + + struct cparam *param = vparam; + + if (IN6_IS_ADDR_LOOPBACK(local) || + IN6_IS_ADDR_LINKLOCAL(local) || + IN6_IS_ADDR_MULTICAST(local)) + return 1; + + if (!(flags & IFACE_PERMANENT)) + return 1; + + if (flags & IFACE_DEPRECATED) + return 1; + + if (!indextoname(daemon->icmp6fd, if_index, ifrn_name)) + return 0; + + for (template = daemon->dhcp6; template; template = template->next) + if (!(template->flags & CONTEXT_TEMPLATE)) + { + /* non-template entries, just fill in interface and local addresses */ + if (prefix <= template->prefix && + is_same_net6(local, &template->start6, template->prefix) && + is_same_net6(local, &template->end6, template->prefix)) + { + template->if_index = if_index; + template->local6 = *local; + } + + } + else if (wildcard_match(template->template_interface, ifrn_name) && + template->prefix >= prefix) + { + start6 = *local; + setaddr6part(&start6, addr6part(&template->start6)); + end6 = *local; + setaddr6part(&end6, addr6part(&template->end6)); + + for (context = daemon->dhcp6; context; context = context->next) + if ((context->flags & CONTEXT_CONSTRUCTED) && + IN6_ARE_ADDR_EQUAL(&start6, &context->start6) && + IN6_ARE_ADDR_EQUAL(&end6, &context->end6)) + { + int flags = context->flags; + context->flags &= ~(CONTEXT_GC | CONTEXT_OLD); + if (flags & CONTEXT_OLD) + { + /* address went, now it's back */ + log_context(AF_INET6, context); + /* fast RAs for a while */ + ra_start_unsolicted(param->now, context); + param->newone = 1; + /* Add address to name again */ + if (context->flags & CONTEXT_RA_NAME) + param->newname = 1; + } + break; + } + + if (!context && (context = whine_malloc(sizeof (struct dhcp_context)))) + { + *context = *template; + context->start6 = start6; + context->end6 = end6; + context->flags &= ~CONTEXT_TEMPLATE; + context->flags |= CONTEXT_CONSTRUCTED; + context->if_index = if_index; + context->local6 = *local; + context->saved_valid = 0; + + context->next = daemon->dhcp6; + daemon->dhcp6 = context; + + ra_start_unsolicted(param->now, context); + /* we created a new one, need to call + lease_update_file to get periodic functions called */ + param->newone = 1; + + /* Will need to add new putative SLAAC addresses to existing leases */ + if (context->flags & CONTEXT_RA_NAME) + param->newname = 1; + + log_context(AF_INET6, context); + } + } + + return 1; +} + +void dhcp_construct_contexts(time_t now) +{ + struct dhcp_context *context, *tmp, **up; + struct cparam param; + param.newone = 0; + param.newname = 0; + param.now = now; + + for (context = daemon->dhcp6; context; context = context->next) + if (context->flags & CONTEXT_CONSTRUCTED) + context->flags |= CONTEXT_GC; + + iface_enumerate(AF_INET6, ¶m, construct_worker); + + for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp) + { + + tmp = context->next; + + if (context->flags & CONTEXT_GC && !(context->flags & CONTEXT_OLD)) + { + if ((context->flags & CONTEXT_RA) || option_bool(OPT_RA)) + { + /* previously constructed context has gone. advertise it's demise */ + context->flags |= CONTEXT_OLD; + context->address_lost_time = now; + /* Apply same ceiling of configured lease time as in radv.c */ + if (context->saved_valid > context->lease_time) + context->saved_valid = context->lease_time; + /* maximum time is 2 hours, from RFC */ + if (context->saved_valid > 7200) /* 2 hours */ + context->saved_valid = 7200; + ra_start_unsolicted(now, context); + param.newone = 1; /* include deletion */ + + if (context->flags & CONTEXT_RA_NAME) + param.newname = 1; + + log_context(AF_INET6, context); + + up = &context->next; + } + else + { + /* we were never doing RA for this, so free now */ + *up = context->next; + free(context); + } + } + else + up = &context->next; + } + + if (param.newone) + { + if (daemon->dhcp || daemon->doing_dhcp6) + { + if (param.newname) + lease_update_slaac(now); + lease_update_file(now); + } + else + /* Not doing DHCP, so no lease system, manage alarms for ra only */ + send_alarm(periodic_ra(now), now); + } +} + +#endif + + diff --git a/src/dns_protocol.h b/src/dns-protocol.h index bc18e79..6cf5158 100644 --- a/src/dns_protocol.h +++ b/src/dns-protocol.h @@ -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 @@ -14,6 +14,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#define NAMESERVER_PORT 53 +#define TFTP_PORT 69 + #define IN6ADDRSZ 16 #define INADDRSZ 4 @@ -33,43 +36,68 @@ #define C_IN 1 /* the arpa internet */ #define C_CHAOS 3 /* for chaos net (MIT) */ +#define C_HESIOD 4 /* hesiod */ #define C_ANY 255 /* wildcard match */ #define T_A 1 -#define T_NS 2 +#define T_NS 2 +#define T_MD 3 +#define T_MF 4 #define T_CNAME 5 #define T_SOA 6 +#define T_MB 7 +#define T_MG 8 +#define T_MR 9 #define T_PTR 12 +#define T_MINFO 14 #define T_MX 15 #define T_TXT 16 +#define T_RP 17 +#define T_AFSDB 18 +#define T_RT 21 #define T_SIG 24 +#define T_PX 26 #define T_AAAA 28 +#define T_NXT 30 #define T_SRV 33 #define T_NAPTR 35 +#define T_KX 36 +#define T_DNAME 39 #define T_OPT 41 +#define T_DS 43 +#define T_RRSIG 46 +#define T_NSEC 47 +#define T_DNSKEY 48 +#define T_NSEC3 50 #define T_TKEY 249 #define T_TSIG 250 +#define T_AXFR 252 #define T_MAILB 253 #define T_ANY 255 +#define EDNS0_OPTION_MAC 65001 /* dyndns.org temporary assignment */ +#define EDNS0_OPTION_CLIENT_SUBNET 8 /* IANA */ + struct dns_header { u16 id; u8 hb3,hb4; u16 qdcount,ancount,nscount,arcount; -} ; +}; -#define HB3_QR 0x80 +#define HB3_QR 0x80 /* Query */ #define HB3_OPCODE 0x78 -#define HB3_AA 0x04 -#define HB3_TC 0x02 -#define HB3_RD 0x01 +#define HB3_AA 0x04 /* Authoritative Answer */ +#define HB3_TC 0x02 /* TrunCated */ +#define HB3_RD 0x01 /* Recursion Desired */ -#define HB4_RA 0x80 -#define HB4_AD 0x20 -#define HB4_CD 0x10 +#define HB4_RA 0x80 /* Recursion Available */ +#define HB4_AD 0x20 /* Authenticated Data */ +#define HB4_CD 0x10 /* Checking Disabled */ #define HB4_RCODE 0x0f #define OPCODE(x) (((x)->hb3 & HB3_OPCODE) >> 3) +#define SET_OPCODE(x, code) (x)->hb3 = ((x)->hb3 & ~HB3_OPCODE) | code + #define RCODE(x) ((x)->hb4 & HB4_RCODE) #define SET_RCODE(x, code) (x)->hb4 = ((x)->hb4 & ~HB4_RCODE) | code @@ -109,3 +137,16 @@ struct dns_header { (cp) += 4; \ } +#define CHECK_LEN(header, pp, plen, len) \ + ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) + +#define ADD_RDLEN(header, pp, plen, len) \ + (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1)) + +/* Escape character in our presentation format for names. + Cannot be '.' or /000 and must be !isprint(). + Note that escaped chars are stored as + <NAME_ESCAPE> <orig-char+1> + to ensure that the escaped form of /000 doesn't include /000 +*/ +#define NAME_ESCAPE 1 diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 827b0dc..04d5758 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.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 @@ -14,58 +14,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/* Declare static char *compiler_opts in config.h */ +#define DNSMASQ_COMPILE_OPTS + #include "dnsmasq.h" struct daemon *daemon; -static char *compile_opts = -#ifndef HAVE_IPV6 -"no-" -#endif -"IPv6 " -#ifndef HAVE_GETOPT_LONG -"no-" -#endif -"GNU-getopt " -#ifdef HAVE_BROKEN_RTC -"no-RTC " -#endif -#ifdef NO_FORK -"no-MMU " -#endif -#ifndef HAVE_DBUS -"no-" -#endif -"DBus " -#ifndef LOCALEDIR -"no-" -#endif -"I18N " -#ifndef HAVE_DHCP -"no-" -#endif -"DHCP " -#if defined(HAVE_DHCP) && !defined(HAVE_SCRIPT) -"no-scripts " -#endif -#ifndef HAVE_TFTP -"no-" -#endif -"TFTP " -#if !defined(LOCALEDIR) && !defined(HAVE_IDN) -"no-" -#endif -"IDN"; - - static volatile pid_t pid = 0; static volatile int pipewrite; -static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp); -static void check_dns_listeners(fd_set *set, time_t now); +static int set_dns_listeners(time_t now); +static void check_dns_listeners(time_t now); static void sig_handler(int sig); static void async_event(int pipe, time_t now); -static void fatal_event(struct event_desc *ev); +static void fatal_event(struct event_desc *ev, char *msg); +static int read_event(int fd, struct event_desc *evp, char **msg); +static void poll_resolv(int force, int do_reload, time_t now); int main (int argc, char **argv) { @@ -75,7 +40,7 @@ int main (int argc, char **argv) struct iname *if_tmp; int piperead, pipefd[2], err_pipe[2]; struct passwd *ent_pw = NULL; -#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) +#if defined(HAVE_SCRIPT) uid_t script_uid = 0; gid_t script_gid = 0; #endif @@ -86,7 +51,16 @@ int main (int argc, char **argv) #if defined(HAVE_LINUX_NETWORK) cap_user_header_t hdr = NULL; cap_user_data_t data = NULL; + char *bound_device = NULL; + int did_bind = 0; #endif +#if defined(HAVE_DHCP) || defined(HAVE_DHCP6) + struct dhcp_context *context; + struct dhcp_relay *relay; +#endif +#ifdef HAVE_TFTP + int tftp_prefix_missing = 0; +#endif #ifdef LOCALEDIR setlocale(LC_ALL, ""); @@ -109,45 +83,112 @@ int main (int argc, char **argv) sigaction(SIGPIPE, &sigact, NULL); umask(022); /* known umask, create leases and pid files as 0644 */ - + + rand_init(); /* Must precede read_opts() */ + read_opts(argc, argv, compile_opts); - + if (daemon->edns_pktsz < PACKETSZ) daemon->edns_pktsz = PACKETSZ; + daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ? daemon->edns_pktsz : DNSMASQ_PACKETSZ; daemon->packet = safe_malloc(daemon->packet_buff_sz); + + daemon->addrbuff = safe_malloc(ADDRSTRLEN); + if (option_bool(OPT_EXTRALOG)) + daemon->addrbuff2 = safe_malloc(ADDRSTRLEN); + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + /* Note that both /000 and '.' are allowed within labels. These get + represented in presentation format using NAME_ESCAPE as an escape + character when in DNSSEC mode. + In theory, if all the characters in a name were /000 or + '.' or NAME_ESCAPE then all would have to be escaped, so the + presentation format would be twice as long as the spec. + + daemon->namebuff was previously allocated by the option-reading + code before we knew if we're in DNSSEC mode, so reallocate here. */ + free(daemon->namebuff); + daemon->namebuff = safe_malloc(MAXDNAME * 2); + daemon->keyname = safe_malloc(MAXDNAME * 2); + daemon->workspacename = safe_malloc(MAXDNAME * 2); + } +#endif #ifdef HAVE_DHCP if (!daemon->lease_file) { - if (daemon->dhcp) + if (daemon->dhcp || daemon->dhcp6) daemon->lease_file = LEASEFILE; } #endif - /* Close any file descriptors we inherited apart from std{in|out|err} */ + /* Close any file descriptors we inherited apart from std{in|out|err} + + Ensure that at least stdin, stdout and stderr (fd 0, 1, 2) exist, + otherwise file descriptors we create can end up being 0, 1, or 2 + and then get accidentally closed later when we make 0, 1, and 2 + open to /dev/null. Normally we'll be started with 0, 1 and 2 open, + but it's not guaranteed. By opening /dev/null three times, we + ensure that we're not using those fds for real stuff. */ for (i = 0; i < max_fd; i++) if (i != STDOUT_FILENO && i != STDERR_FILENO && i != STDIN_FILENO) close(i); + else + open("/dev/null", O_RDWR); -#ifdef HAVE_LINUX_NETWORK - netlink_init(); -#elif !(defined(IP_RECVDSTADDR) && \ - defined(IP_RECVIF) && \ - defined(IP_SENDSRCADDR)) +#ifndef HAVE_LINUX_NETWORK +# if !(defined(IP_RECVDSTADDR) && defined(IP_RECVIF) && defined(IP_SENDSRCADDR)) if (!option_bool(OPT_NOWILD)) { bind_fallback = 1; set_option_bool(OPT_NOWILD); } +# endif + + /* -- bind-dynamic not supported on !Linux, fall back to --bind-interfaces */ + if (option_bool(OPT_CLEVERBIND)) + { + bind_fallback = 1; + set_option_bool(OPT_NOWILD); + reset_option_bool(OPT_CLEVERBIND); + } +#endif + +#ifndef HAVE_INOTIFY + if (daemon->dynamic_dirs) + die(_("dhcp-hostsdir, dhcp-optsdir and hostsdir are not supported on this platform"), NULL, EC_BADCONF); +#endif + + if (option_bool(OPT_DNSSEC_VALID)) + { +#ifdef HAVE_DNSSEC + if (!daemon->ds) + die(_("no trust anchors provided for DNSSEC"), NULL, EC_BADCONF); + + if (daemon->cachesize < CACHESIZ) + die(_("cannot reduce cache size from default when DNSSEC enabled"), NULL, EC_BADCONF); +#else + die(_("DNSSEC not available: set HAVE_DNSSEC in src/config.h"), NULL, EC_BADCONF); #endif + } #ifndef HAVE_TFTP - if (daemon->tftp_unlimited || daemon->tftp_interfaces) + if (option_bool(OPT_TFTP)) die(_("TFTP server not available: set HAVE_TFTP in src/config.h"), NULL, EC_BADCONF); #endif +#ifdef HAVE_CONNTRACK + if (option_bool(OPT_CONNTRACK) && (daemon->query_port != 0 || daemon->osport)) + die (_("cannot use --conntrack AND --query-port"), NULL, EC_BADCONF); +#else + if (option_bool(OPT_CONNTRACK)) + die(_("conntrack support not available: set HAVE_CONNTRACK in src/config.h"), NULL, EC_BADCONF); +#endif + #ifdef HAVE_SOLARIS_NETWORK if (daemon->max_logs != 0) die(_("asychronous logging is not available under Solaris"), NULL, EC_BADCONF); @@ -158,45 +199,151 @@ int main (int argc, char **argv) die(_("asychronous logging is not available under Android"), NULL, EC_BADCONF); #endif - rand_init(); +#ifndef HAVE_AUTH + if (daemon->authserver) + die(_("authoritative DNS not available: set HAVE_AUTH in src/config.h"), NULL, EC_BADCONF); +#endif + +#ifndef HAVE_LOOP + if (option_bool(OPT_LOOP_DETECT)) + die(_("loop detection not available: set HAVE_LOOP in src/config.h"), NULL, EC_BADCONF); +#endif now = dnsmasq_time(); + + /* Create a serial at startup if not configured. */ + if (daemon->authinterface && daemon->soa_sn == 0) +#ifdef HAVE_BROKEN_RTC + die(_("zone serial must be configured in --auth-soa"), NULL, EC_BADCONF); +#else + daemon->soa_sn = now; +#endif + +#ifdef HAVE_DHCP6 + if (daemon->dhcp6) + { + daemon->doing_ra = option_bool(OPT_RA); + + for (context = daemon->dhcp6; context; context = context->next) + { + if (context->flags & CONTEXT_DHCP) + daemon->doing_dhcp6 = 1; + if (context->flags & CONTEXT_RA) + daemon->doing_ra = 1; +#if !defined(HAVE_LINUX_NETWORK) && !defined(HAVE_BSD_NETWORK) + if (context->flags & CONTEXT_TEMPLATE) + die (_("dhcp-range constructor not available on this platform"), NULL, EC_BADCONF); +#endif + } + } +#endif #ifdef HAVE_DHCP - if (daemon->dhcp) + /* Note that order matters here, we must call lease_init before + creating any file descriptors which shouldn't be leaked + to the lease-script init process. We need to call common_init + before lease_init to allocate buffers it uses.*/ + if (daemon->dhcp || daemon->doing_dhcp6 || daemon->relay4 || daemon->relay6) { - /* Note that order matters here, we must call lease_init before - creating any file descriptors which shouldn't be leaked - to the lease-script init process. */ - lease_init(now); - dhcp_init(); + dhcp_common_init(); + if (daemon->dhcp || daemon->doing_dhcp6) + lease_init(now); } + + if (daemon->dhcp || daemon->relay4) + dhcp_init(); + +# ifdef HAVE_DHCP6 + if (daemon->doing_ra || daemon->doing_dhcp6 || daemon->relay6) + ra_init(now); + + if (daemon->doing_dhcp6 || daemon->relay6) + dhcp6_init(); +# endif + +#endif + +#ifdef HAVE_IPSET + if (daemon->ipsets) + ipset_init(); #endif - if (!enumerate_interfaces()) +#if defined(HAVE_LINUX_NETWORK) + netlink_init(); +#elif defined(HAVE_BSD_NETWORK) + route_init(); +#endif + + if (option_bool(OPT_NOWILD) && option_bool(OPT_CLEVERBIND)) + die(_("cannot set --bind-interfaces and --bind-dynamic"), NULL, EC_BADCONF); + + if (!enumerate_interfaces(1) || !enumerate_interfaces(0)) die(_("failed to find list of interfaces: %s"), NULL, EC_MISC); - - if (option_bool(OPT_NOWILD)) + + if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)) { - daemon->listeners = create_bound_listeners(); + create_bound_listeners(1); + + if (!option_bool(OPT_CLEVERBIND)) + for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next) + if (if_tmp->name && !if_tmp->used) + die(_("unknown interface %s"), if_tmp->name, EC_BADNET); + +#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP) + /* after enumerate_interfaces() */ + bound_device = whichdevice(); + + if (daemon->dhcp) + { + if (!daemon->relay4 && bound_device) + { + bindtodevice(bound_device, daemon->dhcpfd); + did_bind = 1; + } + if (daemon->enable_pxe && bound_device) + { + bindtodevice(bound_device, daemon->pxefd); + did_bind = 1; + } + } +#endif - for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next) - if (if_tmp->name && !if_tmp->used) - die(_("unknown interface %s"), if_tmp->name, EC_BADNET); - - for (if_tmp = daemon->if_addrs; if_tmp; if_tmp = if_tmp->next) - if (!if_tmp->used) - { - prettyprint_addr(&if_tmp->addr, daemon->namebuff); - die(_("no interface with address %s"), daemon->namebuff, EC_BADNET); - } +#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP6) + if (daemon->doing_dhcp6 && !daemon->relay6 && bound_device) + { + bindtodevice(bound_device, daemon->dhcp6fd); + did_bind = 1; + } +#endif } else - daemon->listeners = create_wildcard_listeners(); + create_wildcard_listeners(); + +#ifdef HAVE_DHCP6 + /* after enumerate_interfaces() */ + if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra) + join_multicast(1); + + /* After netlink_init() and before create_helper() */ + lease_make_duid(now); +#endif if (daemon->port != 0) - cache_init(); - + { + cache_init(); + +#ifdef HAVE_DNSSEC + blockdata_init(); +#endif + } + +#ifdef HAVE_INOTIFY + if (daemon->port != 0 || daemon->dhcp || daemon->doing_dhcp6) + inotify_dnsmasq_init(); + else + daemon->inotifyfd = -1; +#endif + if (option_bool(OPT_DBUS)) #ifdef HAVE_DBUS { @@ -209,13 +356,15 @@ int main (int argc, char **argv) #else die(_("DBus not available: set HAVE_DBUS in src/config.h"), NULL, EC_BADCONF); #endif - + if (daemon->port != 0) pre_allocate_sfds(); -#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) +#if defined(HAVE_SCRIPT) /* Note getpwnam returns static storage */ - if (daemon->dhcp && daemon->lease_change_command && daemon->scriptuser) + if ((daemon->dhcp || daemon->dhcp6) && + daemon->scriptuser && + (daemon->lease_change_command || daemon->luascript)) { if ((ent_pw = getpwnam(daemon->scriptuser))) { @@ -234,7 +383,7 @@ int main (int argc, char **argv) if (baduser) die(_("unknown user or group: %s"), baduser, EC_BADCONF); - + /* implement group defaults, "dip" if available, or group associated with uid */ if (!daemon->group_set && !gp) { @@ -278,7 +427,7 @@ int main (int argc, char **argv) piperead = pipefd[0]; pipewrite = pipefd[1]; /* prime the pipe to load stuff first time. */ - send_event(pipewrite, EVENT_RELOAD, 0); + send_event(pipewrite, EVENT_INIT, 0, NULL); err_pipe[1] = -1; @@ -301,30 +450,31 @@ int main (int argc, char **argv) if ((pid = fork()) == -1) /* fd == -1 since we've not forked, never returns. */ - send_event(-1, EVENT_FORK_ERR, errno); + send_event(-1, EVENT_FORK_ERR, errno, NULL); if (pid != 0) { struct event_desc ev; - + char *msg; + /* close our copy of write-end */ - close(err_pipe[1]); + while (retry_send(close(err_pipe[1]))); /* check for errors after the fork */ - if (read_write(err_pipe[0], (unsigned char *)&ev, sizeof(ev), 1)) - fatal_event(&ev); + if (read_event(err_pipe[0], &ev, &msg)) + fatal_event(&ev, msg); _exit(EC_GOOD); } - close(err_pipe[0]); + while (retry_send(close(err_pipe[0]))); /* NO calls to die() from here on. */ setsid(); if ((pid = fork()) == -1) - send_event(err_pipe[1], EVENT_FORK_ERR, errno); + send_event(err_pipe[1], EVENT_FORK_ERR, errno, NULL); if (pid != 0) _exit(0); @@ -334,17 +484,52 @@ int main (int argc, char **argv) /* write pidfile _after_ forking ! */ if (daemon->runfile) { - FILE *pidfile; + int fd, err = 0; + + sprintf(daemon->namebuff, "%d\n", (int) getpid()); + + /* Explanation: Some installations of dnsmasq (eg Debian/Ubuntu) locate the pid-file + in a directory which is writable by the non-privileged user that dnsmasq runs as. This + allows the daemon to delete the file as part of its shutdown. This is a security hole to the + extent that an attacker running as the unprivileged user could replace the pidfile with a + symlink, and have the target of that symlink overwritten as root next time dnsmasq starts. + + The folowing code first deletes any existing file, and then opens it with the O_EXCL flag, + ensuring that the open() fails should there be any existing file (because the unlink() failed, + or an attacker exploited the race between unlink() and open()). This ensures that no symlink + attack can succeed. + + Any compromise of the non-privileged user still theoretically allows the pid-file to be + replaced whilst dnsmasq is running. The worst that could allow is that the usual + "shutdown dnsmasq" shell command could be tricked into stopping any other process. + + Note that if dnsmasq is started as non-root (eg for testing) it silently ignores + failure to write the pid-file. + */ + + unlink(daemon->runfile); - /* only complain if started as root */ - if ((pidfile = fopen(daemon->runfile, "w"))) + if ((fd = open(daemon->runfile, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)) == -1) + { + /* only complain if started as root */ + if (getuid() == 0) + err = 1; + } + else { - fprintf(pidfile, "%d\n", (int) getpid()); - fclose(pidfile); + if (!read_write(fd, (unsigned char *)daemon->namebuff, strlen(daemon->namebuff), 0)) + err = 1; + else + { + while (retry_send(close(fd))); + if (errno != 0) + err = 1; + } } - else if (getuid() == 0) + + if (err) { - send_event(err_pipe[1], EVENT_PIDFILE, errno); + send_event(err_pipe[1], EVENT_PIDFILE, errno, daemon->runfile); _exit(0); } } @@ -364,8 +549,8 @@ int main (int argc, char **argv) /* if we are to run scripts, we need to fork a helper before dropping root. */ daemon->helperfd = -1; -#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) - if (daemon->dhcp && daemon->lease_change_command) +#ifdef HAVE_SCRIPT + if ((daemon->dhcp || daemon->dhcp6) && (daemon->lease_change_command || daemon->luascript)) daemon->helperfd = create_helper(pipewrite, err_pipe[1], script_uid, script_gid, max_fd); #endif @@ -379,17 +564,24 @@ int main (int argc, char **argv) (setgroups(0, &dummy) == -1 || setgid(gp->gr_gid) == -1)) { - send_event(err_pipe[1], EVENT_GROUP_ERR, errno); + send_event(err_pipe[1], EVENT_GROUP_ERR, errno, daemon->groupname); _exit(0); } if (ent_pw && ent_pw->pw_uid != 0) { -#if defined(HAVE_LINUX_NETWORK) +#if defined(HAVE_LINUX_NETWORK) /* On linux, we keep CAP_NETADMIN (for ARP-injection) and - CAP_NET_RAW (for icmp) if we're doing dhcp */ - data->effective = data->permitted = data->inheritable = - (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID); + CAP_NET_RAW (for icmp) if we're doing dhcp. If we have yet to bind + ports because of DAD, or we're doing it dynamically, + we need CAP_NET_BIND_SERVICE too. */ + if (is_dad_listeners() || option_bool(OPT_CLEVERBIND)) + data->effective = data->permitted = data->inheritable = + (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | + (1 << CAP_SETUID) | (1 << CAP_NET_BIND_SERVICE); + else + data->effective = data->permitted = data->inheritable = + (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID); /* Tell kernel to not clear capabilities when dropping root */ if (capset(hdr, data) == -1 || prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) @@ -419,26 +611,30 @@ int main (int argc, char **argv) if (bad_capabilities != 0) { - send_event(err_pipe[1], EVENT_CAP_ERR, bad_capabilities); + send_event(err_pipe[1], EVENT_CAP_ERR, bad_capabilities, NULL); _exit(0); } /* finally drop root */ if (setuid(ent_pw->pw_uid) == -1) { - send_event(err_pipe[1], EVENT_USER_ERR, errno); + send_event(err_pipe[1], EVENT_USER_ERR, errno, daemon->username); _exit(0); } #ifdef HAVE_LINUX_NETWORK - data->effective = data->permitted = - (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW); + if (is_dad_listeners() || option_bool(OPT_CLEVERBIND)) + data->effective = data->permitted = + (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_NET_BIND_SERVICE); + else + data->effective = data->permitted = + (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW); data->inheritable = 0; /* lose the setuid and setgid capbilities */ if (capset(hdr, data) == -1) { - send_event(err_pipe[1], EVENT_CAP_ERR, errno); + send_event(err_pipe[1], EVENT_CAP_ERR, errno, NULL); _exit(0); } #endif @@ -447,10 +643,51 @@ int main (int argc, char **argv) } #ifdef HAVE_LINUX_NETWORK + free(hdr); + free(data); if (option_bool(OPT_DEBUG)) prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); #endif +#ifdef HAVE_TFTP + if (option_bool(OPT_TFTP)) + { + DIR *dir; + struct tftp_prefix *p; + + if (daemon->tftp_prefix) + { + if (!((dir = opendir(daemon->tftp_prefix)))) + { + tftp_prefix_missing = 1; + if (!option_bool(OPT_TFTP_NO_FAIL)) + { + send_event(err_pipe[1], EVENT_TFTP_ERR, errno, daemon->tftp_prefix); + _exit(0); + } + } + else + closedir(dir); + } + + for (p = daemon->if_prefix; p; p = p->next) + { + p->missing = 0; + if (!((dir = opendir(p->prefix)))) + { + p->missing = 1; + if (!option_bool(OPT_TFTP_NO_FAIL)) + { + send_event(err_pipe[1], EVENT_TFTP_ERR, errno, p->prefix); + _exit(0); + } + } + else + closedir(dir); + } + } +#endif + if (daemon->port == 0) my_syslog(LOG_INFO, _("started, version %s DNS disabled"), VERSION); else if (daemon->cachesize != 0) @@ -470,12 +707,44 @@ int main (int argc, char **argv) } #endif + if (option_bool(OPT_LOCAL_SERVICE)) + my_syslog(LOG_INFO, _("DNS service limited to local subnets")); + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + int rc; + + /* Delay creating the timestamp file until here, after we've changed user, so that + it has the correct owner to allow updating the mtime later. + This means we have to report fatal errors via the pipe. */ + if ((rc = setup_timestamp()) == -1) + { + send_event(err_pipe[1], EVENT_TIME_ERR, errno, daemon->timestamp_file); + _exit(0); + } + + my_syslog(LOG_INFO, _("DNSSEC validation enabled")); + + if (option_bool(OPT_DNSSEC_TIME)) + my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until first cache reload")); + + if (rc == 1) + my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until system time valid")); + } +#endif + if (log_err != 0) my_syslog(LOG_WARNING, _("warning: failed to change owner of %s: %s"), daemon->log_file, strerror(log_err)); - + if (bind_fallback) my_syslog(LOG_WARNING, _("setting --bind-interfaces option because of OS limitations")); + + if (option_bool(OPT_NOWILD)) + warn_bound_listeners(); + + warn_int_names(); if (!option_bool(OPT_NOWILD)) for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next) @@ -493,40 +762,56 @@ int main (int argc, char **argv) if (daemon->max_logs != 0) my_syslog(LOG_INFO, _("asynchronous logging enabled, queue limit is %d messages"), daemon->max_logs); + #ifdef HAVE_DHCP - if (daemon->dhcp) - { - struct dhcp_context *dhcp_tmp; - - for (dhcp_tmp = daemon->dhcp; dhcp_tmp; dhcp_tmp = dhcp_tmp->next) - { - prettyprint_time(daemon->dhcp_buff2, dhcp_tmp->lease_time); - strcpy(daemon->dhcp_buff, inet_ntoa(dhcp_tmp->start)); - my_syslog(MS_DHCP | LOG_INFO, - (dhcp_tmp->flags & CONTEXT_STATIC) ? - _("DHCP, static leases only on %.0s%s, lease time %s") : - (dhcp_tmp->flags & CONTEXT_PROXY) ? - _("DHCP, proxy on subnet %.0s%s%.0s") : - _("DHCP, IP range %s -- %s, lease time %s"), - daemon->dhcp_buff, inet_ntoa(dhcp_tmp->end), daemon->dhcp_buff2); - } - } + for (context = daemon->dhcp; context; context = context->next) + log_context(AF_INET, context); + + for (relay = daemon->relay4; relay; relay = relay->next) + log_relay(AF_INET, relay); + +# ifdef HAVE_DHCP6 + for (context = daemon->dhcp6; context; context = context->next) + log_context(AF_INET6, context); + + for (relay = daemon->relay6; relay; relay = relay->next) + log_relay(AF_INET6, relay); + + if (daemon->doing_dhcp6 || daemon->doing_ra) + dhcp_construct_contexts(now); + + if (option_bool(OPT_RA)) + my_syslog(MS_DHCP | LOG_INFO, _("IPv6 router advertisement enabled")); +# endif + +# ifdef HAVE_LINUX_NETWORK + if (did_bind) + my_syslog(MS_DHCP | LOG_INFO, _("DHCP, sockets bound exclusively to interface %s"), bound_device); +# endif + + /* after dhcp_contruct_contexts */ + if (daemon->dhcp || daemon->doing_dhcp6) + lease_find_interfaces(now); #endif #ifdef HAVE_TFTP - if (daemon->tftp_unlimited || daemon->tftp_interfaces) + if (option_bool(OPT_TFTP)) { -#ifdef FD_SETSIZE - if (FD_SETSIZE < (unsigned)max_fd) - max_fd = FD_SETSIZE; -#endif + struct tftp_prefix *p; my_syslog(MS_TFTP | LOG_INFO, "TFTP %s%s %s", daemon->tftp_prefix ? _("root is ") : _("enabled"), daemon->tftp_prefix ? daemon->tftp_prefix: "", option_bool(OPT_TFTP_SECURE) ? _("secure mode") : ""); - + + if (tftp_prefix_missing) + my_syslog(MS_TFTP | LOG_WARNING, _("warning: %s inaccessible"), daemon->tftp_prefix); + + for (p = daemon->if_prefix; p; p = p->next) + if (p->missing) + my_syslog(MS_TFTP | LOG_WARNING, _("warning: TFTP directory %s inaccessible"), p->prefix); + /* This is a guess, it assumes that for small limits, disjoint files might be served, but for large limits, a single file will be sent to may clients (the file only needs @@ -559,100 +844,133 @@ int main (int argc, char **argv) /* finished start-up - release original process */ if (err_pipe[1] != -1) - close(err_pipe[1]); + while (retry_send(close(err_pipe[1]))); if (daemon->port != 0) check_servers(); pid = getpid(); +#ifdef HAVE_INOTIFY + /* Using inotify, have to select a resolv file at startup */ + poll_resolv(1, 0, now); +#endif + while (1) { - int maxfd = -1; - struct timeval t, *tp = NULL; - fd_set rset, wset, eset; + int t, timeout = -1; - FD_ZERO(&rset); - FD_ZERO(&wset); - FD_ZERO(&eset); + poll_reset(); /* if we are out of resources, find how long we have to wait for some to come free, we'll loop around then and restart listening for queries */ - if ((t.tv_sec = set_dns_listeners(now, &rset, &maxfd)) != 0) - { - t.tv_usec = 0; - tp = &t; - } + if ((t = set_dns_listeners(now)) != 0) + timeout = t * 1000; /* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */ if (daemon->tftp_trans || (option_bool(OPT_DBUS) && !daemon->dbus)) - { - t.tv_sec = 0; - t.tv_usec = 250000; - tp = &t; - } + timeout = 250; + + /* Wake every second whilst waiting for DAD to complete */ + else if (is_dad_listeners()) + timeout = 1000; #ifdef HAVE_DBUS - set_dbus_listeners(&maxfd, &rset, &wset, &eset); + set_dbus_listeners(); #endif #ifdef HAVE_DHCP - if (daemon->dhcp) + if (daemon->dhcp || daemon->relay4) { - FD_SET(daemon->dhcpfd, &rset); - bump_maxfd(daemon->dhcpfd, &maxfd); + poll_listen(daemon->dhcpfd, POLLIN); if (daemon->pxefd != -1) - { - FD_SET(daemon->pxefd, &rset); - bump_maxfd(daemon->pxefd, &maxfd); - } + poll_listen(daemon->pxefd, POLLIN); } #endif -#ifdef HAVE_LINUX_NETWORK - FD_SET(daemon->netlinkfd, &rset); - bump_maxfd(daemon->netlinkfd, &maxfd); +#ifdef HAVE_DHCP6 + if (daemon->doing_dhcp6 || daemon->relay6) + poll_listen(daemon->dhcp6fd, POLLIN); + + if (daemon->doing_ra) + poll_listen(daemon->icmp6fd, POLLIN); +#endif + +#ifdef HAVE_INOTIFY + if (daemon->inotifyfd != -1) + poll_listen(daemon->inotifyfd, POLLIN); +#endif + +#if defined(HAVE_LINUX_NETWORK) + poll_listen(daemon->netlinkfd, POLLIN); +#elif defined(HAVE_BSD_NETWORK) + poll_listen(daemon->routefd, POLLIN); #endif - FD_SET(piperead, &rset); - bump_maxfd(piperead, &maxfd); + poll_listen(piperead, POLLIN); #ifdef HAVE_DHCP # ifdef HAVE_SCRIPT while (helper_buf_empty() && do_script_run(now)); +# ifdef HAVE_TFTP + while (helper_buf_empty() && do_tftp_script_run()); +# endif + if (!helper_buf_empty()) - { - FD_SET(daemon->helperfd, &wset); - bump_maxfd(daemon->helperfd, &maxfd); - } + poll_listen(daemon->helperfd, POLLOUT); # else /* need this for other side-effects */ while (do_script_run(now)); + +# ifdef HAVE_TFTP + while (do_tftp_script_run()); +# endif + # endif #endif /* must do this just before select(), when we know no more calls to my_syslog() can occur */ - set_log_writer(&wset, &maxfd); + set_log_writer(); + + if (do_poll(timeout) < 0) + continue; - if (select(maxfd+1, &rset, &wset, &eset, tp) < 0) - { - /* otherwise undefined after error */ - FD_ZERO(&rset); FD_ZERO(&wset); FD_ZERO(&eset); - } - now = dnsmasq_time(); - check_log_writer(&wset); + check_log_writer(0); -#ifdef HAVE_LINUX_NETWORK - if (FD_ISSET(daemon->netlinkfd, &rset)) + /* prime. */ + enumerate_interfaces(1); + + /* Check the interfaces to see if any have exited DAD state + and if so, bind the address. */ + if (is_dad_listeners()) + { + enumerate_interfaces(0); + /* NB, is_dad_listeners() == 1 --> we're binding interfaces */ + create_bound_listeners(0); + warn_bound_listeners(); + } + +#if defined(HAVE_LINUX_NETWORK) + if (poll_check(daemon->netlinkfd, POLLIN)) netlink_multicast(); +#elif defined(HAVE_BSD_NETWORK) + if (poll_check(daemon->routefd, POLLIN)) + route_sock(); #endif +#ifdef HAVE_INOTIFY + if (daemon->inotifyfd != -1 && poll_check(daemon->inotifyfd, POLLIN) && inotify_check(now)) + { + if (daemon->port != 0 && !option_bool(OPT_NO_POLL)) + poll_resolv(1, 1, now); + } +#else /* Check for changes to resolv files once per second max. */ /* Don't go silent for long periods if the clock goes backwards. */ if (daemon->last_resolv == 0 || @@ -665,8 +983,9 @@ int main (int argc, char **argv) poll_resolv(0, daemon->last_resolv != 0, now); daemon->last_resolv = now; } - - if (FD_ISSET(piperead, &rset)) +#endif + + if (poll_check(piperead, POLLIN)) async_event(piperead, now); #ifdef HAVE_DBUS @@ -679,26 +998,34 @@ int main (int argc, char **argv) if (daemon->dbus) my_syslog(LOG_INFO, _("connected to system DBus")); } - check_dbus_listeners(&rset, &wset, &eset); + check_dbus_listeners(); #endif - check_dns_listeners(&rset, now); + check_dns_listeners(now); #ifdef HAVE_TFTP - check_tftp_listeners(&rset, now); + check_tftp_listeners(now); #endif #ifdef HAVE_DHCP - if (daemon->dhcp) + if (daemon->dhcp || daemon->relay4) { - if (FD_ISSET(daemon->dhcpfd, &rset)) + if (poll_check(daemon->dhcpfd, POLLIN)) dhcp_packet(now, 0); - if (daemon->pxefd != -1 && FD_ISSET(daemon->pxefd, &rset)) + if (daemon->pxefd != -1 && poll_check(daemon->pxefd, POLLIN)) dhcp_packet(now, 1); } +#ifdef HAVE_DHCP6 + if ((daemon->doing_dhcp6 || daemon->relay6) && poll_check(daemon->dhcp6fd, POLLIN)) + dhcp6_packet(now); + + if (daemon->doing_ra && poll_check(daemon->icmp6fd, POLLIN)) + icmp6_packet(now); +#endif + # ifdef HAVE_SCRIPT - if (daemon->helperfd != -1 && FD_ISSET(daemon->helperfd, &wset)) + if (daemon->helperfd != -1 && poll_check(daemon->helperfd, POLLIN)) helper_write(); # endif #endif @@ -741,28 +1068,75 @@ static void sig_handler(int sig) else return; - send_event(pipewrite, event, 0); + send_event(pipewrite, event, 0, NULL); errno = errsave; } } -void send_event(int fd, int event, int data) +/* now == 0 -> queue immediate callback */ +void send_alarm(time_t event, time_t now) +{ + if (now == 0 || event != 0) + { + /* alarm(0) or alarm(-ve) doesn't do what we want.... */ + if ((now == 0 || difftime(event, now) <= 0.0)) + send_event(pipewrite, EVENT_ALARM, 0, NULL); + else + alarm((unsigned)difftime(event, now)); + } +} + +void queue_event(int event) +{ + send_event(pipewrite, event, 0, NULL); +} + +void send_event(int fd, int event, int data, char *msg) { struct event_desc ev; - + struct iovec iov[2]; + ev.event = event; ev.data = data; + ev.msg_sz = msg ? strlen(msg) : 0; + + iov[0].iov_base = &ev; + iov[0].iov_len = sizeof(ev); + iov[1].iov_base = msg; + iov[1].iov_len = ev.msg_sz; /* error pipe, debug mode. */ if (fd == -1) - fatal_event(&ev); + fatal_event(&ev, msg); else /* pipe is non-blocking and struct event_desc is smaller than PIPE_BUF, so this either fails or writes everything */ - while (write(fd, &ev, sizeof(ev)) == -1 && errno == EINTR); + while (writev(fd, iov, msg ? 2 : 1) == -1 && errno == EINTR); } -static void fatal_event(struct event_desc *ev) +/* NOTE: the memory used to return msg is leaked: use msgs in events only + to describe fatal errors. */ +static int read_event(int fd, struct event_desc *evp, char **msg) +{ + char *buf; + + if (!read_write(fd, (unsigned char *)evp, sizeof(struct event_desc), 1)) + return 0; + + *msg = NULL; + + if (evp->msg_sz != 0 && + (buf = malloc(evp->msg_sz + 1)) && + read_write(fd, (unsigned char *)buf, evp->msg_sz, 1)) + { + buf[evp->msg_sz] = 0; + *msg = buf; + } + + return 1; +} + +static void fatal_event(struct event_desc *ev, char *msg) { errno = ev->data; @@ -781,19 +1155,25 @@ static void fatal_event(struct event_desc *ev) die(_("setting capabilities failed: %s"), NULL, EC_MISC); case EVENT_USER_ERR: - case EVENT_HUSER_ERR: - die(_("failed to change user-id to %s: %s"), - ev->event == EVENT_USER_ERR ? daemon->username : daemon->scriptuser, - EC_MISC); + die(_("failed to change user-id to %s: %s"), msg, EC_MISC); case EVENT_GROUP_ERR: - die(_("failed to change group-id to %s: %s"), daemon->groupname, EC_MISC); + die(_("failed to change group-id to %s: %s"), msg, EC_MISC); case EVENT_PIDFILE: - die(_("failed to open pidfile %s: %s"), daemon->runfile, EC_FILE); + die(_("failed to open pidfile %s: %s"), msg, EC_FILE); case EVENT_LOG_ERR: - die(_("cannot open %s: %s"), daemon->log_file ? daemon->log_file : "log", EC_FILE); + die(_("cannot open log %s: %s"), msg, EC_FILE); + + case EVENT_LUA_ERR: + die(_("failed to load Lua script: %s"), msg, EC_MISC); + + case EVENT_TFTP_ERR: + die(_("TFTP directory %s inaccessible: %s"), msg, EC_FILE); + + case EVENT_TIME_ERR: + die(_("cannot create timestamp file %s: %s" ), msg, EC_BADCONF); } } @@ -801,18 +1181,46 @@ static void async_event(int pipe, time_t now) { pid_t p; struct event_desc ev; - int i; - - if (read_write(pipe, (unsigned char *)&ev, sizeof(ev), 1)) + int i, check = 0; + char *msg; + + /* NOTE: the memory used to return msg is leaked: use msgs in events only + to describe fatal errors. */ + + if (read_event(pipe, &ev, &msg)) switch (ev.event) { case EVENT_RELOAD: +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && option_bool(OPT_DNSSEC_TIME)) + { + my_syslog(LOG_INFO, _("now checking DNSSEC signature timestamps")); + reset_option_bool(OPT_DNSSEC_TIME); + } +#endif + /* fall through */ + + case EVENT_INIT: clear_cache_and_reload(now); - if (daemon->port != 0 && daemon->resolv_files && option_bool(OPT_NO_POLL)) + + if (daemon->port != 0) { - reload_servers(daemon->resolv_files->name); - check_servers(); + if (daemon->resolv_files && option_bool(OPT_NO_POLL)) + { + reload_servers(daemon->resolv_files->name); + check = 1; + } + + if (daemon->servers_file) + { + read_servers_file(); + check = 1; + } + + if (check) + check_servers(); } + #ifdef HAVE_DHCP rerun_scripts(); #endif @@ -825,11 +1233,16 @@ static void async_event(int pipe, time_t now) case EVENT_ALARM: #ifdef HAVE_DHCP - if (daemon->dhcp) + if (daemon->dhcp || daemon->doing_dhcp6) { lease_prune(NULL, now); lease_update_file(now); } +#ifdef HAVE_DHCP6 + else if (daemon->doing_ra) + /* Not doing DHCP, so no lease system, manage alarms for ra only */ + send_alarm(periodic_ra(now), now); +#endif #endif break; @@ -848,11 +1261,11 @@ static void async_event(int pipe, time_t now) break; case EVENT_KILLED: - my_syslog(LOG_WARNING, _("child process killed by signal %d"), ev.data); + my_syslog(LOG_WARNING, _("script process killed by signal %d"), ev.data); break; case EVENT_EXITED: - my_syslog(LOG_WARNING, _("child process exited with status %d"), ev.data); + my_syslog(LOG_WARNING, _("script process exited with status %d"), ev.data); break; case EVENT_EXEC_ERR: @@ -861,9 +1274,10 @@ static void async_event(int pipe, time_t now) break; /* necessary for fatal errors in helper */ - case EVENT_HUSER_ERR: + case EVENT_USER_ERR: case EVENT_DIE: - fatal_event(&ev); + case EVENT_LUA_ERR: + fatal_event(&ev, msg); break; case EVENT_REOPEN: @@ -873,14 +1287,24 @@ static void async_event(int pipe, time_t now) if (daemon->log_file != NULL) log_reopen(daemon->log_file); break; - + + case EVENT_NEWADDR: + newaddress(now); + break; + + case EVENT_NEWROUTE: + resend_query(); + /* Force re-reading resolv file right now, for luck. */ + poll_resolv(0, 1, now); + break; + case EVENT_TERM: /* Knock all our children on the head. */ for (i = 0; i < MAX_PROCS; i++) if (daemon->tcp_pids[i] != 0) kill(daemon->tcp_pids[i], SIGALRM); -#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) +#if defined(HAVE_SCRIPT) /* handle pending lease transitions */ if (daemon->helperfd != -1) { @@ -890,13 +1314,22 @@ static void async_event(int pipe, time_t now) do { helper_write(); } while (!helper_buf_empty() || do_script_run(now)); - close(daemon->helperfd); + while (retry_send(close(daemon->helperfd))); } #endif if (daemon->lease_stream) fclose(daemon->lease_stream); +#ifdef HAVE_DNSSEC + /* update timestamp file on TERM if time is considered valid */ + if (daemon->back_to_the_future) + { + if (utime(daemon->timestamp_file, NULL) == -1) + my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno)); + } +#endif + if (daemon->runfile) unlink(daemon->runfile); @@ -906,7 +1339,7 @@ static void async_event(int pipe, time_t now) } } -void poll_resolv(int force, int do_reload, time_t now) +static void poll_resolv(int force, int do_reload, time_t now) { struct resolvc *res, *latest; struct stat statbuf; @@ -978,25 +1411,35 @@ void poll_resolv(int force, int do_reload, time_t now) void clear_cache_and_reload(time_t now) { + (void)now; + if (daemon->port != 0) cache_reload(); #ifdef HAVE_DHCP - if (daemon->dhcp) + if (daemon->dhcp || daemon->doing_dhcp6) { if (option_bool(OPT_ETHERS)) dhcp_read_ethers(); reread_dhcp(); +#ifdef HAVE_INOTIFY + set_dynamic_inotify(AH_DHCP_HST | AH_DHCP_OPT, 0, NULL, 0); +#endif dhcp_update_configs(daemon->dhcp_conf); - check_dhcp_hosts(0); lease_update_from_configs(); lease_update_file(now); - lease_update_dns(); + lease_update_dns(1); } +#ifdef HAVE_DHCP6 + else if (daemon->doing_ra) + /* Not doing DHCP, so no lease system, manage + alarms for ra only */ + send_alarm(periodic_ra(now), now); +#endif #endif } -static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp) +static int set_dns_listeners(time_t now) { struct serverfd *serverfdp; struct listener *listener; @@ -1008,55 +1451,41 @@ static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp) for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) { tftp++; - FD_SET(transfer->sockfd, set); - bump_maxfd(transfer->sockfd, maxfdp); + poll_listen(transfer->sockfd, POLLIN); } #endif /* will we be able to get memory? */ if (daemon->port != 0) - get_new_frec(now, &wait); + get_new_frec(now, &wait, 0); for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) - { - FD_SET(serverfdp->fd, set); - bump_maxfd(serverfdp->fd, maxfdp); - } - + poll_listen(serverfdp->fd, POLLIN); + if (daemon->port != 0 && !daemon->osport) for (i = 0; i < RANDOM_SOCKS; i++) if (daemon->randomsocks[i].refcount != 0) - { - FD_SET(daemon->randomsocks[i].fd, set); - bump_maxfd(daemon->randomsocks[i].fd, maxfdp); - } - + poll_listen(daemon->randomsocks[i].fd, POLLIN); + for (listener = daemon->listeners; listener; listener = listener->next) { /* only listen for queries if we have resources */ if (listener->fd != -1 && wait == 0) - { - FD_SET(listener->fd, set); - bump_maxfd(listener->fd, maxfdp); - } - + poll_listen(listener->fd, POLLIN); + /* death of a child goes through the select loop, so we don't need to explicitly arrange to wake up here */ if (listener->tcpfd != -1) for (i = 0; i < MAX_PROCS; i++) if (daemon->tcp_pids[i] == 0) { - FD_SET(listener->tcpfd, set); - bump_maxfd(listener->tcpfd, maxfdp); + poll_listen(listener->tcpfd, POLLIN); break; } #ifdef HAVE_TFTP if (tftp <= daemon->tftp_max && listener->tftpfd != -1) - { - FD_SET(listener->tftpfd, set); - bump_maxfd(listener->tftpfd, maxfdp); - } + poll_listen(listener->tftpfd, POLLIN); #endif } @@ -1064,67 +1493,113 @@ static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp) return wait; } -static void check_dns_listeners(fd_set *set, time_t now) +static void check_dns_listeners(time_t now) { struct serverfd *serverfdp; struct listener *listener; int i; for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) - if (FD_ISSET(serverfdp->fd, set)) + if (poll_check(serverfdp->fd, POLLIN)) reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now); if (daemon->port != 0 && !daemon->osport) for (i = 0; i < RANDOM_SOCKS; i++) if (daemon->randomsocks[i].refcount != 0 && - FD_ISSET(daemon->randomsocks[i].fd, set)) + poll_check(daemon->randomsocks[i].fd, POLLIN)) reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now); for (listener = daemon->listeners; listener; listener = listener->next) { - if (listener->fd != -1 && FD_ISSET(listener->fd, set)) + if (listener->fd != -1 && poll_check(listener->fd, POLLIN)) receive_query(listener, now); #ifdef HAVE_TFTP - if (listener->tftpfd != -1 && FD_ISSET(listener->tftpfd, set)) + if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN)) tftp_request(listener, now); #endif - if (listener->tcpfd != -1 && FD_ISSET(listener->tcpfd, set)) + if (listener->tcpfd != -1 && poll_check(listener->tcpfd, POLLIN)) { - int confd; + int confd, client_ok = 1; struct irec *iface = NULL; pid_t p; - - while((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR); + union mysockaddr tcp_addr; + socklen_t tcp_len = sizeof(union mysockaddr); + + while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR); if (confd == -1) continue; + if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1) + { + while (retry_send(close(confd))); + continue; + } + + /* Make sure that the interface list is up-to-date. + + We do this here as we may need the results below, and + the DNS code needs them for --interface-name stuff. + + Multiple calls to enumerate_interfaces() per select loop are + inhibited, so calls to it in the child process (which doesn't select()) + have no effect. This avoids two processes reading from the same + netlink fd and screwing the pooch entirely. + */ + + enumerate_interfaces(0); + if (option_bool(OPT_NOWILD)) - iface = listener->iface; - else + iface = listener->iface; /* May be NULL */ + else { - union mysockaddr tcp_addr; - socklen_t tcp_len = sizeof(union mysockaddr); - /* Check for allowed interfaces when binding the wildcard address: - we do this by looking for an interface with the same address as - the local address of the TCP connection, then looking to see if that's - an allowed interface. As a side effect, we get the netmask of the - interface too, for localisation. */ + int if_index; + char intr_name[IF_NAMESIZE]; + + /* if we can find the arrival interface, check it's one that's allowed */ + if ((if_index = tcp_interface(confd, tcp_addr.sa.sa_family)) != 0 && + indextoname(listener->tcpfd, if_index, intr_name)) + { + struct all_addr addr; + addr.addr.addr4 = tcp_addr.in.sin_addr; +#ifdef HAVE_IPV6 + if (tcp_addr.sa.sa_family == AF_INET6) + addr.addr.addr6 = tcp_addr.in6.sin6_addr; +#endif + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->index == if_index) + break; + + if (!iface && !loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name)) + client_ok = 0; + } - /* interface may be new since startup */ - if (enumerate_interfaces() && - getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) != -1) - for (iface = daemon->interfaces; iface; iface = iface->next) - if (sockaddr_isequal(&iface->addr, &tcp_addr)) - break; + if (option_bool(OPT_CLEVERBIND)) + iface = listener->iface; /* May be NULL */ + else + { + /* Check for allowed interfaces when binding the wildcard address: + we do this by looking for an interface with the same address as + the local address of the TCP connection, then looking to see if that's + an allowed interface. As a side effect, we get the netmask of the + interface too, for localisation. */ + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (sockaddr_isequal(&iface->addr, &tcp_addr)) + break; + + if (!iface) + client_ok = 0; + } } - if (!iface) + if (!client_ok) { shutdown(confd, SHUT_RDWR); - close(confd); + while (retry_send(close(confd))); } #ifndef NO_FORK else if (!option_bool(OPT_DEBUG) && (p = fork()) != 0) @@ -1139,7 +1614,10 @@ static void check_dns_listeners(fd_set *set, time_t now) break; } } - close(confd); + while (retry_send(close(confd))); + + /* The child can use up to TCP_MAX_QUERIES ids, so skip that many. */ + daemon->log_id += TCP_MAX_QUERIES; } #endif else @@ -1147,10 +1625,20 @@ static void check_dns_listeners(fd_set *set, time_t now) unsigned char *buff; struct server *s; int flags; - struct in_addr dst_addr_4; - - dst_addr_4.s_addr = 0; - + struct in_addr netmask; + int auth_dns; + + if (iface) + { + netmask = iface->netmask; + auth_dns = iface->dns_auth; + } + else + { + netmask.s_addr = 0; + auth_dns = 0; + } + #ifndef NO_FORK /* Arrange for SIGALARM after CHILD_LIFETIME seconds to terminate the process. */ @@ -1168,13 +1656,10 @@ static void check_dns_listeners(fd_set *set, time_t now) if ((flags = fcntl(confd, F_GETFL, 0)) != -1) fcntl(confd, F_SETFL, flags & ~O_NONBLOCK); - if (listener->family == AF_INET) - dst_addr_4 = iface->addr.in.sin_addr; - - buff = tcp_request(confd, now, dst_addr_4, iface->netmask); + buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns); shutdown(confd, SHUT_RDWR); - close(confd); + while (retry_send(close(confd))); if (buff) free(buff); @@ -1183,7 +1668,7 @@ static void check_dns_listeners(fd_set *set, time_t now) if (s->tcpfd != -1) { shutdown(s->tcpfd, SHUT_RDWR); - close(s->tcpfd); + while (retry_send(close(s->tcpfd))); } #ifndef NO_FORK if (!option_bool(OPT_DEBUG)) @@ -1225,14 +1710,22 @@ int icmp_ping(struct in_addr addr) better not use any resources our caller has in use...) but we remain deaf to signals or further DHCP packets. */ - int fd; + /* There can be a problem using dnsmasq_time() to end the loop, since + it's not monotonic, and can go backwards if the system clock is + tweaked, leading to the code getting stuck in this loop and + ignoring DHCP requests. To fix this, we check to see if select returned + as a result of a timeout rather than a socket becoming available. We + only allow this to happen as many times as it takes to get to the wait time + in quarter-second chunks. This provides a fallback way to end loop. */ + + int fd, rc; struct sockaddr_in saddr; struct { struct ip ip; struct icmp icmp; } packet; unsigned short id = rand16(); - unsigned int i, j; + unsigned int i, j, timeout_count; int gotreply = 0; time_t start, now; @@ -1261,44 +1754,47 @@ int icmp_ping(struct in_addr addr) j = (j & 0xffff) + (j >> 16); packet.icmp.icmp_cksum = (j == 0xffff) ? j : ~j; - while (sendto(fd, (char *)&packet.icmp, sizeof(struct icmp), 0, - (struct sockaddr *)&saddr, sizeof(saddr)) == -1 && - retry_send()); + while (retry_send(sendto(fd, (char *)&packet.icmp, sizeof(struct icmp), 0, + (struct sockaddr *)&saddr, sizeof(saddr)))); - for (now = start = dnsmasq_time(); - difftime(now, start) < (float)PING_WAIT;) + for (now = start = dnsmasq_time(), timeout_count = 0; + (difftime(now, start) < (float)PING_WAIT) && (timeout_count < PING_WAIT * 4);) { - struct timeval tv; - fd_set rset, wset; struct sockaddr_in faddr; - int maxfd = fd; socklen_t len = sizeof(faddr); - tv.tv_usec = 250000; - tv.tv_sec = 0; + poll_reset(); + poll_listen(fd, POLLIN); + set_dns_listeners(now); + set_log_writer(); - FD_ZERO(&rset); - FD_ZERO(&wset); - FD_SET(fd, &rset); - set_dns_listeners(now, &rset, &maxfd); - set_log_writer(&wset, &maxfd); - - if (select(maxfd+1, &rset, &wset, NULL, &tv) < 0) - { - FD_ZERO(&rset); - FD_ZERO(&wset); - } +#ifdef HAVE_DHCP6 + if (daemon->doing_ra) + poll_listen(daemon->icmp6fd, POLLIN); +#endif + + rc = do_poll(250); + + if (rc < 0) + continue; + else if (rc == 0) + timeout_count++; now = dnsmasq_time(); - check_log_writer(&wset); - check_dns_listeners(&rset, now); + check_log_writer(0); + check_dns_listeners(now); +#ifdef HAVE_DHCP6 + if (daemon->doing_ra && poll_check(daemon->icmp6fd, POLLIN)) + icmp6_packet(now); +#endif + #ifdef HAVE_TFTP - check_tftp_listeners(&rset, now); + check_tftp_listeners(now); #endif - if (FD_ISSET(fd, &rset) && + if (poll_check(fd, POLLIN) && recvfrom(fd, &packet, sizeof(packet), 0, (struct sockaddr *)&faddr, &len) == sizeof(packet) && saddr.sin_addr.s_addr == faddr.sin_addr.s_addr && @@ -1312,7 +1808,7 @@ int icmp_ping(struct in_addr addr) } #if defined(HAVE_LINUX_NETWORK) || defined(HAVE_SOLARIS_NETWORK) - close(fd); + while (retry_send(close(fd))); #else opt = 1; setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 66c602e..cf1a782 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -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 @@ -14,7 +14,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#define COPYRIGHT "Copyright (c) 2000-2011 Simon Kelley" +#define COPYRIGHT "Copyright (c) 2000-2015 Simon Kelley" #ifndef NO_LARGEFILE /* Ensure we can use files >2GB (log files may grow this big) */ @@ -39,19 +39,33 @@ /* get these before config.h for IPv6 stuff... */ #include <sys/types.h> #include <sys/socket.h> + +#ifdef __APPLE__ +/* Define before netinet/in.h to select API. OSX Lion onwards. */ +# define __APPLE_USE_RFC_3542 +#endif #include <netinet/in.h> -/* and this. */ +/* Also needed before config.h. */ #include <getopt.h> #include "config.h" +#include "ip6addr.h" typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; +typedef unsigned long long u64; -#include "dns_protocol.h" -#include "dhcp_protocol.h" +#define countof(x) (long)(sizeof(x) / sizeof(x[0])) +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +#include "dns-protocol.h" +#include "dhcp-protocol.h" +#ifdef HAVE_DHCP6 +#include "dhcp6-protocol.h" +#include "radv-protocol.h" +#endif #define gettext_noop(S) (S) #ifndef LOCALEDIR @@ -68,7 +82,7 @@ typedef unsigned int u32; #if defined(HAVE_SOLARIS_NETWORK) # include <sys/sockio.h> #endif -#include <sys/select.h> +#include <sys/poll.h> #include <sys/wait.h> #include <sys/time.h> #include <sys/un.h> @@ -103,6 +117,7 @@ typedef unsigned int u32; #include <sys/uio.h> #include <syslog.h> #include <dirent.h> +#include <utime.h> #ifndef HAVE_LINUX_NETWORK # include <net/if_dl.h> #endif @@ -127,7 +142,7 @@ extern int capget(cap_user_header_t header, cap_user_data_t data); /* Async event queue */ struct event_desc { - int event, data; + int event, data, msg_sz; }; #define EVENT_RELOAD 1 @@ -148,6 +163,12 @@ struct event_desc { #define EVENT_DIE 16 #define EVENT_LOG_ERR 17 #define EVENT_FORK_ERR 18 +#define EVENT_LUA_ERR 19 +#define EVENT_TFTP_ERR 20 +#define EVENT_INIT 21 +#define EVENT_NEWADDR 22 +#define EVENT_NEWROUTE 23 +#define EVENT_TIME_ERR 24 /* Exit codes. */ #define EC_GOOD 0 @@ -201,8 +222,27 @@ struct event_desc { #define OPT_NO_OVERRIDE 30 #define OPT_NO_REBIND 31 #define OPT_ADD_MAC 32 -#define OPT_DNSSEC 33 -#define OPT_LAST 34 +#define OPT_DNSSEC_PROXY 33 +#define OPT_CONSEC_ADDR 34 +#define OPT_CONNTRACK 35 +#define OPT_FQDN_UPDATE 36 +#define OPT_RA 37 +#define OPT_TFTP_LC 38 +#define OPT_CLEVERBIND 39 +#define OPT_TFTP 40 +#define OPT_CLIENT_SUBNET 41 +#define OPT_QUIET_DHCP 42 +#define OPT_QUIET_DHCP6 43 +#define OPT_QUIET_RA 44 +#define OPT_DNSSEC_VALID 45 +#define OPT_DNSSEC_TIME 46 +#define OPT_DNSSEC_DEBUG 47 +#define OPT_DNSSEC_NO_SIGN 48 +#define OPT_LOCAL_SERVICE 49 +#define OPT_LOOP_DETECT 50 +#define OPT_EXTRALOG 51 +#define OPT_TFTP_NO_FAIL 52 +#define OPT_LAST 53 /* extra flags for my_syslog, we use a couple of facilities since they are known not to occupy the same bits as priorities, no matter how syslog.h is set up. */ @@ -215,6 +255,12 @@ struct all_addr { #ifdef HAVE_IPV6 struct in6_addr addr6; #endif + /* for log_query */ + unsigned int keytag; + /* for cache_insert if RRSIG, DNSKEY, DS */ + struct { + unsigned short class, type; + } dnssec; } addr; }; @@ -242,10 +288,19 @@ struct naptr { struct naptr *next; }; +#define TXT_STAT_CACHESIZE 1 +#define TXT_STAT_INSERTS 2 +#define TXT_STAT_EVICTIONS 3 +#define TXT_STAT_MISSES 4 +#define TXT_STAT_HITS 5 +#define TXT_STAT_AUTH 6 +#define TXT_STAT_SERVERS 7 + struct txt_record { char *name; unsigned char *txt; unsigned short class, len; + int stat; struct txt_record *next; }; @@ -257,11 +312,56 @@ struct ptr_record { struct cname { char *alias, *target; struct cname *next; +}; + +struct ds_config { + char *name, *digest; + int digestlen, class, algo, keytag, digest_type; + struct ds_config *next; +}; + +#define ADDRLIST_LITERAL 1 +#define ADDRLIST_IPV6 2 +#define ADDRLIST_REVONLY 4 + +struct addrlist { + struct all_addr addr; + int flags, prefixlen; + struct addrlist *next; +}; + +#define AUTH6 1 +#define AUTH4 2 + +struct auth_zone { + char *domain; + struct auth_name_list { + char *name; + int flags; + struct auth_name_list *next; + } *interface_names; + struct addrlist *subnet; + struct auth_zone *next; +}; + + +struct host_record { + struct name_list { + char *name; + struct name_list *next; + } *names; + struct in_addr addr; +#ifdef HAVE_IPV6 + struct in6_addr addr6; +#endif + struct host_record *next; }; struct interface_name { char *name; /* domain name */ char *intr; /* interface name */ + int family; /* AF_INET, AF_INET6 or zero for both */ + struct addrlist *addr; struct interface_name *next; }; @@ -270,17 +370,43 @@ union bigname { union bigname *next; /* freelist */ }; +struct blockdata { + struct blockdata *next; + unsigned char key[KEYBLOCK_LEN]; +}; + struct crec { struct crec *next, *prev, *hash_next; - time_t ttd; /* time to die */ - int uid; + /* union is 16 bytes when doing IPv6, 8 bytes on 32 bit machines without IPv6 */ union { struct all_addr addr; struct { - struct crec *cache; - int uid; + union { + struct crec *cache; + struct interface_name *int_name; + } target; + unsigned int uid; /* 0 if union is interface-name */ } cname; + struct { + struct blockdata *keydata; + unsigned short keylen, flags, keytag; + unsigned char algo; + } key; + struct { + struct blockdata *keydata; + unsigned short keylen, keytag; + unsigned char algo; + unsigned char digest; + } ds; + struct { + struct blockdata *keydata; + unsigned short keylen, type_covered, keytag; + char algo; + } sig; } addr; + time_t ttd; /* time to die */ + /* used as class if DNSKEY/DS/RRSIG, index to source for F_HOSTS */ + unsigned int uid; unsigned short flags; union { char sname[SMALLDNAME]; @@ -301,15 +427,32 @@ struct crec { #define F_BIGNAME (1u<<9) #define F_NXDOMAIN (1u<<10) #define F_CNAME (1u<<11) -#define F_NOERR (1u<<12) +#define F_DNSKEY (1u<<12) #define F_CONFIG (1u<<13) +#define F_DS (1u<<14) +#define F_DNSSECOK (1u<<15) + /* below here are only valid as args to log_query: cache entries are limited to 16 bits */ #define F_UPSTREAM (1u<<16) #define F_RRNAME (1u<<17) #define F_SERVER (1u<<18) #define F_QUERY (1u<<19) -#define F_NSRR (1u<<20) +#define F_NOERR (1u<<20) +#define F_AUTH (1u<<21) +#define F_DNSSEC (1u<<22) +#define F_KEYTAG (1u<<23) +#define F_SECSTAT (1u<<24) +#define F_NO_RR (1u<<25) +#define F_IPSET (1u<<26) +#define F_NSIGMATCH (1u<<27) +#define F_NOEXTRA (1u<<28) + +/* Values of uid in crecs with F_CONFIG bit set. */ +#define SRC_INTERFACE 0 +#define SRC_CONFIG 1 +#define SRC_HOSTS 2 +#define SRC_AH 3 /* struct sockaddr is not large enough to hold any address, @@ -323,6 +466,12 @@ union mysockaddr { #endif }; +/* bits in flag param to IPv6 callbacks from iface_enumerate() */ +#define IFACE_TENTATIVE 1 +#define IFACE_DEPRECATED 2 +#define IFACE_PERMANENT 4 + + #define SERV_FROM_RESOLV 1 /* 1 for servers from resolv, 0 for command line. */ #define SERV_NO_ADDR 2 /* no server, this domain is local only */ #define SERV_LITERAL_ADDRESS 4 /* addr is the answer, not the server */ @@ -336,6 +485,8 @@ union mysockaddr { #define SERV_COUNTED 512 /* workspace for log code */ #define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */ #define SERV_NO_REBIND 2048 /* inhibit dns-rebind protection */ +#define SERV_FROM_FILE 4096 /* read from --servers-file */ +#define SERV_LOOP 8192 /* server causes forwarding loop */ struct serverfd { int fd; @@ -354,22 +505,31 @@ struct server { char interface[IF_NAMESIZE+1]; struct serverfd *sfd; char *domain; /* set if this server only handles a domain. */ - int flags, tcpfd; + int flags, tcpfd, edns_pktsz; unsigned int queries, failed_queries; +#ifdef HAVE_LOOP + u32 uid; +#endif struct server *next; }; +struct ipsets { + char **sets; + char *domain; + struct ipsets *next; +}; + struct irec { union mysockaddr addr; struct in_addr netmask; /* only valid for IPv4 */ - int tftp_ok, mtu; - char *name; + int tftp_ok, dhcp_ok, mtu, done, warned, dad, dns_auth, index, multicast_done, found; + char *name; struct irec *next; }; struct listener { int fd, tcpfd, tftpfd, family; - struct irec *iface; /* only valid for non-wildcard */ + struct irec *iface; /* only sometimes valid for non-wildcard */ struct listener *next; }; @@ -377,7 +537,7 @@ struct listener { struct iname { char *name; union mysockaddr addr; - int isloop, used; + int used; struct iname *next; }; @@ -387,20 +547,61 @@ struct resolvc { int is_default, logged; time_t mtime; char *name; +#ifdef HAVE_INOTIFY + int wd; /* inotify watch descriptor */ + char *file; /* pointer to file part if path */ +#endif }; -/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile */ +/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/ #define AH_DIR 1 #define AH_INACTIVE 2 +#define AH_WD_DONE 4 +#define AH_HOSTS 8 +#define AH_DHCP_HST 16 +#define AH_DHCP_OPT 32 struct hostsfile { struct hostsfile *next; int flags; char *fname; - int index; /* matches to cache entries for logging */ +#ifdef HAVE_INOTIFY + int wd; /* inotify watch descriptor */ +#endif + unsigned int index; /* matches to cache entries for logging */ }; + +/* DNSSEC status values. */ +#define STAT_SECURE 1 +#define STAT_INSECURE 2 +#define STAT_BOGUS 3 +#define STAT_NEED_DS 4 +#define STAT_NEED_KEY 5 +#define STAT_TRUNCATED 6 +#define STAT_SECURE_WILDCARD 7 +#define STAT_NO_SIG 8 +#define STAT_NO_DS 9 +#define STAT_NO_NS 10 +#define STAT_NEED_DS_NEG 11 +#define STAT_CHASE_CNAME 12 +#define STAT_INSECURE_DS 13 + #define FREC_NOREBIND 1 #define FREC_CHECKING_DISABLED 2 +#define FREC_HAS_SUBNET 4 +#define FREC_DNSKEY_QUERY 8 +#define FREC_DS_QUERY 16 +#define FREC_AD_QUESTION 32 +#define FREC_DO_QUESTION 64 +#define FREC_ADDED_PHEADER 128 +#define FREC_CHECK_NOSIGN 256 +#define FREC_TEST_PKTSZ 512 + +#ifdef HAVE_DNSSEC +#define HASH_SIZE 20 /* SHA-1 digest size */ +#else +#define HASH_SIZE sizeof(int) +#endif struct frec { union mysockaddr source; @@ -412,28 +613,52 @@ struct frec { #endif unsigned int iface; unsigned short orig_id, new_id; - int fd, forwardall, flags; - unsigned int crc; + int log_id, fd, forwardall, flags; time_t time; + unsigned char *hash[HASH_SIZE]; +#ifdef HAVE_DNSSEC + int class, work_counter; + struct blockdata *stash; /* Saved reply, whilst we validate */ + struct blockdata *orig_domain; /* domain of original query, whilst + we're seeing is if in unsigned domain */ + size_t stash_len, name_start, name_len; + struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ + struct frec *blocking_query; /* Query which is blocking us. */ +#endif struct frec *next; }; +/* flags in top of length field for DHCP-option tables */ +#define OT_ADDR_LIST 0x8000 +#define OT_RFC1035_NAME 0x4000 +#define OT_INTERNAL 0x2000 +#define OT_NAME 0x1000 +#define OT_CSTRING 0x0800 +#define OT_DEC 0x0400 +#define OT_TIME 0x0200 + /* actions in the daemon->helper RPC */ #define ACTION_DEL 1 #define ACTION_OLD_HOSTNAME 2 #define ACTION_OLD 3 #define ACTION_ADD 4 -#define ACTION_CONNECT 5 +#define ACTION_TFTP 5 + +#define LEASE_NEW 1 /* newly created */ +#define LEASE_CHANGED 2 /* modified */ +#define LEASE_AUX_CHANGED 4 /* CLID or expiry changed */ +#define LEASE_AUTH_NAME 8 /* hostname came from config, not from client */ +#define LEASE_USED 16 /* used this DHCPv6 transaction */ +#define LEASE_NA 32 /* IPv6 no-temporary lease */ +#define LEASE_TA 64 /* IPv6 temporary lease */ +#define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */ struct dhcp_lease { int clid_len; /* length of client identifier */ unsigned char *clid; /* clientid */ char *hostname, *fqdn; /* name from client-hostname option or config */ char *old_hostname; /* hostname before it moved to another lease */ - char auth_name; /* hostname came from config, not from client */ - char new; /* newly created */ - char changed; /* modified */ - char aux_changed; /* CLID or expiry changed */ + int flags; time_t expires; /* lease expiry */ #ifdef HAVE_BROKEN_RTC unsigned int length; @@ -444,6 +669,19 @@ struct dhcp_lease { unsigned char *extradata; unsigned int extradata_len, extradata_size; int last_interface; + int new_interface; /* save possible originated interface */ + int new_prefixlen; /* and its prefix length */ +#ifdef HAVE_DHCP6 + struct in6_addr addr6; + int iaid; + struct slaac_address { + struct in6_addr addr; + time_t ping_time; + int backoff; /* zero -> confirmed */ + struct slaac_address *next; + } *slaac_address; + int vendorclass_count; +#endif struct dhcp_lease *next; }; @@ -476,6 +714,9 @@ struct dhcp_config { unsigned char *clid; /* clientid */ char *hostname, *domain; struct dhcp_netid_list *netid; +#ifdef HAVE_DHCP6 + struct in6_addr addr6; +#endif struct in_addr addr; time_t decline_time; unsigned int lease_time; @@ -483,6 +724,8 @@ struct dhcp_config { struct dhcp_config *next; }; +#define have_config(config, mask) ((config) && ((config)->flags & (mask))) + #define CONFIG_DISABLE 1 #define CONFIG_CLID 2 #define CONFIG_TIME 8 @@ -493,6 +736,8 @@ struct dhcp_config { #define CONFIG_ADDR_HOSTS 512 /* address added by from /etc/hosts */ #define CONFIG_DECLINED 1024 /* address declined by client */ #define CONFIG_BANK 2048 /* from dhcp hosts file */ +#define CONFIG_ADDR6 4096 +#define CONFIG_WILDCARD 8192 struct dhcp_opt { int opt, len, flags; @@ -518,9 +763,11 @@ struct dhcp_opt { #define DHOPT_HEX 512 #define DHOPT_VENDOR_MATCH 1024 #define DHOPT_RFC3925 2048 +#define DHOPT_TAGOK 4096 +#define DHOPT_ADDR6 8192 struct dhcp_boot { - char *file, *sname; + char *file, *sname, *tftp_sname; struct in_addr next_server; struct dhcp_netid *netid; struct dhcp_boot *next; @@ -528,7 +775,7 @@ struct dhcp_boot { struct pxe_service { unsigned short CSA, type; - char *menu, *basename; + char *menu, *basename, *sname; struct in_addr server; struct dhcp_netid *netid; struct pxe_service *next; @@ -542,7 +789,8 @@ struct pxe_service { /* vendorclass, userclass, remote-id or cicuit-id */ struct dhcp_vendor { - int len, match_type, option; + int len, match_type; + unsigned int enterprise; char *data; struct dhcp_netid netid; struct dhcp_vendor *next; @@ -562,9 +810,27 @@ struct dhcp_bridge { }; struct cond_domain { - char *domain; + char *domain, *prefix; struct in_addr start, end; +#ifdef HAVE_IPV6 + struct in6_addr start6, end6; +#endif + int is6; struct cond_domain *next; +}; + +#ifdef OPTION6_PREFIX_CLASS +struct prefix_class { + int class; + struct dhcp_netid tag; + struct prefix_class *next; +}; +#endif + +struct ra_interface { + char *name; + int interval, lifetime, prio; + struct ra_interface *next; }; struct dhcp_context { @@ -572,20 +838,43 @@ struct dhcp_context { struct in_addr netmask, broadcast; struct in_addr local, router; struct in_addr start, end; /* range of available addresses */ +#ifdef HAVE_DHCP6 + struct in6_addr start6, end6; /* range of available addresses */ + struct in6_addr local6; + int prefix, if_index; + unsigned int valid, preferred, saved_valid; + time_t ra_time, ra_short_period_start, address_lost_time; + char *template_interface; +#endif int flags; - char *interface; struct dhcp_netid netid, *filter; struct dhcp_context *next, *current; }; -#define CONTEXT_STATIC 1 -#define CONTEXT_NETMASK 2 -#define CONTEXT_BRDCAST 4 -#define CONTEXT_PROXY 8 +#define CONTEXT_STATIC (1u<<0) +#define CONTEXT_NETMASK (1u<<1) +#define CONTEXT_BRDCAST (1u<<2) +#define CONTEXT_PROXY (1u<<3) +#define CONTEXT_RA_ROUTER (1u<<4) +#define CONTEXT_RA_DONE (1u<<5) +#define CONTEXT_RA_NAME (1u<<6) +#define CONTEXT_RA_STATELESS (1u<<7) +#define CONTEXT_DHCP (1u<<8) +#define CONTEXT_DEPRECATE (1u<<9) +#define CONTEXT_TEMPLATE (1u<<10) /* create contexts using addresses */ +#define CONTEXT_CONSTRUCTED (1u<<11) +#define CONTEXT_GC (1u<<12) +#define CONTEXT_RA (1u<<13) +#define CONTEXT_CONF_USED (1u<<14) +#define CONTEXT_USED (1u<<15) +#define CONTEXT_OLD (1u<<16) +#define CONTEXT_V6 (1u<<17) +#define CONTEXT_RA_OFF_LINK (1u<<18) struct ping_result { struct in_addr addr; time_t time; + unsigned int hash; struct ping_result *next; }; @@ -614,17 +903,19 @@ struct addr_list { struct addr_list *next; }; -struct interface_list { - char *interface; - struct interface_list *next; -}; - struct tftp_prefix { char *interface; char *prefix; + int missing; struct tftp_prefix *next; }; +struct dhcp_relay { + struct all_addr local, server; + char *interface; /* Allowable interface for replies from server, and dest for IPv6 multicast */ + int iface_index; /* working - interface in which requests arrived, for return */ + struct dhcp_relay *current, *next; +}; extern struct daemon { /* datastuctures representing the command-line and @@ -634,44 +925,57 @@ extern struct daemon { unsigned int options, options2; struct resolvc default_resolv, *resolv_files; time_t last_resolv; + char *servers_file; struct mx_srv_record *mxnames; struct naptr *naptr; - struct txt_record *txt; + struct txt_record *txt, *rr; struct ptr_record *ptr; + struct host_record *host_records, *host_records_tail; struct cname *cnames; + struct auth_zone *auth_zones; struct interface_name *int_names; char *mxtarget; + int addr4_netmask; + int addr6_netmask; char *lease_file; char *username, *groupname, *scriptuser; + char *luascript; + char *authserver, *hostmaster; + struct iname *authinterface; + struct name_list *secondary_forward_server; int group_set, osport; char *domain_suffix; - struct cond_domain *cond_domain; + struct cond_domain *cond_domain, *synth_domains; char *runfile; char *lease_change_command; - struct iname *if_names, *if_addrs, *if_except, *dhcp_except; - struct bogus_addr *bogus_addr; + struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces; + struct bogus_addr *bogus_addr, *ignore_addr; struct server *servers; + struct ipsets *ipsets; int log_fac; /* log facility */ char *log_file; /* optional log file */ int max_logs; /* queue limit */ int cachesize, ftabsize; int port, query_port, min_port; - unsigned long local_ttl, neg_ttl, max_ttl; + unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl; struct hostsfile *addn_hosts; - struct dhcp_context *dhcp; + struct dhcp_context *dhcp, *dhcp6; + struct ra_interface *ra_interfaces; struct dhcp_config *dhcp_conf; - struct dhcp_opt *dhcp_opts, *dhcp_match; + struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6; struct dhcp_vendor *dhcp_vendors; struct dhcp_mac *dhcp_macs; struct dhcp_boot *boot_config; struct pxe_service *pxe_services; struct tag_if *tag_if; struct addr_list *override_relays; + struct dhcp_relay *relay4, *relay6; int override; int enable_pxe; + int doing_ra, doing_dhcp6; struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names; struct dhcp_netid_list *force_broadcast, *bootp_dynamic; - struct hostsfile *dhcp_hosts_file, *dhcp_opts_file; + struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *dynamic_dirs; int dhcp_max, tftp_max; int dhcp_server_port, dhcp_client_port; int start_tftp_port, end_tftp_port; @@ -680,14 +984,28 @@ extern struct daemon { unsigned short edns_pktsz; char *tftp_prefix; struct tftp_prefix *if_prefix; /* per-interface TFTP prefixes */ - struct interface_list *tftp_interfaces; /* interfaces for limited TFTP service */ - int tftp_unlimited; + unsigned int duid_enterprise, duid_config_len; + unsigned char *duid_config; + char *dbus_name; + unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry; +#ifdef OPTION6_PREFIX_CLASS + struct prefix_class *prefix_classes; +#endif +#ifdef HAVE_DNSSEC + struct ds_config *ds; + int back_to_the_future; + char *timestamp_file; +#endif /* globally used stuff for DNS */ char *packet; /* packet buffer */ int packet_buff_sz; /* size of above */ char *namebuff; /* MAXDNAME size buffer */ - unsigned int local_answer, queries_forwarded; +#ifdef HAVE_DNSSEC + char *keyname; /* MAXDNAME size buffer */ + char *workspacename; /* ditto */ +#endif + unsigned int local_answer, queries_forwarded, auth_answer; struct frec *frec_list; struct serverfd *sfds; struct irec *interfaces; @@ -701,20 +1019,31 @@ extern struct daemon { pid_t tcp_pids[MAX_PROCS]; struct randfd randomsocks[RANDOM_SOCKS]; int v6pktinfo; + struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */ + int log_id, log_display_id; /* ids of transactions for logging */ + union mysockaddr *log_source_addr; /* DHCP state */ int dhcpfd, helperfd, pxefd; +#ifdef HAVE_INOTIFY + int inotifyfd; +#endif #if defined(HAVE_LINUX_NETWORK) int netlinkfd; #elif defined(HAVE_BSD_NETWORK) - int dhcp_raw_fd, dhcp_icmp_fd; + int dhcp_raw_fd, dhcp_icmp_fd, routefd; #endif struct iovec dhcp_packet; char *dhcp_buff, *dhcp_buff2, *dhcp_buff3; struct ping_result *ping_results; FILE *lease_stream; struct dhcp_bridge *bridges; - +#ifdef HAVE_DHCP6 + int duid_len; + unsigned char *duid; + struct iovec outpacket; + int dhcp6fd, icmp6fd; +#endif /* DBus stuff */ /* void * here to avoid depending on dbus headers outside dbus.c */ void *dbus; @@ -723,43 +1052,77 @@ extern struct daemon { #endif /* TFTP stuff */ - struct tftp_transfer *tftp_trans; + struct tftp_transfer *tftp_trans, *tftp_done_trans; + + /* utility string buffer, hold max sized IP address as string */ + char *addrbuff; + char *addrbuff2; /* only allocated when OPT_EXTRALOG */ } *daemon; /* cache.c */ void cache_init(void); void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg); -char *record_source(int index); -void querystr(char *str, unsigned short type); +char *record_source(unsigned int index); +char *querystr(char *desc, unsigned short type); struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, time_t now, - unsigned short prot); + unsigned int prot); struct crec *cache_find_by_name(struct crec *crecp, - char *name, time_t now, unsigned short prot); + char *name, time_t now, unsigned int prot); void cache_end_insert(void); void cache_start_insert(void); struct crec *cache_insert(char *name, struct all_addr *addr, time_t now, unsigned long ttl, unsigned short flags); void cache_reload(void); -void cache_add_dhcp_entry(char *host_name, struct in_addr *host_address, time_t ttd); +void cache_add_dhcp_entry(char *host_name, int prot, struct all_addr *host_address, time_t ttd); +struct in_addr a_record_from_hosts(char *name, time_t now); void cache_unhash_dhcp(void); void dump_cache(time_t now); +int cache_make_stat(struct txt_record *t); char *cache_get_name(struct crec *crecp); +char *cache_get_cname_target(struct crec *crecp); +struct crec *cache_enumerate(int init); +int read_hostsfile(char *filename, unsigned int index, int cache_size, + struct crec **rhash, int hashsz); + +/* blockdata.c */ +#ifdef HAVE_DNSSEC +void blockdata_init(void); +void blockdata_report(void); +struct blockdata *blockdata_alloc(char *data, size_t len); +void *blockdata_retrieve(struct blockdata *block, size_t len, void *data); +void blockdata_free(struct blockdata *blocks); +#endif + +/* domain.c */ char *get_domain(struct in_addr addr); +#ifdef HAVE_IPV6 +char *get_domain6(struct in6_addr *addr); +#endif +int is_name_synthetic(int flags, char *name, struct all_addr *addr); +int is_rev_synth(int flag, struct all_addr *addr, char *name); /* rfc1035.c */ +int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, + char *name, int isExtract, int extrabytes); +unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes); +unsigned char *skip_questions(struct dns_header *header, size_t plen); +unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen); unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep); size_t setup_reply(struct dns_header *header, size_t qlen, struct all_addr *addrp, unsigned int flags, unsigned long local_ttl); int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff, - time_t now, int is_sign, int checkrebind, int checking_disabled); + time_t now, char **ipsets, int is_sign, int checkrebind, + int no_cache, int secure, int *doctored); size_t answer_request(struct dns_header *header, char *limit, size_t qlen, - struct in_addr local_addr, struct in_addr local_netmask, time_t now); + struct in_addr local_addr, struct in_addr local_netmask, + time_t now, int *ad_reqd, int *do_bit); int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, struct bogus_addr *addr, time_t now); +int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr); unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t *len, unsigned char **p, int *is_sign); int check_for_local_domain(char *name, time_t now); @@ -767,10 +1130,43 @@ unsigned int questions_crc(struct dns_header *header, size_t plen, char *buff); size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen); size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3); +size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source); +#ifdef HAVE_DNSSEC +size_t add_do_bit(struct dns_header *header, size_t plen, char *limit); +#endif +int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer); +int add_resource_record(struct dns_header *header, char *limit, int *truncp, + int nameoffset, unsigned char **pp, unsigned long ttl, + int *offset, unsigned short type, unsigned short class, char *format, ...); +unsigned char *skip_questions(struct dns_header *header, size_t plen); +int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, + char *name, int isExtract, int extrabytes); +int in_arpa_name_2_addr(char *namein, struct all_addr *addrp); +int private_net(struct in_addr addr, int ban_localhost); + +/* auth.c */ +#ifdef HAVE_AUTH +size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, + time_t now, union mysockaddr *peer_addr, int local_query); +int in_zone(struct auth_zone *zone, char *name, char **cut); +#endif + +/* dnssec.c */ +size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz); +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); +int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); +int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer, int *nons); +int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname); +int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen); +size_t filter_rrsigs(struct dns_header *header, size_t plen); +unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); +int setup_timestamp(void); /* util.c */ void rand_init(void); unsigned short rand16(void); +u32 rand32(void); +u64 rand64(void); int legal_hostname(char *c); char *canonicalise(char *s, int *nomem); unsigned char *do_rfc1035_name(unsigned char *p, char *sval); @@ -779,10 +1175,16 @@ void safe_pipe(int *fd, int read_noblock); void *whine_malloc(size_t size); int sa_len(union mysockaddr *addr); int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); -int hostname_isequal(char *a, char *b); +int hostname_isequal(const char *a, const char *b); time_t dnsmasq_time(void); +int netmask_length(struct in_addr mask); int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask); -int retry_send(void); +#ifdef HAVE_IPV6 +int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen); +u64 addr6part(struct in6_addr *addr); +void setaddr6part(struct in6_addr *addr, u64 host); +#endif +int retry_send(ssize_t rc); void prettyprint_time(char *buf, unsigned int t); int prettyprint_addr(union mysockaddr *addr, char *buf); int parse_hex(char *in, unsigned char *out, int maxlen, @@ -791,32 +1193,46 @@ int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask); int expand_buf(struct iovec *iov, size_t size); char *print_mac(char *buff, unsigned char *mac, int len); -void bump_maxfd(int fd, int *max); int read_write(int fd, unsigned char *packet, int size, int rw); +int wildcard_match(const char* wildcard, const char* match); +int wildcard_matchn(const char* wildcard, const char* match, int num); + /* log.c */ void die(char *message, char *arg1, int exit_code); int log_start(struct passwd *ent_pw, int errfd); int log_reopen(char *log_file); void my_syslog(int priority, const char *format, ...); -void set_log_writer(fd_set *set, int *maxfdp); -void check_log_writer(fd_set *set); +void set_log_writer(void); +void check_log_writer(int force); void flush_log(void); /* option.c */ void read_opts (int argc, char **argv, char *compile_opts); -char *option_string(unsigned char opt, int *is_ip, int *is_name); +char *option_string(int prot, unsigned int opt, unsigned char *val, + int opt_len, char *buf, int buf_len); void reread_dhcp(void); +void read_servers_file(void); void set_option_bool(unsigned int opt); +void reset_option_bool(unsigned int opt); struct hostsfile *expand_filelist(struct hostsfile *list); +char *parse_server(char *arg, union mysockaddr *addr, + union mysockaddr *source_addr, char *interface, int *flags); +int option_read_dynfile(char *file, int flags); /* forward.c */ void reply_query(int fd, int family, time_t now); void receive_query(struct listener *listen, time_t now); unsigned char *tcp_request(int confd, time_t now, - struct in_addr local_addr, struct in_addr netmask); + union mysockaddr *local_addr, struct in_addr netmask, int auth_dns); void server_gone(struct server *server); -struct frec *get_new_frec(time_t now, int *wait); +struct frec *get_new_frec(time_t now, int *wait, int force); +int send_from(int fd, int nowild, char *packet, size_t len, + union mysockaddr *to, struct all_addr *source, + unsigned int iface); +void resend_query(); +struct randfd *allocate_rfd(int family); +void free_rfd(struct randfd *rfd); /* network.c */ int indextoname(int fd, int index, char *name); @@ -824,13 +1240,35 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, int is_tcp); int random_sock(int family); void pre_allocate_sfds(void); int reload_servers(char *fname); +void mark_servers(int flag); +void cleanup_servers(void); +void add_update_server(int flags, + union mysockaddr *addr, + union mysockaddr *source_addr, + const char *interface, + const char *domain); void check_servers(void); -int enumerate_interfaces(); -struct listener *create_wildcard_listeners(void); -struct listener *create_bound_listeners(void); -int iface_check(int family, struct all_addr *addr, char *name, int *indexp); +int enumerate_interfaces(int reset); +void create_wildcard_listeners(void); +void create_bound_listeners(int die); +void warn_bound_listeners(void); +void warn_int_names(void); +int is_dad_listeners(void); +int iface_check(int family, struct all_addr *addr, char *name, int *auth_dns); +int loopback_exception(int fd, int family, struct all_addr *addr, char *name); +int label_exception(int index, int family, struct all_addr *addr); int fix_fd(int fd); -struct in_addr get_ifaddr(char *intr); +int tcp_interface(int fd, int af); +#ifdef HAVE_IPV6 +int set_ipv6pktinfo(int fd); +#endif +#ifdef HAVE_DHCP6 +void join_multicast(int dienow); +#endif +#if defined(HAVE_LINUX_NETWORK) || defined(HAVE_BSD_NETWORK) +void newaddress(time_t now); +#endif + /* dhcp.c */ #ifdef HAVE_DHCP @@ -842,50 +1280,58 @@ struct dhcp_context *address_available(struct dhcp_context *context, struct dhcp_context *narrow_context(struct dhcp_context *context, struct in_addr taddr, struct dhcp_netid *netids); -int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int negonly); int address_allocate(struct dhcp_context *context, struct in_addr *addrp, unsigned char *hwaddr, int hw_len, struct dhcp_netid *netids, time_t now); -struct dhcp_netid *run_tag_if(struct dhcp_netid *input); -int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type); -struct dhcp_config *find_config(struct dhcp_config *configs, - struct dhcp_context *context, - unsigned char *clid, int clid_len, - unsigned char *hwaddr, int hw_len, - int hw_type, char *hostname); -void dhcp_update_configs(struct dhcp_config *configs); void dhcp_read_ethers(void); -void check_dhcp_hosts(int fatal); struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr); -char *strip_hostname(char *hostname); char *host_from_dns(struct in_addr addr); -char *get_domain(struct in_addr addr); #endif /* lease.c */ #ifdef HAVE_DHCP void lease_update_file(time_t now); -void lease_update_dns(); +void lease_update_dns(int force); void lease_init(time_t now); -struct dhcp_lease *lease_allocate(struct in_addr addr); -void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr, - unsigned char *clid, int hw_len, int hw_type, int clid_len); -void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth); +struct dhcp_lease *lease4_allocate(struct in_addr addr); +#ifdef HAVE_DHCP6 +struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type); +struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, + int lease_type, int iaid, struct in6_addr *addr); +void lease6_reset(void); +struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type, unsigned char *clid, int clid_len, int iaid); +struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr); +u64 lease_find_max_addr6(struct dhcp_context *context); +void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface); +void lease_update_slaac(time_t now); +void lease_set_iaid(struct dhcp_lease *lease, int iaid); +void lease_make_duid(time_t now); +#endif +void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr, + const unsigned char *clid, int hw_len, int hw_type, + int clid_len, time_t now, int force); +void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, char *domain, char *config_domain); void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now); -void lease_set_interface(struct dhcp_lease *lease, int interface); +void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now); struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type, unsigned char *clid, int clid_len); struct dhcp_lease *lease_find_by_addr(struct in_addr addr); +struct in_addr lease_find_max_addr(struct dhcp_context *context); void lease_prune(struct dhcp_lease *target, time_t now); void lease_update_from_configs(void); int do_script_run(time_t now); void rerun_scripts(void); +void lease_find_interfaces(time_t now); +#ifdef HAVE_SCRIPT +void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, + unsigned int len, int delim); +#endif #endif /* rfc2131.c */ #ifdef HAVE_DHCP 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_fd); + size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe_fd, struct in_addr fallback); unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr, int clid_len, unsigned char *clid, int *len_out); #endif @@ -895,9 +1341,10 @@ unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr, int make_icmp_sock(void); int icmp_ping(struct in_addr addr); #endif -void send_event(int fd, int event, int data); +void queue_event(int event); +void send_alarm(time_t event, time_t now); +void send_event(int fd, int event, int data, char *msg); void clear_cache_and_reload(time_t now); -void poll_resolv(int force, int do_reload, time_t now); /* netlink.c */ #ifdef HAVE_LINUX_NETWORK @@ -910,6 +1357,8 @@ void netlink_multicast(void); void init_bpf(void); void send_via_bpf(struct dhcp_packet *mess, size_t len, struct in_addr iface_addr, struct ifreq *ifr); +void route_init(void); +void route_sock(void); #endif /* bpf.c or netlink.c */ @@ -918,24 +1367,155 @@ int iface_enumerate(int family, void *parm, int (callback)()); /* dbus.c */ #ifdef HAVE_DBUS char *dbus_init(void); -void check_dbus_listeners(fd_set *rset, fd_set *wset, fd_set *eset); -void set_dbus_listeners(int *maxfdp, fd_set *rset, fd_set *wset, fd_set *eset); +void check_dbus_listeners(void); +void set_dbus_listeners(void); # ifdef HAVE_DHCP void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname); # endif #endif +/* ipset.c */ +#ifdef HAVE_IPSET +void ipset_init(void); +int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, int remove); +#endif + /* helper.c */ -#if defined(HAVE_DHCP) && !defined(NO_FORK) +#if defined(HAVE_SCRIPT) int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd); void helper_write(void); void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now); +#ifdef HAVE_TFTP +void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer); +#endif int helper_buf_empty(void); #endif /* tftp.c */ #ifdef HAVE_TFTP void tftp_request(struct listener *listen, time_t now); -void check_tftp_listeners(fd_set *rset, time_t now); +void check_tftp_listeners(time_t now); +int do_tftp_script_run(void); #endif + +/* conntrack.c */ +#ifdef HAVE_CONNTRACK +int get_incoming_mark(union mysockaddr *peer_addr, struct all_addr *local_addr, + int istcp, unsigned int *markp); +#endif + +/* dhcp6.c */ +#ifdef HAVE_DHCP6 +void dhcp6_init(void); +void dhcp6_packet(time_t now); +struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, int temp_addr, + int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans); +int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr); +struct dhcp_context *address6_available(struct dhcp_context *context, + struct in6_addr *taddr, + struct dhcp_netid *netids, + int plain_range); +struct dhcp_context *address6_valid(struct dhcp_context *context, + struct in6_addr *taddr, + struct dhcp_netid *netids, + int plain_range); +struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, + int prefix, u64 addr); +void make_duid(time_t now); +void dhcp_construct_contexts(time_t now); +void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, + unsigned int *maclenp, unsigned int *mactypep); +#endif + +/* rfc3315.c */ +#ifdef HAVE_DHCP6 +unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name, + struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr, + size_t sz, struct in6_addr *client_addr, time_t now); +void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id); + +unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface); +#endif + +/* dhcp-common.c */ +#ifdef HAVE_DHCP +void dhcp_common_init(void); +ssize_t recv_dhcp_packet(int fd, struct msghdr *msg); +struct dhcp_netid *run_tag_if(struct dhcp_netid *input); +struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags, + struct dhcp_opt *opts); +int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int negonly); +char *strip_hostname(char *hostname); +void log_tags(struct dhcp_netid *netid, u32 xid); +int match_bytes(struct dhcp_opt *o, unsigned char *p, int len); +void dhcp_update_configs(struct dhcp_config *configs); +void display_opts(void); +int lookup_dhcp_opt(int prot, char *name); +int lookup_dhcp_len(int prot, int val); +char *option_string(int prot, unsigned int opt, unsigned char *val, + int opt_len, char *buf, int buf_len); +struct dhcp_config *find_config(struct dhcp_config *configs, + struct dhcp_context *context, + unsigned char *clid, int clid_len, + unsigned char *hwaddr, int hw_len, + int hw_type, char *hostname); +int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type); +#ifdef HAVE_LINUX_NETWORK +char *whichdevice(void); +void bindtodevice(char *device, int fd); +#endif +# ifdef HAVE_DHCP6 +void display_opts6(void); +# endif +void log_context(int family, struct dhcp_context *context); +void log_relay(int family, struct dhcp_relay *relay); +#endif + +/* outpacket.c */ +#ifdef HAVE_DHCP6 +void end_opt6(int container); +int save_counter(int newval); +void *expand(size_t headroom); +int new_opt6(int opt); +void *put_opt6(void *data, size_t len); +void put_opt6_long(unsigned int val); +void put_opt6_short(unsigned int val); +void put_opt6_char(unsigned int val); +void put_opt6_string(char *s); +#endif + +/* radv.c */ +#ifdef HAVE_DHCP6 +void ra_init(time_t now); +void icmp6_packet(time_t now); +time_t periodic_ra(time_t now); +void ra_start_unsolicted(time_t now, struct dhcp_context *context); +#endif + +/* slaac.c */ +#ifdef HAVE_DHCP6 +void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force); +time_t periodic_slaac(time_t now, struct dhcp_lease *leases); +void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases); +#endif + +/* loop.c */ +#ifdef HAVE_LOOP +void loop_send_probes(); +int detect_loop(char *query, int type); +#endif + +/* inotify.c */ +#ifdef HAVE_INOTIFY +void inotify_dnsmasq_init(); +int inotify_check(time_t now); +void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz); +#endif + +/* poll.c */ +void poll_reset(void); +int poll_check(int fd, short event); +void poll_listen(int fd, short event); +int do_poll(int timeout); + diff --git a/src/dnssec.c b/src/dnssec.c new file mode 100644 index 0000000..4deda24 --- /dev/null +++ b/src/dnssec.c @@ -0,0 +1,2544 @@ +/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com> + and Copyright (c) 2012-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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_DNSSEC + +#include <nettle/rsa.h> +#include <nettle/dsa.h> +#ifndef NO_NETTLE_ECC +# include <nettle/ecdsa.h> +# include <nettle/ecc-curve.h> +#endif +#include <nettle/nettle-meta.h> +#include <nettle/bignum.h> + +/* Nettle-3.0 moved to a new API for DSA. We use a name that's defined in the new API + to detect Nettle-3, and invoke the backwards compatibility mode. */ +#ifdef dsa_params_init +#include <nettle/dsa-compat.h> +#endif + +#define SERIAL_UNDEF -100 +#define SERIAL_EQ 0 +#define SERIAL_LT -1 +#define SERIAL_GT 1 + +/* http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ +static char *ds_digest_name(int digest) +{ + switch (digest) + { + case 1: return "sha1"; + case 2: return "sha256"; + case 3: return "gosthash94"; + case 4: return "sha384"; + default: return NULL; + } +} + +/* http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ +static char *algo_digest_name(int algo) +{ + switch (algo) + { + case 1: return "md5"; + case 3: return "sha1"; + case 5: return "sha1"; + case 6: return "sha1"; + case 7: return "sha1"; + case 8: return "sha256"; + case 10: return "sha512"; + case 12: return "gosthash94"; + case 13: return "sha256"; + case 14: return "sha384"; + default: return NULL; + } +} + +/* Find pointer to correct hash function in nettle library */ +static const struct nettle_hash *hash_find(char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; nettle_hashes[i]; i++) + { + if (strcmp(nettle_hashes[i]->name, name) == 0) + return nettle_hashes[i]; + } + + return NULL; +} + +/* expand ctx and digest memory allocations if necessary and init hash function */ +static int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) +{ + static void *ctx = NULL; + static unsigned char *digest = NULL; + static unsigned int ctx_sz = 0; + static unsigned int digest_sz = 0; + + void *new; + + if (ctx_sz < hash->context_size) + { + if (!(new = whine_malloc(hash->context_size))) + return 0; + if (ctx) + free(ctx); + ctx = new; + ctx_sz = hash->context_size; + } + + if (digest_sz < hash->digest_size) + { + if (!(new = whine_malloc(hash->digest_size))) + return 0; + if (digest) + free(digest); + digest = new; + digest_sz = hash->digest_size; + } + + *ctxp = ctx; + *digestp = digest; + + hash->init(ctx); + + return 1; +} + +static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, int algo) +{ + unsigned char *p; + size_t exp_len; + + static struct rsa_public_key *key = NULL; + static mpz_t sig_mpz; + + if (key == NULL) + { + if (!(key = whine_malloc(sizeof(struct rsa_public_key)))) + return 0; + + nettle_rsa_public_key_init(key); + mpz_init(sig_mpz); + } + + if ((key_len < 3) || !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + key_len--; + if ((exp_len = *p++) == 0) + { + GETSHORT(exp_len, p); + key_len -= 2; + } + + if (exp_len >= key_len) + return 0; + + key->size = key_len - exp_len; + mpz_import(key->e, exp_len, 1, 1, 0, 0, p); + mpz_import(key->n, key->size, 1, 1, 0, 0, p + exp_len); + + mpz_import(sig_mpz, sig_len, 1, 1, 0, 0, sig); + + switch (algo) + { + case 1: + return nettle_rsa_md5_verify_digest(key, digest, sig_mpz); + case 5: case 7: + return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz); + case 8: + return nettle_rsa_sha256_verify_digest(key, digest, sig_mpz); + case 10: + return nettle_rsa_sha512_verify_digest(key, digest, sig_mpz); + } + + return 0; +} + +static int dnsmasq_dsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, int algo) +{ + unsigned char *p; + unsigned int t; + + static struct dsa_public_key *key = NULL; + static struct dsa_signature *sig_struct; + + if (key == NULL) + { + if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) || + !(key = whine_malloc(sizeof(struct dsa_public_key)))) + return 0; + + nettle_dsa_public_key_init(key); + nettle_dsa_signature_init(sig_struct); + } + + if ((sig_len < 41) || !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + t = *p++; + + if (key_len < (213 + (t * 24))) + return 0; + + mpz_import(key->q, 20, 1, 1, 0, 0, p); p += 20; + mpz_import(key->p, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); + mpz_import(key->g, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); + mpz_import(key->y, 64 + (t*8), 1, 1, 0, 0, p); p += 64 + (t*8); + + mpz_import(sig_struct->r, 20, 1, 1, 0, 0, sig+1); + mpz_import(sig_struct->s, 20, 1, 1, 0, 0, sig+21); + + (void)algo; + + return nettle_dsa_sha1_verify_digest(key, digest, sig_struct); +} + +#ifndef NO_NETTLE_ECC +static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len, + unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +{ + unsigned char *p; + unsigned int t; + struct ecc_point *key; + + static struct ecc_point *key_256 = NULL, *key_384 = NULL; + static mpz_t x, y; + static struct dsa_signature *sig_struct; + + if (!sig_struct) + { + if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature)))) + return 0; + + nettle_dsa_signature_init(sig_struct); + mpz_init(x); + mpz_init(y); + } + + switch (algo) + { + case 13: + if (!key_256) + { + if (!(key_256 = whine_malloc(sizeof(struct ecc_point)))) + return 0; + + nettle_ecc_point_init(key_256, &nettle_secp_256r1); + } + + key = key_256; + t = 32; + break; + + case 14: + if (!key_384) + { + if (!(key_384 = whine_malloc(sizeof(struct ecc_point)))) + return 0; + + nettle_ecc_point_init(key_384, &nettle_secp_384r1); + } + + key = key_384; + t = 48; + break; + + default: + return 0; + } + + if (sig_len != 2*t || key_len != 2*t || + !(p = blockdata_retrieve(key_data, key_len, NULL))) + return 0; + + mpz_import(x, t , 1, 1, 0, 0, p); + mpz_import(y, t , 1, 1, 0, 0, p + t); + + if (!ecc_point_set(key, x, y)) + return 0; + + mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig); + mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t); + + return nettle_ecdsa_verify(key, digest_len, digest, sig_struct); +} +#endif + +static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +{ + (void)digest_len; + + switch (algo) + { + case 1: case 5: case 7: case 8: case 10: + return dnsmasq_rsa_verify(key_data, key_len, sig, sig_len, digest, algo); + + case 3: case 6: + return dnsmasq_dsa_verify(key_data, key_len, sig, sig_len, digest, algo); + +#ifndef NO_NETTLE_ECC + case 13: case 14: + return dnsmasq_ecdsa_verify(key_data, key_len, sig, sig_len, digest, digest_len, algo); +#endif + } + + return 0; +} + +/* Convert from presentation format to wire format, in place. + Also map UC -> LC. + Note that using extract_name to get presentation format + then calling to_wire() removes compression and maps case, + thus generating names in canonical form. + Calling to_wire followed by from_wire is almost an identity, + except that the UC remains mapped to LC. + + Note that both /000 and '.' are allowed within labels. These get + represented in presentation format using NAME_ESCAPE as an escape + character. In theory, if all the characters in a name were /000 or + '.' or NAME_ESCAPE then all would have to be escaped, so the + presentation format would be twice as long as the spec (1024). + The buffers are all delcared as 2049 (allowing for the trailing zero) + for this reason. +*/ +static int to_wire(char *name) +{ + unsigned char *l, *p, *q, term; + int len; + + for (l = (unsigned char*)name; *l != 0; l = p) + { + for (p = l; *p != '.' && *p != 0; p++) + if (*p >= 'A' && *p <= 'Z') + *p = *p - 'A' + 'a'; + else if (*p == NAME_ESCAPE) + { + for (q = p; *q; q++) + *q = *(q+1); + (*p)--; + } + term = *p; + + if ((len = p - l) != 0) + memmove(l+1, l, len); + *l = len; + + p++; + + if (term == 0) + *p = 0; + } + + return l + 1 - (unsigned char *)name; +} + +/* Note: no compression allowed in input. */ +static void from_wire(char *name) +{ + unsigned char *l, *p, *last; + int len; + + for (last = (unsigned char *)name; *last != 0; last += *last+1); + + for (l = (unsigned char *)name; *l != 0; l += len+1) + { + len = *l; + memmove(l, l+1, len); + for (p = l; p < l + len; p++) + if (*p == '.' || *p == 0 || *p == NAME_ESCAPE) + { + memmove(p+1, p, 1 + last - p); + len++; + *p++ = NAME_ESCAPE; + (*p)++; + } + + l[len] = '.'; + } + + if ((char *)l != name) + *(l-1) = 0; +} + +/* Input in presentation format */ +static int count_labels(char *name) +{ + int i; + + if (*name == 0) + return 0; + + for (i = 0; *name; name++) + if (*name == '.') + i++; + + return i+1; +} + +/* Implement RFC1982 wrapped compare for 32-bit numbers */ +static int serial_compare_32(unsigned long s1, unsigned long s2) +{ + if (s1 == s2) + return SERIAL_EQ; + + if ((s1 < s2 && (s2 - s1) < (1UL<<31)) || + (s1 > s2 && (s1 - s2) > (1UL<<31))) + return SERIAL_LT; + if ((s1 < s2 && (s2 - s1) > (1UL<<31)) || + (s1 > s2 && (s1 - s2) < (1UL<<31))) + return SERIAL_GT; + return SERIAL_UNDEF; +} + +/* Called at startup. If the timestamp file is configured and exists, put its mtime on + timestamp_time. If it doesn't exist, create it, and set the mtime to 1-1-2015. + return -1 -> Cannot create file. + 0 -> not using timestamp, or timestamp exists and is in past. + 1 -> timestamp exists and is in future. +*/ + +static time_t timestamp_time; + +int setup_timestamp(void) +{ + struct stat statbuf; + + daemon->back_to_the_future = 0; + + if (!daemon->timestamp_file) + return 0; + + if (stat(daemon->timestamp_file, &statbuf) != -1) + { + timestamp_time = statbuf.st_mtime; + check_and_exit: + if (difftime(timestamp_time, time(0)) <= 0) + { + /* time already OK, update timestamp, and do key checking from the start. */ + if (utime(daemon->timestamp_file, NULL) == -1) + my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno)); + daemon->back_to_the_future = 1; + return 0; + } + return 1; + } + + if (errno == ENOENT) + { + /* NB. for explanation of O_EXCL flag, see comment on pidfile in dnsmasq.c */ + int fd = open(daemon->timestamp_file, O_WRONLY | O_CREAT | O_NONBLOCK | O_EXCL, 0666); + if (fd != -1) + { + struct utimbuf timbuf; + + close(fd); + + timestamp_time = timbuf.actime = timbuf.modtime = 1420070400; /* 1-1-2015 */ + if (utime(daemon->timestamp_file, &timbuf) == 0) + goto check_and_exit; + } + } + + return -1; +} + +/* Check whether today/now is between date_start and date_end */ +static int check_date_range(unsigned long date_start, unsigned long date_end) +{ + unsigned long curtime = time(0); + + /* Checking timestamps may be temporarily disabled */ + + /* If the current time if _before_ the timestamp + on our persistent timestamp file, then assume the + time if not yet correct, and don't check the + key timestamps. As soon as the current time is + later then the timestamp, update the timestamp + and start checking keys */ + if (daemon->timestamp_file) + { + if (daemon->back_to_the_future == 0 && difftime(timestamp_time, curtime) <= 0) + { + if (utime(daemon->timestamp_file, NULL) != 0) + my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno)); + + daemon->back_to_the_future = 1; + set_option_bool(OPT_DNSSEC_TIME); + queue_event(EVENT_RELOAD); /* purge cache */ + } + + if (daemon->back_to_the_future == 0) + return 1; + } + else if (option_bool(OPT_DNSSEC_TIME)) + return 1; + + /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ + return serial_compare_32(curtime, date_start) == SERIAL_GT + && serial_compare_32(curtime, date_end) == SERIAL_LT; +} + +static u16 *get_desc(int type) +{ + /* List of RRtypes which include domains in the data. + 0 -> domain + integer -> no of plain bytes + -1 -> end + + zero is not a valid RRtype, so the final entry is returned for + anything which needs no mangling. + */ + + static u16 rr_desc[] = + { + T_NS, 0, -1, + T_MD, 0, -1, + T_MF, 0, -1, + T_CNAME, 0, -1, + T_SOA, 0, 0, -1, + T_MB, 0, -1, + T_MG, 0, -1, + T_MR, 0, -1, + T_PTR, 0, -1, + T_MINFO, 0, 0, -1, + T_MX, 2, 0, -1, + T_RP, 0, 0, -1, + T_AFSDB, 2, 0, -1, + T_RT, 2, 0, -1, + T_SIG, 18, 0, -1, + T_PX, 2, 0, 0, -1, + T_NXT, 0, -1, + T_KX, 2, 0, -1, + T_SRV, 6, 0, -1, + T_DNAME, 0, -1, + 0, -1 /* wildcard/catchall */ + }; + + u16 *p = rr_desc; + + while (*p != type && *p != 0) + while (*p++ != (u16)-1); + + return p+1; +} + +/* Return bytes of canonicalised rdata, when the return value is zero, the remaining + data, pointed to by *p, should be used raw. */ +static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen, + unsigned char **p, u16 **desc) +{ + int d = **desc; + + /* No more data needs mangling */ + if (d == (u16)-1) + { + /* If there's more data than we have space for, just return what fits, + we'll get called again for more chunks */ + if (end - *p > bufflen) + { + memcpy(buff, *p, bufflen); + *p += bufflen; + return bufflen; + } + + return 0; + } + + (*desc)++; + + if (d == 0 && extract_name(header, plen, p, buff, 1, 0)) + /* domain-name, canonicalise */ + return to_wire(buff); + else + { + /* plain data preceding a domain-name, don't run off the end of the data */ + if ((end - *p) < d) + d = end - *p; + + if (d != 0) + { + memcpy(buff, *p, d); + *p += d; + } + + return d; + } +} + +static int expand_workspace(unsigned char ***wkspc, int *sz, int new) +{ + unsigned char **p; + int new_sz = *sz; + + if (new_sz > new) + return 1; + + if (new >= 100) + return 0; + + new_sz += 5; + + if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **)))) + return 0; + + if (*wkspc) + { + memcpy(p, *wkspc, *sz * sizeof(unsigned char **)); + free(*wkspc); + } + + *wkspc = p; + *sz = new_sz; + + return 1; +} + +/* Bubble sort the RRset into the canonical order. + Note that the byte-streams from two RRs may get unsynced: consider + RRs which have two domain-names at the start and then other data. + The domain-names may have different lengths in each RR, but sort equal + + ------------ + |abcde|fghi| + ------------ + |abcd|efghi| + ------------ + + leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. +*/ + +static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, + unsigned char **rrset, char *buff1, char *buff2) +{ + int swap, quit, i; + + do + { + for (swap = 0, i = 0; i < rrsetidx-1; i++) + { + int rdlen1, rdlen2, left1, left2, len1, len2, len, rc; + u16 *dp1, *dp2; + unsigned char *end1, *end2; + /* Note that these have been determined to be OK previously, + so we don't need to check for NULL return here. */ + unsigned char *p1 = skip_name(rrset[i], header, plen, 10); + unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10); + + p1 += 8; /* skip class, type, ttl */ + GETSHORT(rdlen1, p1); + end1 = p1 + rdlen1; + + p2 += 8; /* skip class, type, ttl */ + GETSHORT(rdlen2, p2); + end2 = p2 + rdlen2; + + dp1 = dp2 = rr_desc; + + for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;) + { + if (left1 != 0) + memmove(buff1, buff1 + len1 - left1, left1); + + if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0) + { + quit = 1; + len1 = end1 - p1; + memcpy(buff1 + left1, p1, len1); + } + len1 += left1; + + if (left2 != 0) + memmove(buff2, buff2 + len2 - left2, left2); + + if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0) + { + quit = 1; + len2 = end2 - p2; + memcpy(buff2 + left2, p2, len2); + } + len2 += left2; + + if (len1 > len2) + left1 = len1 - len2, left2 = 0, len = len2; + else + left2 = len2 - len1, left1 = 0, len = len1; + + rc = (len == 0) ? 0 : memcmp(buff1, buff2, len); + + if (rc > 0 || (rc == 0 && quit && len1 > len2)) + { + unsigned char *tmp = rrset[i+1]; + rrset[i+1] = rrset[i]; + rrset[i] = tmp; + swap = quit = 1; + } + else if (rc < 0) + quit = 1; + } + } + } while (swap); +} + +/* Validate a single RRset (class, type, name) in the supplied DNS reply + Return code: + STAT_SECURE if it validates. + STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. + (In this case *wildcard_out points to the "body" of the wildcard within name.) + STAT_NO_SIG no RRsigs found. + STAT_INSECURE RRset empty. + STAT_BOGUS signature is wrong, bad packet. + STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) + + if key is non-NULL, use that key, which has the algo and tag given in the params of those names, + otherwise find the key in the cache. + + name is unchanged on exit. keyname is used as workspace and trashed. +*/ +static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, + char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) +{ + static unsigned char **rrset = NULL, **sigs = NULL; + static int rrset_sz = 0, sig_sz = 0; + + unsigned char *p; + int rrsetidx, sigidx, res, rdlen, j, name_labels; + struct crec *crecp = NULL; + int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; + u16 *rr_desc = get_desc(type); + + if (wildcard_out) + *wildcard_out = NULL; + + if (!(p = skip_questions(header, plen))) + return STAT_BOGUS; + + name_labels = count_labels(name); /* For 4035 5.3.2 check */ + + /* look for RRSIGs for this RRset and get pointers to each RR in the set. */ + for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); + j != 0; j--) + { + unsigned char *pstart, *pdata; + int stype, sclass; + + pstart = p; + + if (!(res = extract_name(header, plen, &p, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(stype, p); + GETSHORT(sclass, p); + p += 4; /* TTL */ + + pdata = p; + + GETSHORT(rdlen, p); + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; + + if (res == 1 && sclass == class) + { + if (stype == type) + { + if (!expand_workspace(&rrset, &rrset_sz, rrsetidx)) + return STAT_BOGUS; + + rrset[rrsetidx++] = pstart; + } + + if (stype == T_RRSIG) + { + if (rdlen < 18) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type_covered, p); + + if (type_covered == type) + { + if (!expand_workspace(&sigs, &sig_sz, sigidx)) + return STAT_BOGUS; + + sigs[sigidx++] = pdata; + } + + p = pdata + 2; /* restore for ADD_RDLEN */ + } + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_BOGUS; + } + + /* RRset empty */ + if (rrsetidx == 0) + return STAT_INSECURE; + + /* no RRSIGs */ + if (sigidx == 0) + return STAT_NO_SIG; + + /* Sort RRset records into canonical order. + Note that at this point keyname and daemon->workspacename buffs are + unused, and used as workspace by the sort. */ + sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); + + /* Now try all the sigs to try and find one which validates */ + for (j = 0; j <sigidx; j++) + { + unsigned char *psav, *sig, *digest; + int i, wire_len, sig_len; + const struct nettle_hash *hash; + void *ctx; + char *name_start; + u32 nsigttl; + + p = sigs[j]; + GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */ + psav = p; + + p += 2; /* type_covered - already checked */ + algo = *p++; + labels = *p++; + GETLONG(orig_ttl, p); + GETLONG(sig_expiration, p); + GETLONG(sig_inception, p); + GETSHORT(key_tag, p); + + if (!extract_name(header, plen, &p, keyname, 1, 0)) + return STAT_BOGUS; + + /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal + the name of the zone containing the RRset. We can't tell that + for certain, but we can check that the RRset name is equal to + or encloses the signers name, which should be enough to stop + an attacker using signatures made with the key of an unrelated + zone he controls. Note that the root key is always allowed. */ + if (*keyname != 0) + { + int failed = 0; + + for (name_start = name; !hostname_isequal(name_start, keyname); ) + if ((name_start = strchr(name_start, '.'))) + name_start++; /* chop a label off and try again */ + else + { + failed = 1; + break; + } + + /* Bad sig, try another */ + if (failed) + continue; + } + + /* Other 5.3.1 checks */ + if (!check_date_range(sig_inception, sig_expiration) || + labels > name_labels || + !(hash = hash_find(algo_digest_name(algo))) || + !hash_init(hash, &ctx, &digest)) + continue; + + /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ + if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) + return STAT_NEED_KEY; + + sig = p; + sig_len = rdlen - (p - psav); + + nsigttl = htonl(orig_ttl); + + hash->update(ctx, 18, psav); + wire_len = to_wire(keyname); + hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); + from_wire(keyname); + + for (i = 0; i < rrsetidx; ++i) + { + int seg; + unsigned char *end, *cp; + u16 len, *dp; + + p = rrset[i]; + if (!extract_name(header, plen, &p, name, 1, 10)) + return STAT_BOGUS; + + name_start = name; + + /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */ + if (labels < name_labels) + { + int k; + for (k = name_labels - labels; k != 0; k--) + { + while (*name_start != '.' && *name_start != 0) + name_start++; + if (k != 1 && *name_start == '.') + name_start++; + } + + if (wildcard_out) + *wildcard_out = name_start+1; + + name_start--; + *name_start = '*'; + } + + wire_len = to_wire(name_start); + hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start); + hash->update(ctx, 4, p); /* class and type */ + hash->update(ctx, 4, (unsigned char *)&nsigttl); + + p += 8; /* skip class, type, ttl */ + GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; + + end = p + rdlen; + + /* canonicalise rdata and calculate length of same, use name buffer as workspace. + Note that name buffer is twice MAXDNAME long in DNSSEC mode. */ + cp = p; + dp = rr_desc; + for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg); + len += end - cp; + len = htons(len); + hash->update(ctx, 2, (unsigned char *)&len); + + /* Now canonicalise again and digest. */ + cp = p; + dp = rr_desc; + while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp))) + hash->update(ctx, seg, (unsigned char *)name); + if (cp != end) + hash->update(ctx, end - cp, cp); + } + + hash->digest(ctx, hash->digest_size, digest); + + /* namebuff used for workspace above, restore to leave unchanged on exit */ + p = (unsigned char*)(rrset[0]); + extract_name(header, plen, &p, name, 1, 0); + + if (key) + { + if (algo_in == algo && keytag_in == key_tag && + verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo)) + return STAT_SECURE; + } + else + { + /* iterate through all possible keys 4035 5.3.1 */ + for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) + if (crecp->addr.key.algo == algo && + crecp->addr.key.keytag == key_tag && + crecp->uid == (unsigned int)class && + verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo)) + return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE; + } + } + + return STAT_BOGUS; +} + +/* The DNS packet is expected to contain the answer to a DNSKEY query. + Put all DNSKEYs in the answer which are valid into the cache. + return codes: + STAT_SECURE At least one valid DNSKEY found and in cache. + STAT_BOGUS No DNSKEYs found, which can be validated with DS, + or self-sign for DNSKEY RRset is not valid, bad packet. + STAT_NEED_DS DS records to validate a key not found, name in keyname +*/ +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +{ + unsigned char *psave, *p = (unsigned char *)(header+1); + struct crec *crecp, *recp1; + int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag, type_covered; + struct blockdata *key; + struct all_addr a; + + if (ntohs(header->qdcount) != 1 || + !extract_name(header, plen, &p, name, 1, 4)) + return STAT_BOGUS; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0) + return STAT_BOGUS; + + /* See if we have cached a DS record which validates this key */ + if (!(crecp = cache_find_by_name(NULL, name, now, F_DS))) + { + strcpy(keyname, name); + return STAT_NEED_DS; + } + + /* If we've cached that DS provably doesn't exist, result must be INSECURE */ + if (crecp->flags & F_NEG) + return STAT_INSECURE_DS; + + /* NOTE, we need to find ONE DNSKEY which matches the DS */ + for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) + { + /* Ensure we have type, class TTL and length */ + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4) + return STAT_BOGUS; /* bad packet */ + + if (qclass != class || qtype != T_DNSKEY || rc == 2) + { + p += rdlen; + continue; + } + + psave = p; + + GETSHORT(flags, p); + if (*p++ != 3) + return STAT_BOGUS; + algo = *p++; + keytag = dnskey_keytag(algo, flags, p, rdlen - 4); + key = NULL; + + /* key must have zone key flag set */ + if (flags & 0x100) + key = blockdata_alloc((char*)p, rdlen - 4); + + p = psave; + + if (!ADD_RDLEN(header, p, plen, rdlen)) + { + if (key) + blockdata_free(key); + return STAT_BOGUS; /* bad packet */ + } + + /* No zone key flag or malloc failure */ + if (!key) + continue; + + for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) + { + void *ctx; + unsigned char *digest, *ds_digest; + const struct nettle_hash *hash; + + if (recp1->addr.ds.algo == algo && + recp1->addr.ds.keytag == keytag && + recp1->uid == (unsigned int)class && + (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) && + hash_init(hash, &ctx, &digest)) + + { + int wire_len = to_wire(name); + + /* Note that digest may be different between DSs, so + we can't move this outside the loop. */ + hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name); + hash->update(ctx, (unsigned int)rdlen, psave); + hash->digest(ctx, hash->digest_size, digest); + + from_wire(name); + + if (recp1->addr.ds.keylen == (int)hash->digest_size && + (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) && + memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && + validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) + { + valid = 1; + break; + } + } + } + blockdata_free(key); + } + + if (valid) + { + /* DNSKEY RRset determined to be OK, now cache it and the RRsigs that sign it. */ + cache_start_insert(); + + p = skip_questions(header, plen); + + for (j = ntohs(header->ancount); j != 0; j--) + { + /* Ensure we have type, class TTL and length */ + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + return STAT_INSECURE; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; /* bad packet */ + + if (qclass == class && rc == 1) + { + psave = p; + + if (qtype == T_DNSKEY) + { + if (rdlen < 4) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(flags, p); + if (*p++ != 3) + return STAT_BOGUS; + algo = *p++; + keytag = dnskey_keytag(algo, flags, p, rdlen - 4); + + /* Cache needs to known class for DNSSEC stuff */ + a.addr.dnssec.class = class; + + if ((key = blockdata_alloc((char*)p, rdlen - 4))) + { + if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))) + blockdata_free(key); + else + { + a.addr.keytag = keytag; + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u"); + + recp1->addr.key.keylen = rdlen - 4; + recp1->addr.key.keydata = key; + recp1->addr.key.algo = algo; + recp1->addr.key.keytag = keytag; + recp1->addr.key.flags = flags; + } + } + } + else if (qtype == T_RRSIG) + { + /* RRSIG, cache if covers DNSKEY RRset */ + if (rdlen < 18) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type_covered, p); + + if (type_covered == T_DNSKEY) + { + a.addr.dnssec.class = class; + a.addr.dnssec.type = type_covered; + + algo = *p++; + p += 13; /* labels, orig_ttl, expiration, inception */ + GETSHORT(keytag, p); + if ((key = blockdata_alloc((char*)psave, rdlen))) + { + if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) + blockdata_free(key); + else + { + crecp->addr.sig.keydata = key; + crecp->addr.sig.keylen = rdlen; + crecp->addr.sig.keytag = keytag; + crecp->addr.sig.type_covered = type_covered; + crecp->addr.sig.algo = algo; + } + } + } + } + + p = psave; + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_BOGUS; /* bad packet */ + } + + /* commit cache insert. */ + cache_end_insert(); + return STAT_SECURE; + } + + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY"); + return STAT_BOGUS; +} + +/* The DNS packet is expected to contain the answer to a DS query + Put all DSs in the answer which are valid into the cache. + return codes: + STAT_SECURE At least one valid DS found and in cache. + STAT_NO_DS It's proved there's no DS here. + STAT_NO_NS It's proved there's no DS _or_ NS here. + STAT_BOGUS no DS in reply or not signed, fails validation, bad packet. + STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname +*/ + +int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +{ + unsigned char *p = (unsigned char *)(header+1); + int qtype, qclass, val, i, neganswer, nons; + + if (ntohs(header->qdcount) != 1 || + !(p = skip_name(p, header, plen, 4))) + return STAT_BOGUS; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qtype != T_DS || qclass != class) + val = STAT_BOGUS; + else + val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer, &nons); + /* Note dnssec_validate_reply() will have cached positive answers */ + + if (val == STAT_INSECURE) + val = STAT_BOGUS; + + p = (unsigned char *)(header+1); + extract_name(header, plen, &p, name, 1, 4); + p += 4; /* qtype, qclass */ + + if (!(p = skip_section(p, ntohs(header->ancount), header, plen))) + val = STAT_BOGUS; + + /* If we return STAT_NO_SIG, name contains the name of the DS query */ + if (val == STAT_NO_SIG) + { + *keyname = 0; + return val; + } + + /* If the key needed to validate the DS is on the same domain as the DS, we'll + loop getting nowhere. Stop that now. This can happen of the DS answer comes + from the DS's zone, and not the parent zone. */ + if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname))) + { + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); + return STAT_BOGUS; + } + + /* By here, the answer is proved secure, and a positive answer has been cached. */ + if (val == STAT_SECURE && neganswer) + { + int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; + unsigned long ttl, minttl = ULONG_MAX; + struct all_addr a; + + if (RCODE(header) == NXDOMAIN) + flags |= F_NXDOMAIN; + + /* We only cache validated DS records, DNSSECOK flag hijacked + to store presence/absence of NS. */ + if (nons) + flags &= ~F_DNSSECOK; + + for (i = ntohs(header->nscount); i != 0; i--) + { + if (!(p = skip_name(p, header, plen, 0))) + return STAT_BOGUS; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + GETLONG(ttl, p); + GETSHORT(rdlen, p); + + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; /* bad packet */ + + if (qclass != class || qtype != T_SOA) + { + p += rdlen; + continue; + } + + if (ttl < minttl) + minttl = ttl; + + /* MNAME */ + if (!(p = skip_name(p, header, plen, 0))) + return STAT_BOGUS; + /* RNAME */ + if (!(p = skip_name(p, header, plen, 20))) + return STAT_BOGUS; + p += 16; /* SERIAL REFRESH RETRY EXPIRE */ + + GETLONG(ttl, p); /* minTTL */ + if (ttl < minttl) + minttl = ttl; + + break; + } + + if (i != 0) + { + cache_start_insert(); + + a.addr.dnssec.class = class; + cache_insert(name, &a, now, ttl, flags); + + cache_end_insert(); + + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no delegation" : "no DS"); + } + + return nons ? STAT_NO_NS : STAT_NO_DS; + } + + return val; +} + +/* 4034 6.1 */ +static int hostname_cmp(const char *a, const char *b) +{ + char *sa, *ea, *ca, *sb, *eb, *cb; + unsigned char ac, bc; + + sa = ea = (char *)a + strlen(a); + sb = eb = (char *)b + strlen(b); + + while (1) + { + while (sa != a && *(sa-1) != '.') + sa--; + + while (sb != b && *(sb-1) != '.') + sb--; + + ca = sa; + cb = sb; + + while (1) + { + if (ca == ea) + { + if (cb == eb) + break; + + return -1; + } + + if (cb == eb) + return 1; + + ac = (unsigned char) *ca++; + bc = (unsigned char) *cb++; + + if (ac >= 'A' && ac <= 'Z') + ac += 'a' - 'A'; + if (bc >= 'A' && bc <= 'Z') + bc += 'a' - 'A'; + + if (ac < bc) + return -1; + else if (ac != bc) + return 1; + } + + + if (sa == a) + { + if (sb == b) + return 0; + + return -1; + } + + if (sb == b) + return 1; + + ea = sa--; + eb = sb--; + } +} + +/* Find all the NSEC or NSEC3 records in a reply. + return an array of pointers to them. */ +static int find_nsec_records(struct dns_header *header, size_t plen, unsigned char ***nsecsetp, int *nsecsetl, int class_reqd) +{ + static unsigned char **nsecset = NULL; + static int nsecset_sz = 0; + + int type_found = 0; + unsigned char *p = skip_questions(header, plen); + int type, class, rdlen, i, nsecs_found; + + /* Move to NS section */ + if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) + return 0; + + for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) + { + unsigned char *pstart = p; + + if (!(p = skip_name(p, header, plen, 10))) + return 0; + + GETSHORT(type, p); + GETSHORT(class, p); + p += 4; /* TTL */ + GETSHORT(rdlen, p); + + if (class == class_reqd && (type == T_NSEC || type == T_NSEC3)) + { + /* No mixed NSECing 'round here, thankyouverymuch */ + if (type_found == T_NSEC && type == T_NSEC3) + return 0; + if (type_found == T_NSEC3 && type == T_NSEC) + return 0; + + type_found = type; + + if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) + return 0; + + nsecset[nsecs_found++] = pstart; + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return 0; + } + + *nsecsetp = nsecset; + *nsecsetl = nsecs_found; + + return type_found; +} + +static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, + char *workspace1, char *workspace2, char *name, int type, int *nons) +{ + int i, rc, rdlen; + unsigned char *p, *psave; + int offset = (type & 0xff) >> 3; + int mask = 0x80 >> (type & 0x07); + + if (nons) + *nons = 0; + + /* Find NSEC record that proves name doesn't exist */ + for (i = 0; i < nsec_count; i++) + { + p = nsecs[i]; + if (!extract_name(header, plen, &p, workspace1, 1, 10)) + return STAT_BOGUS; + p += 8; /* class, type, TTL */ + GETSHORT(rdlen, p); + psave = p; + if (!extract_name(header, plen, &p, workspace2, 1, 10)) + return STAT_BOGUS; + + rc = hostname_cmp(workspace1, name); + + if (rc == 0) + { + /* 4035 para 5.4. Last sentence */ + if (type == T_NSEC || type == T_RRSIG) + return STAT_SECURE; + + /* NSEC with the same name as the RR we're testing, check + that the type in question doesn't appear in the type map */ + rdlen -= p - psave; + /* rdlen is now length of type map, and p points to it */ + + /* If we can prove that there's no NS record, return that information. */ + if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0) + *nons = 1; + + while (rdlen >= 2) + { + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; + + if (p[0] == type >> 8) + { + /* Does the NSEC say our type exists? */ + if (offset < p[1] && (p[offset+2] & mask) != 0) + return STAT_BOGUS; + + break; /* finshed checking */ + } + + rdlen -= p[1]; + p += p[1]; + } + + return STAT_SECURE; + } + else if (rc == -1) + { + /* Normal case, name falls between NSEC name and next domain name, + wrap around case, name falls between NSEC name (rc == -1) and end */ + if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0) + return STAT_SECURE; + } + else + { + /* wrap around case, name falls between start and next domain name */ + if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 ) + return STAT_SECURE; + } + } + + return STAT_BOGUS; +} + +/* return digest length, or zero on error */ +static int hash_name(char *in, unsigned char **out, struct nettle_hash const *hash, + unsigned char *salt, int salt_len, int iterations) +{ + void *ctx; + unsigned char *digest; + int i; + + if (!hash_init(hash, &ctx, &digest)) + return 0; + + hash->update(ctx, to_wire(in), (unsigned char *)in); + hash->update(ctx, salt_len, salt); + hash->digest(ctx, hash->digest_size, digest); + + for(i = 0; i < iterations; i++) + { + hash->update(ctx, hash->digest_size, digest); + hash->update(ctx, salt_len, salt); + hash->digest(ctx, hash->digest_size, digest); + } + + from_wire(in); + + *out = digest; + return hash->digest_size; +} + +/* Decode base32 to first "." or end of string */ +static int base32_decode(char *in, unsigned char *out) +{ + int oc, on, c, mask, i; + unsigned char *p = out; + + for (c = *in, oc = 0, on = 0; c != 0 && c != '.'; c = *++in) + { + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'a' && c <= 'v') + c -= 'a', c += 10; + else if (c >= 'A' && c <= 'V') + c -= 'A', c += 10; + else + return 0; + + for (mask = 0x10, i = 0; i < 5; i++) + { + if (c & mask) + oc |= 1; + mask = mask >> 1; + if (((++on) & 7) == 0) + *p++ = oc; + oc = oc << 1; + } + } + + if ((on & 7) != 0) + return 0; + + return p - out; +} + +static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type, + char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons) +{ + int i, hash_len, salt_len, base32_len, rdlen; + unsigned char *p, *psave; + + for (i = 0; i < nsec_count; i++) + if ((p = nsecs[i])) + { + if (!extract_name(header, plen, &p, workspace1, 1, 0) || + !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) + return 0; + + p += 8; /* class, type, TTL */ + GETSHORT(rdlen, p); + psave = p; + p += 4; /* algo, flags, iterations */ + salt_len = *p++; /* salt_len */ + p += salt_len; /* salt */ + hash_len = *p++; /* p now points to next hashed name */ + + if (!CHECK_LEN(header, p, plen, hash_len)) + return 0; + + if (digest_len == base32_len && hash_len == base32_len) + { + int rc = memcmp(workspace2, digest, digest_len); + + if (rc == 0) + { + /* We found an NSEC3 whose hashed name exactly matches the query, so + we just need to check the type map. p points to the RR data for the record. */ + + int offset = (type & 0xff) >> 3; + int mask = 0x80 >> (type & 0x07); + + p += hash_len; /* skip next-domain hash */ + rdlen -= p - psave; + + if (!CHECK_LEN(header, p, plen, rdlen)) + return 0; + + /* If we can prove that there's no NS record, return that information. */ + if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0) + *nons = 1; + + while (rdlen >= 2) + { + if (p[0] == type >> 8) + { + /* Does the NSEC3 say our type exists? */ + if (offset < p[1] && (p[offset+2] & mask) != 0) + return STAT_BOGUS; + + break; /* finshed checking */ + } + + rdlen -= p[1]; + p += p[1]; + } + + return 1; + } + else if (rc < 0) + { + /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, + wrap around case, name-hash falls between NSEC3 name-hash and end */ + if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0) + return 1; + } + else + { + /* wrap around case, name falls between start and next domain name */ + if (memcmp(workspace2, p, digest_len) >= 0 && memcmp(p, digest, digest_len) >= 0) + return 1; + } + } + } + return 0; +} + +static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, + char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons) +{ + unsigned char *salt, *p, *digest; + int digest_len, i, iterations, salt_len, base32_len, algo = 0; + struct nettle_hash const *hash; + char *closest_encloser, *next_closest, *wildcard; + + if (nons) + *nons = 0; + + /* Look though the NSEC3 records to find the first one with + an algorithm we support (currently only algo == 1). + + Take the algo, iterations, and salt of that record + as the ones we're going to use, and prune any + that don't match. */ + + for (i = 0; i < nsec_count; i++) + { + if (!(p = skip_name(nsecs[i], header, plen, 15))) + return STAT_BOGUS; /* bad packet */ + + p += 10; /* type, class, TTL, rdlen */ + algo = *p++; + + if (algo == 1) + break; /* known algo */ + } + + /* No usable NSEC3s */ + if (i == nsec_count) + return STAT_BOGUS; + + p++; /* flags */ + GETSHORT (iterations, p); + salt_len = *p++; + salt = p; + if (!CHECK_LEN(header, salt, plen, salt_len)) + return STAT_BOGUS; /* bad packet */ + + /* Now prune so we only have NSEC3 records with same iterations, salt and algo */ + for (i = 0; i < nsec_count; i++) + { + unsigned char *nsec3p = nsecs[i]; + int this_iter; + + nsecs[i] = NULL; /* Speculative, will be restored if OK. */ + + if (!(p = skip_name(nsec3p, header, plen, 15))) + return STAT_BOGUS; /* bad packet */ + + p += 10; /* type, class, TTL, rdlen */ + + if (*p++ != algo) + continue; + + p++; /* flags */ + + GETSHORT(this_iter, p); + if (this_iter != iterations) + continue; + + if (salt_len != *p++) + continue; + + if (!CHECK_LEN(header, p, plen, salt_len)) + return STAT_BOGUS; /* bad packet */ + + if (memcmp(p, salt, salt_len) != 0) + continue; + + /* All match, put the pointer back */ + nsecs[i] = nsec3p; + } + + /* Algo is checked as 1 above */ + if (!(hash = hash_find("sha1"))) + return STAT_BOGUS; + + if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; + + if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons)) + return STAT_SECURE; + + /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" + or an answer inferred from a wildcard record. */ + closest_encloser = name; + next_closest = NULL; + + do + { + if (*closest_encloser == '.') + closest_encloser++; + + if (wildname && hostname_isequal(closest_encloser, wildname)) + break; + + if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; + + for (i = 0; i < nsec_count; i++) + if ((p = nsecs[i])) + { + if (!extract_name(header, plen, &p, workspace1, 1, 0) || + !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) + return STAT_BOGUS; + + if (digest_len == base32_len && + memcmp(digest, workspace2, digest_len) == 0) + break; /* Gotit */ + } + + if (i != nsec_count) + break; + + next_closest = closest_encloser; + } + while ((closest_encloser = strchr(closest_encloser, '.'))); + + if (!closest_encloser) + return STAT_BOGUS; + + /* Look for NSEC3 that proves the non-existence of the next-closest encloser */ + if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; + + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL)) + return STAT_BOGUS; + + /* Finally, check that there's no seat of wildcard synthesis */ + if (!wildname) + { + if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest) + return STAT_BOGUS; + + wildcard--; + *wildcard = '*'; + + if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) + return STAT_BOGUS; + + if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL)) + return STAT_BOGUS; + } + + return STAT_SECURE; +} + +/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */ +/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */ +int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, + int *class, int *neganswer, int *nons) +{ + unsigned char *ans_start, *qname, *p1, *p2, **nsecs; + int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype; + int i, j, rc, nsec_count, cname_count = CNAME_CHAIN; + int nsec_type = 0, have_answer = 0; + + if (neganswer) + *neganswer = 0; + + if (RCODE(header) == SERVFAIL || ntohs(header->qdcount) != 1) + return STAT_BOGUS; + + if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) + return STAT_INSECURE; + + qname = p1 = (unsigned char *)(header+1); + + if (!extract_name(header, plen, &p1, name, 1, 4)) + return STAT_BOGUS; + + GETSHORT(qtype, p1); + GETSHORT(qclass, p1); + ans_start = p1; + + if (qtype == T_ANY) + have_answer = 1; + + /* Can't validate an RRISG query */ + if (qtype == T_RRSIG) + return STAT_INSECURE; + + cname_loop: + for (j = ntohs(header->ancount); j != 0; j--) + { + /* leave pointer to missing name in qname */ + + if (!(rc = extract_name(header, plen, &p1, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type2, p1); + GETSHORT(class2, p1); + p1 += 4; /* TTL */ + GETSHORT(rdlen2, p1); + + if (rc == 1 && qclass == class2) + { + /* Do we have an answer for the question? */ + if (type2 == qtype) + { + have_answer = 1; + break; + } + else if (type2 == T_CNAME) + { + qname = p1; + + /* looped CNAMES */ + if (!cname_count-- || !extract_name(header, plen, &p1, name, 1, 0)) + return STAT_BOGUS; + + p1 = ans_start; + goto cname_loop; + } + } + + if (!ADD_RDLEN(header, p1, plen, rdlen2)) + return STAT_BOGUS; + } + + if (neganswer && !have_answer) + *neganswer = 1; + + /* No data, therefore no sigs */ + if (ntohs(header->ancount) + ntohs(header->nscount) == 0) + { + *keyname = 0; + return STAT_NO_SIG; + } + + for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) + { + if (!extract_name(header, plen, &p1, name, 1, 10)) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type1, p1); + GETSHORT(class1, p1); + p1 += 4; /* TTL */ + GETSHORT(rdlen1, p1); + + /* Don't try and validate RRSIGs! */ + if (type1 != T_RRSIG) + { + /* Check if we've done this RRset already */ + for (p2 = ans_start, j = 0; j < i; j++) + { + if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type2, p2); + GETSHORT(class2, p2); + p2 += 4; /* TTL */ + GETSHORT(rdlen2, p2); + + if (type2 == type1 && class2 == class1 && rc == 1) + break; /* Done it before: name, type, class all match. */ + + if (!ADD_RDLEN(header, p2, plen, rdlen2)) + return STAT_BOGUS; + } + + /* Not done, validate now */ + if (j == i) + { + int ttl, keytag, algo, digest, type_covered; + unsigned char *psave; + struct all_addr a; + struct blockdata *key; + struct crec *crecp; + char *wildname; + int have_wildcard = 0; + + rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0); + + if (rc == STAT_SECURE_WILDCARD) + { + have_wildcard = 1; + + /* An attacker replay a wildcard answer with a different + answer and overlay a genuine RR. To prove this + hasn't happened, the answer must prove that + the gennuine record doesn't exist. Check that here. */ + if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1))) + return STAT_BOGUS; /* No NSECs or bad packet */ + + if (nsec_type == T_NSEC) + rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL); + else + rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, + keyname, name, type1, wildname, NULL); + + if (rc != STAT_SECURE) + return rc; + } + else if (rc != STAT_SECURE) + { + if (class) + *class = class1; /* Class for DS or DNSKEY */ + + if (rc == STAT_NO_SIG) + { + /* If we dropped off the end of a CNAME chain, return + STAT_NO_SIG and the last name is keyname. This is used for proving non-existence + if DS records in CNAME chains. */ + if (cname_count == CNAME_CHAIN || i < ntohs(header->ancount)) + /* No CNAME chain, or no sig in answer section, return empty name. */ + *keyname = 0; + else if (!extract_name(header, plen, &qname, keyname, 1, 0)) + return STAT_BOGUS; + } + + return rc; + } + + /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */ + cache_start_insert(); + + for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++) + { + if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type2, p2); + GETSHORT(class2, p2); + GETLONG(ttl, p2); + GETSHORT(rdlen2, p2); + + if (!CHECK_LEN(header, p2, plen, rdlen2)) + return STAT_BOGUS; /* bad packet */ + + if (class2 == class1 && rc == 1) + { + psave = p2; + + if (type1 == T_DS && type2 == T_DS) + { + if (rdlen2 < 4) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(keytag, p2); + algo = *p2++; + digest = *p2++; + + /* Cache needs to known class for DNSSEC stuff */ + a.addr.dnssec.class = class2; + + if ((key = blockdata_alloc((char*)p2, rdlen2 - 4))) + { + if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) + blockdata_free(key); + else + { + a.addr.keytag = keytag; + log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); + crecp->addr.ds.digest = digest; + crecp->addr.ds.keydata = key; + crecp->addr.ds.algo = algo; + crecp->addr.ds.keytag = keytag; + crecp->addr.ds.keylen = rdlen2 - 4; + } + } + } + else if (type2 == T_RRSIG) + { + if (rdlen2 < 18) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type_covered, p2); + + if (type_covered == type1 && + (type_covered == T_A || type_covered == T_AAAA || + type_covered == T_CNAME || type_covered == T_DS || + type_covered == T_DNSKEY || type_covered == T_PTR)) + { + a.addr.dnssec.type = type_covered; + a.addr.dnssec.class = class1; + + algo = *p2++; + p2 += 13; /* labels, orig_ttl, expiration, inception */ + GETSHORT(keytag, p2); + + /* We don't cache sigs for wildcard answers, because to reproduce the + answer from the cache will require one or more NSEC/NSEC3 records + which we don't cache. The lack of the RRSIG ensures that a query for + this RRset asking for a secure answer will always be forwarded. */ + if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2))) + { + if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) + blockdata_free(key); + else + { + crecp->addr.sig.keydata = key; + crecp->addr.sig.keylen = rdlen2; + crecp->addr.sig.keytag = keytag; + crecp->addr.sig.type_covered = type_covered; + crecp->addr.sig.algo = algo; + } + } + } + } + + p2 = psave; + } + + if (!ADD_RDLEN(header, p2, plen, rdlen2)) + return STAT_BOGUS; /* bad packet */ + } + + cache_end_insert(); + } + } + + if (!ADD_RDLEN(header, p1, plen, rdlen1)) + return STAT_BOGUS; + } + + /* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */ + if (have_answer) + return STAT_SECURE; + + /* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */ + /* First marshall the NSEC records, if we've not done it previously */ + if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass))) + { + /* No NSEC records. If we dropped off the end of a CNAME chain, return + STAT_NO_SIG and the last name is keyname. This is used for proving non-existence + if DS records in CNAME chains. */ + if (cname_count == CNAME_CHAIN) /* No CNAME chain, return empty name. */ + *keyname = 0; + else if (!extract_name(header, plen, &qname, keyname, 1, 0)) + return STAT_BOGUS; + return STAT_NO_SIG; /* No NSECs, this is probably a dangling CNAME pointing into + an unsigned zone. Return STAT_NO_SIG to cause this to be proved. */ + } + + /* Get name of missing answer */ + if (!extract_name(header, plen, &qname, name, 1, 0)) + return STAT_BOGUS; + + if (nsec_type == T_NSEC) + return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons); + else + return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons); +} + +/* Chase the CNAME chain in the packet until the first record which _doesn't validate. + Needed for proving answer in unsigned space. + Return STAT_NEED_* + STAT_BOGUS - error + STAT_INSECURE - name of first non-secure record in name +*/ +int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname) +{ + unsigned char *p = (unsigned char *)(header+1); + int type, class, qclass, rdlen, j, rc; + int cname_count = CNAME_CHAIN; + char *wildname; + + /* Get question */ + if (!extract_name(header, plen, &p, name, 1, 4)) + return STAT_BOGUS; + + p +=2; /* type */ + GETSHORT(qclass, p); + + while (1) + { + for (j = ntohs(header->ancount); j != 0; j--) + { + if (!(rc = extract_name(header, plen, &p, name, 0, 10))) + return STAT_BOGUS; /* bad packet */ + + GETSHORT(type, p); + GETSHORT(class, p); + p += 4; /* TTL */ + GETSHORT(rdlen, p); + + /* Not target, loop */ + if (rc == 2 || qclass != class) + { + if (!ADD_RDLEN(header, p, plen, rdlen)) + return STAT_BOGUS; + continue; + } + + /* Got to end of CNAME chain. */ + if (type != T_CNAME) + return STAT_INSECURE; + + /* validate CNAME chain, return if insecure or need more data */ + rc = validate_rrset(now, header, plen, class, type, name, keyname, &wildname, NULL, 0, 0, 0); + + if (rc == STAT_SECURE_WILDCARD) + { + int nsec_type, nsec_count, i; + unsigned char **nsecs; + + /* An attacker can replay a wildcard answer with a different + answer and overlay a genuine RR. To prove this + hasn't happened, the answer must prove that + the genuine record doesn't exist. Check that here. */ + if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class))) + return STAT_BOGUS; /* No NSECs or bad packet */ + + /* Note that we're called here because something didn't validate in validate_reply, + so we can't assume that any NSEC records have been validated. We do them by steam here */ + + for (i = 0; i < nsec_count; i++) + { + unsigned char *p1 = nsecs[i]; + + if (!extract_name(header, plen, &p1, daemon->workspacename, 1, 0)) + return STAT_BOGUS; + + rc = validate_rrset(now, header, plen, class, nsec_type, daemon->workspacename, keyname, NULL, NULL, 0, 0, 0); + + /* NSECs can't be wildcards. */ + if (rc == STAT_SECURE_WILDCARD) + rc = STAT_BOGUS; + + if (rc != STAT_SECURE) + return rc; + } + + if (nsec_type == T_NSEC) + rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type, NULL); + else + rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, + keyname, name, type, wildname, NULL); + + if (rc != STAT_SECURE) + return rc; + } + + if (rc != STAT_SECURE) + { + if (rc == STAT_NO_SIG) + rc = STAT_INSECURE; + return rc; + } + + /* Loop down CNAME chain/ */ + if (!cname_count-- || + !extract_name(header, plen, &p, name, 1, 0) || + !(p = skip_questions(header, plen))) + return STAT_BOGUS; + + break; + } + + /* End of CNAME chain */ + return STAT_INSECURE; + } +} + + +/* Compute keytag (checksum to quickly index a key). See RFC4034 */ +int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen) +{ + if (alg == 1) + { + /* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm. + See RFC4034, Appendix B.1 */ + return key[keylen-4] * 256 + key[keylen-3]; + } + else + { + unsigned long ac = flags + 0x300 + alg; + int i; + + for (i = 0; i < keylen; ++i) + ac += (i & 1) ? key[i] : key[i] << 8; + + ac += (ac >> 16) & 0xffff; + return ac & 0xffff; + } +} + +size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, + int type, union mysockaddr *addr, int edns_pktsz) +{ + unsigned char *p; + char *types = querystr("dnssec-query", type); + size_t ret; + + if (addr->sa.sa_family == AF_INET) + log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types); +#ifdef HAVE_IPV6 + else + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types); +#endif + + header->qdcount = htons(1); + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + + header->hb3 = HB3_RD; + SET_OPCODE(header, QUERY); + /* For debugging, set Checking Disabled, otherwise, have the upstream check too, + this allows it to select auth servers when one is returning bad data. */ + header->hb4 = option_bool(OPT_DNSSEC_DEBUG) ? HB4_CD : 0; + + /* ID filled in later */ + + p = (unsigned char *)(header+1); + + p = do_rfc1035_name(p, name); + *p++ = 0; + PUTSHORT(type, p); + PUTSHORT(class, p); + + ret = add_do_bit(header, p - (unsigned char *)header, end); + + if (find_pseudoheader(header, ret, NULL, &p, NULL)) + PUTSHORT(edns_pktsz, p); + + return ret; +} + +/* Go through a domain name, find "pointers" and fix them up based on how many bytes + we've chopped out of the packet, or check they don't point into an elided part. */ +static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) +{ + unsigned char *ansp = *namep; + + while(1) + { + unsigned int label_type; + + if (!CHECK_LEN(header, ansp, plen, 1)) + return 0; + + label_type = (*ansp) & 0xc0; + + if (label_type == 0xc0) + { + /* pointer for compression. */ + unsigned int offset; + int i; + unsigned char *p; + + if (!CHECK_LEN(header, ansp, plen, 2)) + return 0; + + offset = ((*ansp++) & 0x3f) << 8; + offset |= *ansp++; + + p = offset + (unsigned char *)header; + + for (i = 0; i < rr_count; i++) + if (p < rrs[i]) + break; + else + if (i & 1) + offset -= rrs[i] - rrs[i-1]; + + /* does the pointer end up in an elided RR? */ + if (i & 1) + return 0; + + /* No, scale the pointer */ + if (fixup) + { + ansp -= 2; + *ansp++ = (offset >> 8) | 0xc0; + *ansp++ = offset & 0xff; + } + break; + } + else if (label_type == 0x80) + return 0; /* reserved */ + else if (label_type == 0x40) + { + /* Extended label type */ + unsigned int count; + + if (!CHECK_LEN(header, ansp, plen, 2)) + return 0; + + if (((*ansp++) & 0x3f) != 1) + return 0; /* we only understand bitstrings */ + + count = *(ansp++); /* Bits in bitstring */ + + if (count == 0) /* count == 0 means 256 bits */ + ansp += 32; + else + ansp += ((count-1)>>3)+1; + } + else + { /* label type == 0 Bottom six bits is length */ + unsigned int len = (*ansp++) & 0x3f; + + if (!ADD_RDLEN(header, ansp, plen, len)) + return 0; + + if (len == 0) + break; /* zero length label marks the end. */ + } + } + + *namep = ansp; + + return 1; +} + +/* Go through RRs and check or fixup the domain names contained within */ +static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count) +{ + int i, type, class, rdlen; + unsigned char *pp; + + for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++) + { + pp = p; + + if (!(p = skip_name(p, header, plen, 10))) + return 0; + + GETSHORT(type, p); + GETSHORT(class, p); + p += 4; /* TTL */ + GETSHORT(rdlen, p); + + if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG) + { + /* fixup name of RR */ + if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) + return 0; + + if (class == C_IN) + { + u16 *d; + + for (pp = p, d = get_desc(type); *d != (u16)-1; d++) + { + if (*d != 0) + pp += *d; + else if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) + return 0; + } + } + } + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return 0; + } + + return 1; +} + + +size_t filter_rrsigs(struct dns_header *header, size_t plen) +{ + static unsigned char **rrs; + static int rr_sz = 0; + + unsigned char *p = (unsigned char *)(header+1); + int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar; + + if (ntohs(header->qdcount) != 1 || + !(p = skip_name(p, header, plen, 4))) + return plen; + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + /* First pass, find pointers to start and end of all the records we wish to elide: + records added for DNSSEC, unless explicity queried for */ + for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; + i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); + i++) + { + unsigned char *pstart = p; + int type, class; + + if (!(p = skip_name(p, header, plen, 10))) + return plen; + + GETSHORT(type, p); + GETSHORT(class, p); + p += 4; /* TTL */ + GETSHORT(rdlen, p); + + if ((type == T_NSEC || type == T_NSEC3 || type == T_RRSIG) && + (type != qtype || class != qclass)) + { + if (!expand_workspace(&rrs, &rr_sz, rr_found + 1)) + return plen; + + rrs[rr_found++] = pstart; + + if (!ADD_RDLEN(header, p, plen, rdlen)) + return plen; + + rrs[rr_found++] = p; + + if (i < ntohs(header->ancount)) + chop_an++; + else if (i < (ntohs(header->nscount) + ntohs(header->ancount))) + chop_ns++; + else + chop_ar++; + } + else if (!ADD_RDLEN(header, p, plen, rdlen)) + return plen; + } + + /* Nothing to do. */ + if (rr_found == 0) + return plen; + + /* Second pass, look for pointers in names in the records we're keeping and make sure they don't + point to records we're going to elide. This is theoretically possible, but unlikely. If + it happens, we give up and leave the answer unchanged. */ + p = (unsigned char *)(header+1); + + /* question first */ + if (!check_name(&p, header, plen, 0, rrs, rr_found)) + return plen; + p += 4; /* qclass, qtype */ + + /* Now answers and NS */ + if (!check_rrs(p, header, plen, 0, rrs, rr_found)) + return plen; + + /* Third pass, elide records */ + for (p = rrs[0], i = 1; i < rr_found; i += 2) + { + unsigned char *start = rrs[i]; + unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen; + + memmove(p, start, end-start); + p += end-start; + } + + plen = p - (unsigned char *)header; + header->ancount = htons(ntohs(header->ancount) - chop_an); + header->nscount = htons(ntohs(header->nscount) - chop_ns); + header->arcount = htons(ntohs(header->arcount) - chop_ar); + + /* Fourth pass, fix up pointers in the remaining records */ + p = (unsigned char *)(header+1); + + check_name(&p, header, plen, 1, rrs, rr_found); + p += 4; /* qclass, qtype */ + + check_rrs(p, header, plen, 1, rrs, rr_found); + + return plen; +} + +unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) +{ + int q; + unsigned int len; + unsigned char *p = (unsigned char *)(header+1); + const struct nettle_hash *hash; + void *ctx; + unsigned char *digest; + + if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) + return NULL; + + for (q = ntohs(header->qdcount); q != 0; q--) + { + if (!extract_name(header, plen, &p, name, 1, 4)) + break; /* bad packet */ + + len = to_wire(name); + hash->update(ctx, len, (unsigned char *)name); + /* CRC the class and type as well */ + hash->update(ctx, 4, p); + + p += 4; + if (!CHECK_LEN(header, p, plen, 0)) + break; /* bad packet */ + } + + hash->digest(ctx, hash->digest_size, digest); + return digest; +} + +#endif /* HAVE_DNSSEC */ diff --git a/src/domain.c b/src/domain.c new file mode 100644 index 0000000..278698c --- /dev/null +++ b/src/domain.c @@ -0,0 +1,232 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + + +static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c); +#ifdef HAVE_IPV6 +static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c); +#endif + + +int is_name_synthetic(int flags, char *name, struct all_addr *addr) +{ + char *p; + struct cond_domain *c = NULL; + int prot = AF_INET; + +#ifdef HAVE_IPV6 + if (flags & F_IPV6) + prot = AF_INET6; +#endif + + for (c = daemon->synth_domains; c; c = c->next) + { + int found = 0; + char *tail, *pref; + + for (tail = name, pref = c->prefix; *tail != 0 && pref && *pref != 0; tail++, pref++) + { + unsigned int c1 = (unsigned char) *pref; + unsigned int c2 = (unsigned char) *tail; + + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + + if (c1 != c2) + break; + } + + if (pref && *pref != 0) + continue; /* prefix match fail */ + + /* NB, must not alter name if we return zero */ + for (p = tail; *p; p++) + { + char c = *p; + + if ((c >='0' && c <= '9') || c == '-') + continue; + +#ifdef HAVE_IPV6 + if (prot == AF_INET6 && ((c >='A' && c <= 'F') || (c >='a' && c <= 'f'))) + continue; +#endif + + break; + } + + if (*p != '.') + continue; + + *p = 0; + + /* swap . or : for - */ + for (p = tail; *p; p++) + if (*p == '-') + { + if (prot == AF_INET) + *p = '.'; +#ifdef HAVE_IPV6 + else + *p = ':'; +#endif + } + + if (hostname_isequal(c->domain, p+1) && inet_pton(prot, tail, addr)) + { + if (prot == AF_INET) + { + if (!c->is6 && + ntohl(addr->addr.addr4.s_addr) >= ntohl(c->start.s_addr) && + ntohl(addr->addr.addr4.s_addr) <= ntohl(c->end.s_addr)) + found = 1; + } +#ifdef HAVE_IPV6 + else + { + u64 addrpart = addr6part(&addr->addr.addr6); + + if (c->is6 && + is_same_net6(&addr->addr.addr6, &c->start6, 64) && + addrpart >= addr6part(&c->start6) && + addrpart <= addr6part(&c->end6)) + found = 1; + } +#endif + } + + /* restore name */ + for (p = tail; *p; p++) + if (*p == '.' || *p == ':') + *p = '-'; + + *p = '.'; + + if (found) + return 1; + } + + return 0; +} + + +int is_rev_synth(int flag, struct all_addr *addr, char *name) +{ + struct cond_domain *c; + + if (flag & F_IPV4 && (c = search_domain(addr->addr.addr4, daemon->synth_domains))) + { + char *p; + + *name = 0; + if (c->prefix) + strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN); + + inet_ntop(AF_INET, &addr->addr.addr4, name + strlen(name), ADDRSTRLEN); + for (p = name; *p; p++) + if (*p == '.') + *p = '-'; + + strncat(name, ".", MAXDNAME); + strncat(name, c->domain, MAXDNAME); + + return 1; + } + +#ifdef HAVE_IPV6 + if (flag & F_IPV6 && (c = search_domain6(&addr->addr.addr6, daemon->synth_domains))) + { + char *p; + + *name = 0; + if (c->prefix) + strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN); + + inet_ntop(AF_INET6, &addr->addr.addr6, name + strlen(name), ADDRSTRLEN); + + /* IPv6 presentation address can start with ":", but valid domain names + cannot start with "-" so prepend a zero in that case. */ + if (!c->prefix && *name == ':') + { + *name = '0'; + inet_ntop(AF_INET6, &addr->addr.addr6, name+1, ADDRSTRLEN); + } + + for (p = name; *p; p++) + if (*p == ':') + *p = '-'; + + strncat(name, ".", MAXDNAME); + strncat(name, c->domain, MAXDNAME); + + return 1; + } +#endif + + return 0; +} + + +static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c) +{ + for (; c; c = c->next) + if (!c->is6 && + ntohl(addr.s_addr) >= ntohl(c->start.s_addr) && + ntohl(addr.s_addr) <= ntohl(c->end.s_addr)) + return c; + + return NULL; +} + +char *get_domain(struct in_addr addr) +{ + struct cond_domain *c; + + if ((c = search_domain(addr, daemon->cond_domain))) + return c->domain; + + return daemon->domain_suffix; +} + +#ifdef HAVE_IPV6 +static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c) +{ + u64 addrpart = addr6part(addr); + + for (; c; c = c->next) + if (c->is6 && + is_same_net6(addr, &c->start6, 64) && + addrpart >= addr6part(&c->start6) && + addrpart <= addr6part(&c->end6)) + return c; + + return NULL; +} + +char *get_domain6(struct in6_addr *addr) +{ + struct cond_domain *c; + + if (addr && (c = search_domain6(addr, daemon->cond_domain))) + return c->domain; + + return daemon->domain_suffix; +} +#endif diff --git a/src/forward.c b/src/forward.c index 92bc6b0..2731b90 100644 --- a/src/forward.c +++ b/src/forward.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 @@ -16,19 +16,27 @@ #include "dnsmasq.h" -static struct frec *lookup_frec(unsigned short id, unsigned int crc); +static struct frec *lookup_frec(unsigned short id, void *hash); static struct frec *lookup_frec_by_sender(unsigned short id, union mysockaddr *addr, - unsigned int crc); -static unsigned short get_id(unsigned int crc); + void *hash); +static unsigned short get_id(void); static void free_frec(struct frec *f); -static struct randfd *allocate_rfd(int family); + +#ifdef HAVE_DNSSEC +static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, + int class, char *name, char *keyname, struct server *server, int *keycount); +static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname); +static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, + char *name, char *keyname); +#endif + /* Send a UDP packet with its source address set as "source" unless nowild is true, when we just send it with the kernel default */ -static void send_from(int fd, int nowild, char *packet, size_t len, - union mysockaddr *to, struct all_addr *source, - unsigned int iface) +int send_from(int fd, int nowild, char *packet, size_t len, + union mysockaddr *to, struct all_addr *source, + unsigned int iface) { struct msghdr msg; struct iovec iov[1]; @@ -70,7 +78,7 @@ static void send_from(int fd, int nowild, char *packet, size_t len, p.ipi_spec_dst = source->addr.addr4; memcpy(CMSG_DATA(cmptr), &p, sizeof(p)); msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - cmptr->cmsg_level = SOL_IP; + cmptr->cmsg_level = IPPROTO_IP; cmptr->cmsg_type = IP_PKTINFO; #elif defined(IP_SENDSRCADDR) memcpy(CMSG_DATA(cmptr), &(source->addr.addr4), sizeof(source->addr.addr4)); @@ -88,27 +96,23 @@ static void send_from(int fd, int nowild, char *packet, size_t len, memcpy(CMSG_DATA(cmptr), &p, sizeof(p)); msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cmptr->cmsg_type = daemon->v6pktinfo; - cmptr->cmsg_level = IPV6_LEVEL; + cmptr->cmsg_level = IPPROTO_IPV6; } #else - iface = 0; /* eliminate warning */ + (void)iface; /* eliminate warning */ #endif } - retry: - if (sendmsg(fd, &msg, 0) == -1) + while (retry_send(sendmsg(fd, &msg, 0))); + + /* If interface is still in DAD, EINVAL results - ignore that. */ + if (errno != 0 && errno != EINVAL) { - /* certain Linux kernels seem to object to setting the source address in the IPv6 stack - by returning EINVAL from sendmsg. In that case, try again without setting the - source address, since it will nearly alway be correct anyway. IPv6 stinks. */ - if (errno == EINVAL && msg.msg_controllen) - { - msg.msg_controllen = 0; - goto retry; - } - if (retry_send()) - goto retry; + my_syslog(LOG_ERR, _("failed to send packet: %s"), strerror(errno)); + return 0; } + + return 1; } static unsigned int search_servers(time_t now, struct all_addr **addrpp, @@ -207,10 +211,10 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, } } - if (flags == 0 && !(qtype & F_NSRR) && + if (flags == 0 && !(qtype & F_QUERY) && option_bool(OPT_NODOTS_LOCAL) && !strchr(qdomain, '.') && namelen != 0) - /* don't forward simple names, make exception for NS queries and empty name. */ - flags = F_NXDOMAIN; + /* don't forward A or AAAA queries for simple names, except the empty name */ + flags = F_NOERR; if (flags == F_NXDOMAIN && check_for_local_domain(qdomain, now)) flags = F_NOERR; @@ -234,24 +238,83 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, static int forward_query(int udpfd, union mysockaddr *udpaddr, struct all_addr *dst_addr, unsigned int dst_iface, - struct dns_header *header, size_t plen, time_t now, struct frec *forward) + struct dns_header *header, size_t plen, time_t now, + struct frec *forward, int ad_reqd, int do_bit) { char *domain = NULL; int type = 0, norebind = 0; struct all_addr *addrp = NULL; - unsigned int crc = questions_crc(header, plen, daemon->namebuff); unsigned int flags = 0; - unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); struct server *start = NULL; - - /* RFC 4035: sect 4.6 para 2 */ - header->hb4 &= ~HB4_AD; - +#ifdef HAVE_DNSSEC + void *hash = hash_questions(header, plen, daemon->namebuff); +#else + unsigned int crc = questions_crc(header, plen, daemon->namebuff); + void *hash = &crc; +#endif + unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); + unsigned char *pheader; + + (void)do_bit; + /* may be no servers available. */ if (!daemon->servers) forward = NULL; - else if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, crc))) + else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) { + /* If we didn't get an answer advertising a maximal packet in EDNS, + fall back to 1280, which should work everywhere on IPv6. + If that generates an answer, it will become the new default + for this server */ + forward->flags |= FREC_TEST_PKTSZ; + +#ifdef HAVE_DNSSEC + /* If we've already got an answer to this query, but we're awaiting keys for validation, + there's no point retrying the query, retry the key query instead...... */ + if (forward->blocking_query) + { + int fd; + + forward->flags &= ~FREC_TEST_PKTSZ; + + while (forward->blocking_query) + forward = forward->blocking_query; + + forward->flags |= FREC_TEST_PKTSZ; + + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + plen = forward->stash_len; + + if (find_pseudoheader(header, plen, NULL, &pheader, NULL)) + PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : forward->sentto->edns_pktsz, pheader); + + if (forward->sentto->addr.sa.sa_family == AF_INET) + log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); +#ifdef HAVE_IPV6 + else + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); +#endif + + if (forward->sentto->sfd) + fd = forward->sentto->sfd->fd; + else + { +#ifdef HAVE_IPV6 + if (forward->sentto->addr.sa.sa_family == AF_INET6) + fd = forward->rfd6->fd; + else +#endif + fd = forward->rfd4->fd; + } + + while (retry_send( sendto(fd, (char *)header, plen, 0, + &forward->sentto->addr.sa, + sa_len(&forward->sentto->addr)))); + + return 1; + } +#endif + /* retry on existing query, send to all available servers */ domain = forward->sentto->domain; forward->sentto->failed_queries++; @@ -270,7 +333,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); - if (!flags && !(forward = get_new_frec(now, NULL))) + if (!flags && !(forward = get_new_frec(now, NULL, 0))) /* table full - server failure. */ flags = F_NEG; @@ -280,15 +343,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, forward->dest = *dst_addr; forward->iface = dst_iface; forward->orig_id = ntohs(header->id); - forward->new_id = get_id(crc); + forward->new_id = get_id(); forward->fd = udpfd; - forward->crc = crc; + memcpy(forward->hash, hash, HASH_SIZE); forward->forwardall = 0; + forward->flags = 0; if (norebind) forward->flags |= FREC_NOREBIND; if (header->hb4 & HB4_CD) forward->flags |= FREC_CHECKING_DISABLED; - + if (ad_reqd) + forward->flags |= FREC_AD_QUESTION; +#ifdef HAVE_DNSSEC + forward->work_counter = DNSSEC_WORK; + if (do_bit) + forward->flags |= FREC_DO_QUESTION; +#endif + header->id = htons(forward->new_id); /* In strict_order mode, always try servers in the order @@ -328,8 +399,38 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, struct server *firstsentto = start; int forwarded = 0; - if (udpaddr && option_bool(OPT_ADD_MAC)) - plen = add_mac(header, plen, ((char *) header) + PACKETSZ, udpaddr); + /* If a query is retried, use the log_id for the retry when logging the answer. */ + forward->log_id = daemon->log_id; + + if (option_bool(OPT_ADD_MAC)) + plen = add_mac(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source); + + if (option_bool(OPT_CLIENT_SUBNET)) + { + size_t new = add_source_addr(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source); + if (new != plen) + { + plen = new; + forward->flags |= FREC_HAS_SUBNET; + } + } + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + size_t new_plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz); + + /* For debugging, set Checking Disabled, otherwise, have the upstream check too, + this allows it to select auth servers when one is returning bad data. */ + if (option_bool(OPT_DNSSEC_DEBUG)) + header->hb4 |= HB4_CD; + + if (new_plen != plen) + forward->flags |= FREC_ADDED_PHEADER; + + plen = new_plen; + } +#endif while (1) { @@ -339,7 +440,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (type == (start->flags & SERV_TYPE) && (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && - !(start->flags & SERV_LITERAL_ADDRESS)) + !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) { int fd; @@ -366,16 +467,27 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, daemon->rfd_save = forward->rfd4; fd = forward->rfd4->fd; } + +#ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; + if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark)) + setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } +#endif } + + if (find_pseudoheader(header, plen, NULL, &pheader, NULL)) + PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : start->edns_pktsz, pheader); - if (sendto(fd, (char *)header, plen, 0, - &start->addr.sa, - sa_len(&start->addr)) == -1) - { - if (retry_send()) - continue; - } - else + if (retry_send(sendto(fd, (char *)header, plen, 0, + &start->addr.sa, + sa_len(&start->addr)))) + continue; + + if (errno == 0) { /* Keep info in case we want to re-send this packet */ daemon->srv_save = start; @@ -419,39 +531,80 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (udpfd != -1) { plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl); - send_from(udpfd, option_bool(OPT_NOWILD), (char *)header, plen, udpaddr, dst_addr, dst_iface); + send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface); } return 0; } -static size_t process_reply(struct dns_header *header, time_t now, - struct server *server, size_t n, int check_rebind, int checking_disabled) +static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind, + int no_cache, int cache_secure, int bogusanswer, int ad_reqd, int do_bit, int added_pheader, + int check_subnet, union mysockaddr *query_source) { unsigned char *pheader, *sizep; + char **sets = 0; int munged = 0, is_sign; size_t plen; + (void)ad_reqd; + (void)do_bit; + (void)bogusanswer; + +#ifdef HAVE_IPSET + if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL)) + { + /* Similar algorithm to search_servers. */ + struct ipsets *ipset_pos; + unsigned int namelen = strlen(daemon->namebuff); + unsigned int matchlen = 0; + for (ipset_pos = daemon->ipsets; ipset_pos; ipset_pos = ipset_pos->next) + { + unsigned int domainlen = strlen(ipset_pos->domain); + char *matchstart = daemon->namebuff + namelen - domainlen; + if (namelen >= domainlen && hostname_isequal(matchstart, ipset_pos->domain) && + (domainlen == 0 || namelen == domainlen || *(matchstart - 1) == '.' ) && + domainlen >= matchlen) + { + matchlen = domainlen; + sets = ipset_pos->sets; + } + } + } +#endif + /* If upstream is advertising a larger UDP packet size than we allow, trim it so that we don't get overlarge requests for the client. We can't do this for signed packets. */ - if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)) && !is_sign) + if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign))) { unsigned short udpsz; unsigned char *psave = sizep; GETSHORT(udpsz, sizep); - if (udpsz > daemon->edns_pktsz) + + if (!is_sign && udpsz > daemon->edns_pktsz) PUTSHORT(daemon->edns_pktsz, psave); + + if (check_subnet && !check_source(header, plen, pheader, query_source)) + { + my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch")); + return 0; + } + + if (added_pheader) + { + pheader = 0; + header->arcount = htons(0); + } } - + /* RFC 4035 sect 4.6 para 3 */ - if (!is_sign && !option_bool(OPT_DNSSEC)) + if (!is_sign && !option_bool(OPT_DNSSEC_PROXY)) header->hb4 &= ~HB4_AD; - + if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN)) - return n; + return resize_packet(header, n, pheader, plen); /* Complain loudly if the upstream server is non-recursive. */ if (!(header->hb4 & HB4_RA) && RCODE(header) == NOERROR && ntohs(header->ancount) == 0 && @@ -462,16 +615,19 @@ static size_t process_reply(struct dns_header *header, time_t now, if (!option_bool(OPT_LOG)) server->flags |= SERV_WARNED_RECURSIVE; } - + if (daemon->bogus_addr && RCODE(header) != NXDOMAIN && check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now)) { munged = 1; SET_RCODE(header, NXDOMAIN); header->hb3 &= ~HB3_AA; + cache_secure = 0; } else { + int doctored = 0; + if (RCODE(header) == NXDOMAIN && extract_request(header, n, daemon->namebuff, NULL) && check_for_local_domain(daemon->namebuff, now)) @@ -482,15 +638,42 @@ static size_t process_reply(struct dns_header *header, time_t now, munged = 1; header->hb3 |= HB3_AA; SET_RCODE(header, NOERROR); + cache_secure = 0; } - if (extract_addresses(header, n, daemon->namebuff, now, is_sign, check_rebind, checking_disabled)) + if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored)) { my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); munged = 1; + cache_secure = 0; } + + if (doctored) + cache_secure = 0; } +#ifdef HAVE_DNSSEC + if (bogusanswer && !(header->hb4 & HB4_CD)) + { + if (!option_bool(OPT_DNSSEC_DEBUG)) + { + /* Bogus reply, turn into SERVFAIL */ + SET_RCODE(header, SERVFAIL); + munged = 1; + } + } + + if (option_bool(OPT_DNSSEC_VALID)) + header->hb4 &= ~HB4_AD; + + if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure) + header->hb4 |= HB4_AD; + + /* If the requestor didn't set the DO bit, don't return DNSSEC info. */ + if (!do_bit) + n = filter_rrsigs(header, n); +#endif + /* do this after extract_addresses. Ensure NODATA reply and remove nameserver info. */ @@ -499,6 +682,7 @@ static size_t process_reply(struct dns_header *header, time_t now, header->ancount = htons(0); header->nscount = htons(0); header->arcount = htons(0); + header->hb3 &= ~HB3_TC; } /* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide @@ -516,10 +700,14 @@ void reply_query(int fd, int family, time_t now) union mysockaddr serveraddr; struct frec *forward; socklen_t addrlen = sizeof(serveraddr); - ssize_t n = recvfrom(fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen); + ssize_t n = recvfrom(fd, daemon->packet, daemon->packet_buff_sz, 0, &serveraddr.sa, &addrlen); size_t nn; struct server *server; - + void *hash; +#ifndef HAVE_DNSSEC + unsigned int crc; +#endif + /* packet buffer overwritten */ daemon->srv_save = NULL; @@ -530,22 +718,40 @@ void reply_query(int fd, int family, time_t now) serveraddr.in6.sin6_flowinfo = 0; #endif + header = (struct dns_header *)daemon->packet; + + if (n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR)) + return; + /* spoof check: answer must come from known server, */ for (server = daemon->servers; server; server = server->next) if (!(server->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR)) && sockaddr_isequal(&server->addr, &serveraddr)) break; - - header = (struct dns_header *)daemon->packet; - if (!server || - n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR) || - !(forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff)))) + if (!server) return; - - server = forward->sentto; - if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) && +#ifdef HAVE_DNSSEC + hash = hash_questions(header, n, daemon->namebuff); +#else + hash = &crc; + crc = questions_crc(header, n, daemon->namebuff); +#endif + + if (!(forward = lookup_frec(ntohs(header->id), hash))) + return; + + /* log_query gets called indirectly all over the place, so + pass these in global variables - sorry. */ + daemon->log_display_id = forward->log_id; + daemon->log_source_addr = &forward->source; + + if (daemon->ignore_addr && RCODE(header) == NOERROR && + check_for_ignored_address(header, n, daemon->ignore_addr)) + return; + + if (RCODE(header) == REFUSED && !option_bool(OPT_ORDER) && forward->forwardall == 0) /* for broken servers, attempt to send to another one. */ @@ -563,16 +769,18 @@ void reply_query(int fd, int family, time_t now) header->arcount = htons(0); if ((nn = resize_packet(header, (size_t)n, pheader, plen))) { - header->hb3 &= ~(HB3_QR | HB3_TC); - forward_query(-1, NULL, NULL, 0, header, nn, now, forward); + header->hb3 &= ~(HB3_QR | HB3_AA | HB3_TC); + header->hb4 &= ~(HB4_RA | HB4_RCODE); + forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0, 0); return; } } } - + + server = forward->sentto; if ((forward->sentto->flags & SERV_TYPE) == 0) { - if (RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) + if (RCODE(header) == REFUSED) server = NULL; else { @@ -590,24 +798,307 @@ void reply_query(int fd, int family, time_t now) if (!option_bool(OPT_ALL_SERVERS)) daemon->last_server = server; } + + /* We tried resending to this server with a smaller maximum size and got an answer. + Make that permanent. To avoid reduxing the packet size for an single dropped packet, + only do this when we get a truncated answer, or one larger than the safe size. */ + if (server && (forward->flags & FREC_TEST_PKTSZ) && + ((header->hb3 & HB3_TC) || n >= SAFE_PKTSZ)) + server->edns_pktsz = SAFE_PKTSZ; /* If the answer is an error, keep the forward record in place in case we get a good reply from another server. Kill it when we've had replies from all to avoid filling the forwarding table when everything is broken */ - if (forward->forwardall == 0 || --forward->forwardall == 1 || - (RCODE(header) != REFUSED && RCODE(header) != SERVFAIL)) + if (forward->forwardall == 0 || --forward->forwardall == 1 || RCODE(header) != SERVFAIL) { - int check_rebind = !(forward->flags & FREC_NOREBIND); + int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; - if (!option_bool(OPT_NO_REBIND)) - check_rebind = 0; + if (option_bool(OPT_NO_REBIND)) + check_rebind = !(forward->flags & FREC_NOREBIND); + + /* Don't cache replies where DNSSEC validation was turned off, either + the upstream server told us so, or the original query specified it. */ + if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED)) + no_cache_dnssec = 1; - if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED))) +#ifdef HAVE_DNSSEC + if (server && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) + { + int status; + + /* We've had a reply already, which we're validating. Ignore this duplicate */ + if (forward->blocking_query) + return; + + if (header->hb3 & HB3_TC) + { + /* Truncated answer can't be validated. + If this is an answer to a DNSSEC-generated query, we still + need to get the client to retry over TCP, so return + an answer with the TC bit set, even if the actual answer fits. + */ + status = STAT_TRUNCATED; + } + else if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + { + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + /* Provably no DS, everything below is insecure, even if signatures are offered */ + if (status == STAT_NO_DS) + /* We only cache sigs when we've validated a reply. + Avoid caching a reply with sigs if there's a vaildated break in the + DS chain, so we don't return replies from cache missing sigs. */ + status = STAT_INSECURE_DS; + else if (status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + { + status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); + if (status == STAT_INSECURE) + status = STAT_INSECURE_DS; + } + else + status = STAT_INSECURE_DS; + } + else if (status == STAT_NO_NS) + status = STAT_BOGUS; + } + else if (forward->flags & FREC_CHECK_NOSIGN) + { + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + if (status != STAT_NEED_KEY) + status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname); + } + else + { + status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL); + if (status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); + else + status = STAT_INSECURE; + } + } + /* Can't validate, as we're missing key data. Put this + answer aside, whilst we get that. */ + if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY) + { + struct frec *new, *orig; + + /* Free any saved query */ + if (forward->stash) + blockdata_free(forward->stash); + + /* Now save reply pending receipt of key data */ + if (!(forward->stash = blockdata_alloc((char *)header, n))) + return; + forward->stash_len = n; + + anotherkey: + /* Find the original query that started it all.... */ + for (orig = forward; orig->dependent; orig = orig->dependent); + + if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) + status = STAT_INSECURE; + else + { + int fd; + struct frec *next = new->next; + *new = *forward; /* copy everything, then overwrite */ + new->next = next; + new->blocking_query = NULL; + new->sentto = server; + new->rfd4 = NULL; + new->orig_domain = NULL; +#ifdef HAVE_IPV6 + new->rfd6 = NULL; +#endif + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN); + + new->dependent = forward; /* to find query awaiting new one. */ + forward->blocking_query = new; /* for garbage cleaning */ + /* validate routines leave name of required record in daemon->keyname */ + if (status == STAT_NEED_KEY) + { + new->flags |= FREC_DNSKEY_QUERY; + nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz, + daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz); + } + else + { + if (status == STAT_NEED_DS_NEG) + new->flags |= FREC_CHECK_NOSIGN; + else + new->flags |= FREC_DS_QUERY; + nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz, + daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz); + } + if ((hash = hash_questions(header, nn, daemon->namebuff))) + memcpy(new->hash, hash, HASH_SIZE); + new->new_id = get_id(); + header->id = htons(new->new_id); + /* Save query for retransmission */ + if (!(new->stash = blockdata_alloc((char *)header, nn))) + return; + + new->stash_len = nn; + + /* Don't resend this. */ + daemon->srv_save = NULL; + + if (server->sfd) + fd = server->sfd->fd; + else + { + fd = -1; +#ifdef HAVE_IPV6 + if (server->addr.sa.sa_family == AF_INET6) + { + if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) + fd = new->rfd6->fd; + } + else +#endif + { + if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) + fd = new->rfd4->fd; + } + } + + if (fd != -1) + { + while (retry_send(sendto(fd, (char *)header, nn, 0, + &server->addr.sa, + sa_len(&server->addr)))); + server->queries++; + } + + return; + } + } + + /* Ok, we reached far enough up the chain-of-trust that we can validate something. + Now wind back down, pulling back answers which wouldn't previously validate + and validate them with the new data. Note that if an answer needs multiple + keys to validate, we may find another key is needed, in which case we set off + down another branch of the tree. Once we get to the original answer + (FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */ + while (forward->dependent) + { + struct frec *prev = forward->dependent; + free_frec(forward); + forward = prev; + forward->blocking_query = NULL; /* already gone */ + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + n = forward->stash_len; + + if (status == STAT_SECURE) + { + if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + { + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + /* Provably no DS, everything below is insecure, even if signatures are offered */ + if (status == STAT_NO_DS) + /* We only cache sigs when we've validated a reply. + Avoid caching a reply with sigs if there's a vaildated break in the + DS chain, so we don't return replies from cache missing sigs. */ + status = STAT_INSECURE_DS; + else if (status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + { + status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); + if (status == STAT_INSECURE) + status = STAT_INSECURE_DS; + } + else + status = STAT_INSECURE_DS; + } + else if (status == STAT_NO_NS) + status = STAT_BOGUS; + } + else if (forward->flags & FREC_CHECK_NOSIGN) + { + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + if (status != STAT_NEED_KEY) + status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname); + } + else + { + status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL); + if (status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); + else + status = STAT_INSECURE; + } + } + + if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY) + goto anotherkey; + } + } + + no_cache_dnssec = 0; + + if (status == STAT_INSECURE_DS) + { + /* We only cache sigs when we've validated a reply. + Avoid caching a reply with sigs if there's a vaildated break in the + DS chain, so we don't return replies from cache missing sigs. */ + status = STAT_INSECURE; + no_cache_dnssec = 1; + } + + if (status == STAT_TRUNCATED) + header->hb3 |= HB3_TC; + else + { + char *result, *domain = "result"; + + if (forward->work_counter == 0) + { + result = "ABANDONED"; + status = STAT_BOGUS; + } + else + result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + + if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) + domain = daemon->namebuff; + + log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); + } + + if (status == STAT_SECURE) + cache_secure = 1; + else if (status == STAT_BOGUS) + { + no_cache_dnssec = 1; + bogusanswer = 1; + } + } +#endif + + /* restore CD bit to the value in the query */ + if (forward->flags & FREC_CHECKING_DISABLED) + header->hb4 |= HB4_CD; + else + header->hb4 &= ~HB4_CD; + + if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, + forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, + forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source))) { header->id = htons(forward->orig_id); header->hb4 |= HB4_RA; /* recursion if available */ - send_from(forward->fd, option_bool(OPT_NOWILD), daemon->packet, nn, + send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, &forward->source, &forward->dest, forward->iface); } free_frec(forward); /* cancel */ @@ -624,7 +1115,10 @@ void receive_query(struct listener *listen, time_t now) struct in_addr netmask, dst_addr_4; size_t m; ssize_t n; - int if_index = 0; + int if_index = 0, auth_dns = 0; +#ifdef HAVE_AUTH + int local_auth = 0; +#endif struct iovec iov[1]; struct msghdr msg; struct cmsghdr *cmptr; @@ -643,21 +1137,30 @@ void receive_query(struct listener *listen, time_t now) CMSG_SPACE(sizeof(struct sockaddr_dl))]; #endif } control_u; - +#ifdef HAVE_IPV6 + /* Can always get recvd interface for IPv6 */ + int check_dst = !option_bool(OPT_NOWILD) || listen->family == AF_INET6; +#else + int check_dst = !option_bool(OPT_NOWILD); +#endif + /* packet buffer overwritten */ daemon->srv_save = NULL; - if (listen->family == AF_INET && option_bool(OPT_NOWILD)) - { - dst_addr_4 = listen->iface->addr.in.sin_addr; - netmask = listen->iface->netmask; - } - else + dst_addr_4.s_addr = dst_addr.addr.addr4.s_addr = 0; + netmask.s_addr = 0; + + if (option_bool(OPT_NOWILD) && listen->iface) { - dst_addr_4.s_addr = 0; - netmask.s_addr = 0; + auth_dns = listen->iface->dns_auth; + + if (listen->family == AF_INET) + { + dst_addr_4 = dst_addr.addr.addr4 = listen->iface->addr.in.sin_addr; + netmask = listen->iface->netmask; + } } - + iov[0].iov_base = daemon->packet; iov[0].iov_len = daemon->edns_pktsz; @@ -678,12 +1181,61 @@ void receive_query(struct listener *listen, time_t now) return; source_addr.sa.sa_family = listen->family; + + if (listen->family == AF_INET) + { + /* Source-port == 0 is an error, we can't send back to that. + http://www.ietf.org/mail-archive/web/dnsop/current/msg11441.html */ + if (source_addr.in.sin_port == 0) + return; + } #ifdef HAVE_IPV6 - if (listen->family == AF_INET6) - source_addr.in6.sin6_flowinfo = 0; + else + { + /* Source-port == 0 is an error, we can't send back to that. */ + if (source_addr.in6.sin6_port == 0) + return; + source_addr.in6.sin6_flowinfo = 0; + } #endif - - if (!option_bool(OPT_NOWILD)) + + /* We can be configured to only accept queries from at-most-one-hop-away addresses. */ + if (option_bool(OPT_LOCAL_SERVICE)) + { + struct addrlist *addr; +#ifdef HAVE_IPV6 + if (listen->family == AF_INET6) + { + for (addr = daemon->interface_addrs; addr; addr = addr->next) + if ((addr->flags & ADDRLIST_IPV6) && + is_same_net6(&addr->addr.addr.addr6, &source_addr.in6.sin6_addr, addr->prefixlen)) + break; + } + else +#endif + { + struct in_addr netmask; + for (addr = daemon->interface_addrs; addr; addr = addr->next) + { + netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen)); + if (!(addr->flags & ADDRLIST_IPV6) && + is_same_net(addr->addr.addr.addr4, source_addr.in.sin_addr, netmask)) + break; + } + } + if (!addr) + { + static int warned = 0; + if (!warned) + { + my_syslog(LOG_WARNING, _("Ignoring query from non-local network")); + warned = 1; + } + return; + } + } + + if (check_dst) { struct ifreq ifr; @@ -693,7 +1245,7 @@ void receive_query(struct listener *listen, time_t now) #if defined(HAVE_LINUX_NETWORK) if (listen->family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) - if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { union { unsigned char *c; @@ -733,7 +1285,7 @@ void receive_query(struct listener *listen, time_t now) if (listen->family == AF_INET6) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) - if (cmptr->cmsg_level == IPV6_LEVEL && cmptr->cmsg_type == daemon->v6pktinfo) + if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) { union { unsigned char *c; @@ -749,24 +1301,59 @@ void receive_query(struct listener *listen, time_t now) /* enforce available interface configuration */ - if (!indextoname(listen->fd, if_index, ifr.ifr_name) || - !iface_check(listen->family, &dst_addr, ifr.ifr_name, &if_index)) - return; - - if (listen->family == AF_INET && - option_bool(OPT_LOCALISE) && - ioctl(listen->fd, SIOCGIFNETMASK, &ifr) == -1) + if (!indextoname(listen->fd, if_index, ifr.ifr_name)) return; - netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + if (!iface_check(listen->family, &dst_addr, ifr.ifr_name, &auth_dns)) + { + if (!option_bool(OPT_CLEVERBIND)) + enumerate_interfaces(0); + if (!loopback_exception(listen->fd, listen->family, &dst_addr, ifr.ifr_name) && + !label_exception(if_index, listen->family, &dst_addr)) + return; + } + + if (listen->family == AF_INET && option_bool(OPT_LOCALISE)) + { + struct irec *iface; + + /* get the netmask of the interface whch has the address we were sent to. + This is no neccessarily the interface we arrived on. */ + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->addr.sa.sa_family == AF_INET && + iface->addr.in.sin_addr.s_addr == dst_addr_4.s_addr) + break; + + /* interface may be new */ + if (!iface && !option_bool(OPT_CLEVERBIND)) + enumerate_interfaces(0); + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->addr.sa.sa_family == AF_INET && + iface->addr.in.sin_addr.s_addr == dst_addr_4.s_addr) + break; + + /* If we failed, abandon localisation */ + if (iface) + netmask = iface->netmask; + else + dst_addr_4.s_addr = 0; + } } + + /* log_query gets called indirectly all over the place, so + pass these in global variables - sorry. */ + daemon->log_display_id = ++daemon->log_id; + daemon->log_source_addr = &source_addr; if (extract_request(header, (size_t)n, daemon->namebuff, &type)) { - char types[20]; - - querystr(types, type); - +#ifdef HAVE_AUTH + struct auth_zone *zone; +#endif + char *types = querystr(auth_dns ? "auth" : "query", type); + if (listen->family == AF_INET) log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, (struct all_addr *)&source_addr.in.sin_addr, types); @@ -775,206 +1362,801 @@ void receive_query(struct listener *listen, time_t now) log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, (struct all_addr *)&source_addr.in6.sin6_addr, types); #endif - } - m = answer_request (header, ((char *) header) + PACKETSZ, (size_t)n, - dst_addr_4, netmask, now); - if (m >= 1) +#ifdef HAVE_AUTH + /* find queries for zones we're authoritative for, and answer them directly */ + if (!auth_dns) + for (zone = daemon->auth_zones; zone; zone = zone->next) + if (in_zone(zone, daemon->namebuff, NULL)) + { + auth_dns = 1; + local_auth = 1; + break; + } +#endif + +#ifdef HAVE_LOOP + /* Check for forwarding loop */ + if (detect_loop(daemon->namebuff, type)) + return; +#endif + } + +#ifdef HAVE_AUTH + if (auth_dns) { - send_from(listen->fd, option_bool(OPT_NOWILD), (char *)header, - m, &source_addr, &dst_addr, if_index); - daemon->local_answer++; + m = answer_auth(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, now, &source_addr, local_auth); + if (m >= 1) + { + send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), + (char *)header, m, &source_addr, &dst_addr, if_index); + daemon->auth_answer++; + } } - else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, - header, (size_t)n, now, NULL)) - daemon->queries_forwarded++; else - daemon->local_answer++; +#endif + { + int ad_reqd, do_bit; + m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, + dst_addr_4, netmask, now, &ad_reqd, &do_bit); + + if (m >= 1) + { + send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), + (char *)header, m, &source_addr, &dst_addr, if_index); + daemon->local_answer++; + } + else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, + header, (size_t)n, now, NULL, ad_reqd, do_bit)) + daemon->queries_forwarded++; + else + daemon->local_answer++; + } +} + +#ifdef HAVE_DNSSEC + +/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS + and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or + STAT_NEED_DS_NEG and keyname if we need to do the query. */ +static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, + char *name, char *keyname) +{ + int status = dnssec_chase_cname(now, header, plen, name, keyname); + + if (status != STAT_INSECURE) + return status; + + /* Store the domain we're trying to check. */ + forward->name_start = strlen(name); + forward->name_len = forward->name_start + 1; + if (!(forward->orig_domain = blockdata_alloc(name, forward->name_len))) + return STAT_BOGUS; + + return do_check_sign(forward, 0, now, name, keyname); } + +/* We either have a a reply (header non-NULL, or we need to start by looking in the cache */ +static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname) +{ + /* get domain we're checking back from blockdata store, it's stored on the original query. */ + while (forward->dependent && !forward->orig_domain) + forward = forward->dependent; + + blockdata_retrieve(forward->orig_domain, forward->name_len, name); + + while (1) + { + char *p; + + if (status == 0) + { + struct crec *crecp; + + /* Haven't received answer, see if in cache */ + if (!(crecp = cache_find_by_name(NULL, &name[forward->name_start], now, F_DS))) + { + /* put name of DS record we're missing into keyname */ + strcpy(keyname, &name[forward->name_start]); + /* and wait for reply to arrive */ + return STAT_NEED_DS_NEG; + } + + /* F_DNSSECOK misused in DS cache records to non-existance of NS record */ + if (!(crecp->flags & F_NEG)) + status = STAT_SECURE; + else if (crecp->flags & F_DNSSECOK) + status = STAT_NO_DS; + else + status = STAT_NO_NS; + } + + /* Have entered non-signed part of DNS tree. */ + if (status == STAT_NO_DS) + return forward->dependent ? STAT_INSECURE_DS : STAT_INSECURE; + + if (status == STAT_BOGUS) + return STAT_BOGUS; + + if (status == STAT_NO_SIG && *keyname != 0) + { + /* There is a validated CNAME chain that doesn't end in a DS record. Start + the search again in that domain. */ + blockdata_free(forward->orig_domain); + forward->name_start = strlen(keyname); + forward->name_len = forward->name_start + 1; + if (!(forward->orig_domain = blockdata_alloc(keyname, forward->name_len))) + return STAT_BOGUS; + + strcpy(name, keyname); + status = 0; /* force to cache when we iterate. */ + continue; + } + + /* There's a proven DS record, or we're within a zone, where there doesn't need + to be a DS record. Add a name and try again. + If we've already tried the whole name, then fail */ + + if (forward->name_start == 0) + return STAT_BOGUS; + + for (p = &name[forward->name_start-2]; (*p != '.') && (p != name); p--); + + if (p != name) + p++; + + forward->name_start = p - name; + status = 0; /* force to cache when we iterate. */ + } +} + +/* Move down from the root, until we find a signed non-existance of a DS, in which case + an unsigned answer is OK, or we find a signed DS, in which case there should be + a signature, and the answer is BOGUS */ +static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name, + char *keyname, struct server *server, int *keycount) +{ + size_t m; + unsigned char *packet, *payload; + u16 *length; + int status, name_len; + struct blockdata *block; + + char *name_start; + + /* Get first insecure entry in CNAME chain */ + status = tcp_key_recurse(now, STAT_CHASE_CNAME, header, plen, class, name, keyname, server, keycount); + if (status == STAT_BOGUS) + return STAT_BOGUS; + + if (!(packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)))) + return STAT_BOGUS; + + payload = &packet[2]; + header = (struct dns_header *)payload; + length = (u16 *)packet; + + /* Stash the name away, since the buffer will be trashed when we recurse */ + name_len = strlen(name) + 1; + name_start = name + name_len - 1; + + if (!(block = blockdata_alloc(name, name_len))) + { + free(packet); + return STAT_BOGUS; + } + + while (1) + { + unsigned char c1, c2; + struct crec *crecp; + + if (--(*keycount) == 0) + { + free(packet); + blockdata_free(block); + return STAT_BOGUS; + } + + while ((crecp = cache_find_by_name(NULL, name_start, now, F_DS))) + { + if ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK)) + { + /* Found a secure denial of DS - delegation is indeed insecure */ + free(packet); + blockdata_free(block); + return STAT_INSECURE; + } + + /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation. + Add another label and continue. */ + + if (name_start == name) + { + free(packet); + blockdata_free(block); + return STAT_BOGUS; /* run out of labels */ + } + + name_start -= 2; + while (*name_start != '.' && name_start != name) + name_start--; + if (name_start != name) + name_start++; + } + + /* Can't find it in the cache, have to send a query */ + + m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz); + + *length = htons(m); + + if (read_write(server->tcpfd, packet, m + sizeof(u16), 0) && + read_write(server->tcpfd, &c1, 1, 1) && + read_write(server->tcpfd, &c2, 1, 1) && + read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) + { + m = (c1 << 8) | c2; + + /* Note this trashes all three name workspaces */ + status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount); + + if (status == STAT_NO_DS) + { + /* Found a secure denial of DS - delegation is indeed insecure */ + free(packet); + blockdata_free(block); + return STAT_INSECURE; + } + + if (status == STAT_NO_SIG && *keyname != 0) + { + /* There is a validated CNAME chain that doesn't end in a DS record. Start + the search again in that domain. */ + blockdata_free(block); + name_len = strlen(keyname) + 1; + name_start = name + name_len - 1; + + if (!(block = blockdata_alloc(keyname, name_len))) + return STAT_BOGUS; + + strcpy(name, keyname); + continue; + } + + if (status == STAT_BOGUS) + { + free(packet); + blockdata_free(block); + return STAT_BOGUS; + } + + /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation. + Add another label and continue. */ + + /* Get name we're checking back. */ + blockdata_retrieve(block, name_len, name); + + if (name_start == name) + { + free(packet); + blockdata_free(block); + return STAT_BOGUS; /* run out of labels */ + } + + name_start -= 2; + while (*name_start != '.' && name_start != name) + name_start--; + if (name_start != name) + name_start++; + } + else + { + /* IO failure */ + free(packet); + blockdata_free(block); + return STAT_BOGUS; /* run out of labels */ + } + } +} + +static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, + int class, char *name, char *keyname, struct server *server, int *keycount) +{ + /* Recurse up the key heirarchy */ + int new_status; + + /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ + if (--(*keycount) == 0) + return STAT_INSECURE; + + if (status == STAT_NEED_KEY) + new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); + else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG) + { + new_status = dnssec_validate_ds(now, header, n, name, keyname, class); + if (status == STAT_NEED_DS) + { + if (new_status == STAT_NO_DS) + new_status = STAT_INSECURE_DS; + if (new_status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + { + new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); + if (new_status == STAT_INSECURE) + new_status = STAT_INSECURE_DS; + } + else + new_status = STAT_INSECURE_DS; + } + else if (new_status == STAT_NO_NS) + new_status = STAT_BOGUS; + } + } + else if (status == STAT_CHASE_CNAME) + new_status = dnssec_chase_cname(now, header, n, name, keyname); + else + { + new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL); + + if (new_status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); + else + new_status = STAT_INSECURE; + } + } + + /* Can't validate because we need a key/DS whose name now in keyname. + Make query for same, and recurse to validate */ + if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY) + { + size_t m; + unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); + unsigned char *payload = &packet[2]; + struct dns_header *new_header = (struct dns_header *)payload; + u16 *length = (u16 *)packet; + unsigned char c1, c2; + + if (!packet) + return STAT_INSECURE; + + another_tcp_key: + m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class, + new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz); + + *length = htons(m); + + if (!read_write(server->tcpfd, packet, m + sizeof(u16), 0) || + !read_write(server->tcpfd, &c1, 1, 1) || + !read_write(server->tcpfd, &c2, 1, 1) || + !read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) + new_status = STAT_INSECURE; + else + { + m = (c1 << 8) | c2; + + new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount); + + if (new_status == STAT_SECURE) + { + /* Reached a validated record, now try again at this level. + Note that we may get ANOTHER NEED_* if an answer needs more than one key. + If so, go round again. */ + + if (status == STAT_NEED_KEY) + new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); + else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG) + { + new_status = dnssec_validate_ds(now, header, n, name, keyname, class); + if (status == STAT_NEED_DS) + { + if (new_status == STAT_NO_DS) + new_status = STAT_INSECURE_DS; + else if (new_status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + { + new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); + if (new_status == STAT_INSECURE) + new_status = STAT_INSECURE_DS; + } + else + new_status = STAT_INSECURE_DS; + } + else if (new_status == STAT_NO_NS) + new_status = STAT_BOGUS; + } + } + else if (status == STAT_CHASE_CNAME) + new_status = dnssec_chase_cname(now, header, n, name, keyname); + else + { + new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL); + + if (new_status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); + else + new_status = STAT_INSECURE; + } + } + + if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY) + goto another_tcp_key; + } + } + + free(packet); + } + return new_status; +} +#endif + /* The daemon forks before calling this: it should deal with one connection, blocking as neccessary, and then return. Note, need to be a bit careful about resources for debug mode, when the fork is suppressed: that's done by the caller. */ unsigned char *tcp_request(int confd, time_t now, - struct in_addr local_addr, struct in_addr netmask) + union mysockaddr *local_addr, struct in_addr netmask, int auth_dns) { size_t size = 0; int norebind = 0; - int checking_disabled; +#ifdef HAVE_AUTH + int local_auth = 0; +#endif + int checking_disabled, ad_question, do_bit, added_pheader = 0; + int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; size_t m; - unsigned short qtype, gotname; + unsigned short qtype; + unsigned int gotname; unsigned char c1, c2; - /* Max TCP packet + slop */ - unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ); - struct dns_header *header; + /* Max TCP packet + slop + size */ + unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); + unsigned char *payload = &packet[2]; + /* largest field in header is 16-bits, so this is still sufficiently aligned */ + struct dns_header *header = (struct dns_header *)payload; + u16 *length = (u16 *)packet; struct server *last_server; + struct in_addr dst_addr_4; + union mysockaddr peer_addr; + socklen_t peer_len = sizeof(union mysockaddr); + int query_count = 0; + + if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1) + return packet; + /* We can be configured to only accept queries from at-most-one-hop-away addresses. */ + if (option_bool(OPT_LOCAL_SERVICE)) + { + struct addrlist *addr; +#ifdef HAVE_IPV6 + if (peer_addr.sa.sa_family == AF_INET6) + { + for (addr = daemon->interface_addrs; addr; addr = addr->next) + if ((addr->flags & ADDRLIST_IPV6) && + is_same_net6(&addr->addr.addr.addr6, &peer_addr.in6.sin6_addr, addr->prefixlen)) + break; + } + else +#endif + { + struct in_addr netmask; + for (addr = daemon->interface_addrs; addr; addr = addr->next) + { + netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen)); + if (!(addr->flags & ADDRLIST_IPV6) && + is_same_net(addr->addr.addr.addr4, peer_addr.in.sin_addr, netmask)) + break; + } + } + if (!addr) + { + my_syslog(LOG_WARNING, _("Ignoring query from non-local network")); + return packet; + } + } + while (1) { - if (!packet || + if (query_count == TCP_MAX_QUERIES || + !packet || !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) || !(size = c1 << 8 | c2) || - !read_write(confd, packet, size, 1)) + !read_write(confd, payload, size, 1)) return packet; if (size < (int)sizeof(struct dns_header)) continue; - header = (struct dns_header *)packet; + query_count++; + + /* log_query gets called indirectly all over the place, so + pass these in global variables - sorry. */ + daemon->log_display_id = ++daemon->log_id; + daemon->log_source_addr = &peer_addr; + + check_subnet = 0; /* save state of "cd" flag in query */ - checking_disabled = header->hb4 & HB4_CD; + if ((checking_disabled = header->hb4 & HB4_CD)) + no_cache_dnssec = 1; - /* RFC 4035: sect 4.6 para 2 */ - header->hb4 &= ~HB4_AD; - if ((gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) { - union mysockaddr peer_addr; - socklen_t peer_len = sizeof(union mysockaddr); +#ifdef HAVE_AUTH + struct auth_zone *zone; +#endif + char *types = querystr(auth_dns ? "auth" : "query", qtype); - if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) != -1) - { - char types[20]; - - querystr(types, qtype); - - if (peer_addr.sa.sa_family == AF_INET) - log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&peer_addr.in.sin_addr, types); + if (peer_addr.sa.sa_family == AF_INET) + log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&peer_addr.in.sin_addr, types); #ifdef HAVE_IPV6 - else - log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&peer_addr.in6.sin6_addr, types); + else + log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&peer_addr.in6.sin6_addr, types); +#endif + +#ifdef HAVE_AUTH + /* find queries for zones we're authoritative for, and answer them directly */ + if (!auth_dns) + for (zone = daemon->auth_zones; zone; zone = zone->next) + if (in_zone(zone, daemon->namebuff, NULL)) + { + auth_dns = 1; + local_auth = 1; + break; + } #endif - } } - /* m > 0 if answered from cache */ - m = answer_request(header, ((char *) header) + 65536, (unsigned int)size, - local_addr, netmask, now); - - /* Do this by steam now we're not in the select() loop */ - check_log_writer(NULL); + if (local_addr->sa.sa_family == AF_INET) + dst_addr_4 = local_addr->in.sin_addr; + else + dst_addr_4.s_addr = 0; - if (m == 0) +#ifdef HAVE_AUTH + if (auth_dns) + m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, local_auth); + else +#endif { - unsigned int flags = 0; - struct all_addr *addrp = NULL; - int type = 0; - char *domain = NULL; - - if (option_bool(OPT_ADD_MAC)) + /* m > 0 if answered from cache */ + m = answer_request(header, ((char *) header) + 65536, (size_t)size, + dst_addr_4, netmask, now, &ad_question, &do_bit); + + /* Do this by steam now we're not in the select() loop */ + check_log_writer(1); + + if (m == 0) { - union mysockaddr peer_addr; - socklen_t peer_len = sizeof(union mysockaddr); + unsigned int flags = 0; + struct all_addr *addrp = NULL; + int type = 0; + char *domain = NULL; - if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) != -1) + if (option_bool(OPT_ADD_MAC)) size = add_mac(header, size, ((char *) header) + 65536, &peer_addr); - } - - if (gotname) - flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); - - if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) - last_server = daemon->servers; - else - last_server = daemon->last_server; - - if (!flags && last_server) - { - struct server *firstsendto = NULL; - unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); - - /* Loop round available servers until we succeed in connecting to one. - Note that this code subtley ensures that consecutive queries on this connection - which can go to the same server, do so. */ - while (1) - { - if (!firstsendto) - firstsendto = last_server; - else + + if (option_bool(OPT_CLIENT_SUBNET)) + { + size_t new = add_source_addr(header, size, ((char *) header) + 65536, &peer_addr); + if (size != new) { - if (!(last_server = last_server->next)) - last_server = daemon->servers; - - if (last_server == firstsendto) - break; + size = new; + check_subnet = 1; } + } + + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); - /* server for wrong domain */ - if (type != (last_server->flags & SERV_TYPE) || - (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain))) - continue; - - if ((last_server->tcpfd == -1) && - (last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) != -1 && - (!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 1) || - connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) + if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) + last_server = daemon->servers; + else + last_server = daemon->last_server; + + if (!flags && last_server) + { + struct server *firstsendto = NULL; +#ifdef HAVE_DNSSEC + unsigned char *newhash, hash[HASH_SIZE]; + if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff))) + memcpy(hash, newhash, HASH_SIZE); + else + memset(hash, 0, HASH_SIZE); +#else + unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); +#endif + /* Loop round available servers until we succeed in connecting to one. + Note that this code subtley ensures that consecutive queries on this connection + which can go to the same server, do so. */ + while (1) { - close(last_server->tcpfd); - last_server->tcpfd = -1; - } - - if (last_server->tcpfd == -1) - continue; + if (!firstsendto) + firstsendto = last_server; + else + { + if (!(last_server = last_server->next)) + last_server = daemon->servers; + + if (last_server == firstsendto) + break; + } + + /* server for wrong domain */ + if (type != (last_server->flags & SERV_TYPE) || + (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) || + (last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) + continue; + + if (last_server->tcpfd == -1) + { + if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) + continue; + +#ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; + struct all_addr local; +#ifdef HAVE_IPV6 + if (local_addr->sa.sa_family == AF_INET6) + local.addr.addr6 = local_addr->in6.sin6_addr; + else +#endif + local.addr.addr4 = local_addr->in.sin_addr; + + if (get_incoming_mark(&peer_addr, &local, 1, &mark)) + setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } +#endif + + if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 1) || + connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + continue; + } + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + size_t new_size = add_do_bit(header, size, ((char *) header) + 65536); + + /* For debugging, set Checking Disabled, otherwise, have the upstream check too, + this allows it to select auth servers when one is returning bad data. */ + if (option_bool(OPT_DNSSEC_DEBUG)) + header->hb4 |= HB4_CD; + + if (size != new_size) + added_pheader = 1; + + size = new_size; + } +#endif + } + + *length = htons(size); - c1 = size >> 8; - c2 = size; - - if (!read_write(last_server->tcpfd, &c1, 1, 0) || - !read_write(last_server->tcpfd, &c2, 1, 0) || - !read_write(last_server->tcpfd, packet, size, 0) || - !read_write(last_server->tcpfd, &c1, 1, 1) || - !read_write(last_server->tcpfd, &c2, 1, 1)) - { - close(last_server->tcpfd); - last_server->tcpfd = -1; - continue; - } - - m = (c1 << 8) | c2; - if (!read_write(last_server->tcpfd, packet, m, 1)) - return packet; - - if (!gotname) - strcpy(daemon->namebuff, "query"); - if (last_server->addr.sa.sa_family == AF_INET) - log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&last_server->addr.in.sin_addr, NULL); + /* get query name again for logging - may have been overwritten */ + if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) + strcpy(daemon->namebuff, "query"); + + if (!read_write(last_server->tcpfd, packet, size + sizeof(u16), 0) || + !read_write(last_server->tcpfd, &c1, 1, 1) || + !read_write(last_server->tcpfd, &c2, 1, 1) || + !read_write(last_server->tcpfd, payload, (c1 << 8) | c2, 1)) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + continue; + } + + m = (c1 << 8) | c2; + + if (last_server->addr.sa.sa_family == AF_INET) + log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&last_server->addr.in.sin_addr, NULL); #ifdef HAVE_IPV6 - else - log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); + else + log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); #endif - - /* There's no point in updating the cache, since this process will exit and - lose the information after a few queries. We make this call for the alias and - bogus-nxdomain side-effects. */ - /* If the crc of the question section doesn't match the crc we sent, then - someone might be attempting to insert bogus values into the cache by - sending replies containing questions and bogus answers. */ - if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff)) - m = process_reply(header, now, last_server, (unsigned int)m, - option_bool(OPT_NO_REBIND) && !norebind, checking_disabled); - - break; + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled) + { + int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ + int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount); + char *result, *domain = "result"; + + if (status == STAT_INSECURE_DS) + { + /* We only cache sigs when we've validated a reply. + Avoid caching a reply with sigs if there's a vaildated break in the + DS chain, so we don't return replies from cache missing sigs. */ + status = STAT_INSECURE; + no_cache_dnssec = 1; + } + + if (keycount == 0) + { + result = "ABANDONED"; + status = STAT_BOGUS; + } + else + result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + + if (status == STAT_BOGUS && extract_request(header, m, daemon->namebuff, NULL)) + domain = daemon->namebuff; + + log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); + + if (status == STAT_BOGUS) + { + no_cache_dnssec = 1; + bogusanswer = 1; + } + + if (status == STAT_SECURE) + cache_secure = 1; + } +#endif + + /* restore CD bit to the value in the query */ + if (checking_disabled) + header->hb4 |= HB4_CD; + else + header->hb4 &= ~HB4_CD; + + /* There's no point in updating the cache, since this process will exit and + lose the information after a few queries. We make this call for the alias and + bogus-nxdomain side-effects. */ + /* If the crc of the question section doesn't match the crc we sent, then + someone might be attempting to insert bogus values into the cache by + sending replies containing questions and bogus answers. */ +#ifdef HAVE_DNSSEC + newhash = hash_questions(header, (unsigned int)m, daemon->namebuff); + if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0) + { + m = 0; + break; + } +#else + if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff)) + { + m = 0; + break; + } +#endif + + m = process_reply(header, now, last_server, (unsigned int)m, + option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, + ad_question, do_bit, added_pheader, check_subnet, &peer_addr); + + break; + } } + + /* In case of local answer or no connections made. */ + if (m == 0) + m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); } - - /* In case of local answer or no connections made. */ - if (m == 0) - m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); } - - check_log_writer(NULL); + + check_log_writer(1); - c1 = m>>8; - c2 = m; - if (!read_write(confd, &c1, 1, 0) || - !read_write(confd, &c2, 1, 0) || - !read_write(confd, packet, m, 0)) + *length = htons(m); + + if (m == 0 || !read_write(confd, packet, m + sizeof(u16), 0)) return packet; } } @@ -993,13 +2175,19 @@ static struct frec *allocate_frec(time_t now) #ifdef HAVE_IPV6 f->rfd6 = NULL; #endif +#ifdef HAVE_DNSSEC + f->dependent = NULL; + f->blocking_query = NULL; + f->stash = NULL; + f->orig_domain = NULL; +#endif daemon->frec_list = f; } return f; } -static struct randfd *allocate_rfd(int family) +struct randfd *allocate_rfd(int family) { static int finger = 0; int i; @@ -1035,28 +2223,52 @@ static struct randfd *allocate_rfd(int family) return NULL; /* doom */ } +void free_rfd(struct randfd *rfd) +{ + if (rfd && --(rfd->refcount) == 0) + close(rfd->fd); +} + static void free_frec(struct frec *f) { - if (f->rfd4 && --(f->rfd4->refcount) == 0) - close(f->rfd4->fd); - + free_rfd(f->rfd4); f->rfd4 = NULL; f->sentto = NULL; f->flags = 0; #ifdef HAVE_IPV6 - if (f->rfd6 && --(f->rfd6->refcount) == 0) - close(f->rfd6->fd); - + free_rfd(f->rfd6); f->rfd6 = NULL; #endif + +#ifdef HAVE_DNSSEC + if (f->stash) + { + blockdata_free(f->stash); + f->stash = NULL; + } + + if (f->orig_domain) + { + blockdata_free(f->orig_domain); + f->orig_domain = NULL; + } + + /* Anything we're waiting on is pointless now, too */ + if (f->blocking_query) + free_frec(f->blocking_query); + f->blocking_query = NULL; + f->dependent = NULL; +#endif } /* if wait==NULL return a free or older than TIMEOUT record. else return *wait zero if one available, or *wait is delay to when the oldest in-use record will expire. Impose an absolute - limit of 4*TIMEOUT before we wipe things (for random sockets) */ -struct frec *get_new_frec(time_t now, int *wait) + limit of 4*TIMEOUT before we wipe things (for random sockets). + If force is set, always return a result, even if we have + to allocate above the limit. */ +struct frec *get_new_frec(time_t now, int *wait, int force) { struct frec *f, *oldest, *target; int count; @@ -1105,10 +2317,19 @@ struct frec *get_new_frec(time_t now, int *wait) } /* none available, calculate time 'till oldest record expires */ - if (count > daemon->ftabsize) + if (!force && count > daemon->ftabsize) { + static time_t last_log = 0; + if (oldest && wait) *wait = oldest->time + (time_t)TIMEOUT - now; + + if ((int)difftime(now, last_log) > 5) + { + last_log = now; + my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize); + } + return NULL; } @@ -1120,13 +2341,13 @@ struct frec *get_new_frec(time_t now, int *wait) } /* crc is all-ones if not known. */ -static struct frec *lookup_frec(unsigned short id, unsigned int crc) +static struct frec *lookup_frec(unsigned short id, void *hash) { struct frec *f; for(f = daemon->frec_list; f; f = f->next) if (f->sentto && f->new_id == id && - (f->crc == crc || crc == 0xffffffff)) + (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0)) return f; return NULL; @@ -1134,19 +2355,39 @@ static struct frec *lookup_frec(unsigned short id, unsigned int crc) static struct frec *lookup_frec_by_sender(unsigned short id, union mysockaddr *addr, - unsigned int crc) + void *hash) { struct frec *f; for(f = daemon->frec_list; f; f = f->next) if (f->sentto && f->orig_id == id && - f->crc == crc && + memcmp(hash, f->hash, HASH_SIZE) == 0 && sockaddr_isequal(&f->source, addr)) return f; return NULL; } + +/* Send query packet again, if we can. */ +void resend_query() +{ + if (daemon->srv_save) + { + int fd; + + if (daemon->srv_save->sfd) + fd = daemon->srv_save->sfd->fd; + else if (daemon->rfd_save && daemon->rfd_save->refcount != 0) + fd = daemon->rfd_save->fd; + else + return; + + while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0, + &daemon->srv_save->addr.sa, + sa_len(&daemon->srv_save->addr)))); + } +} /* A server record is going away, remove references to it */ void server_gone(struct server *server) @@ -1165,13 +2406,13 @@ void server_gone(struct server *server) } /* return unique random ids. */ -static unsigned short get_id(unsigned int crc) +static unsigned short get_id(void) { unsigned short ret = 0; do ret = rand16(); - while (lookup_frec(ret, crc)); + while (lookup_frec(ret, NULL)); return ret; } diff --git a/src/helper.c b/src/helper.c index 93f99f0..1fee72d 100644 --- a/src/helper.c +++ b/src/helper.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 @@ -16,6 +16,8 @@ #include "dnsmasq.h" +#ifdef HAVE_SCRIPT + /* This file has code to fork a helper process which recieves data via a pipe shared with the main process and which is responsible for calling a script when DHCP leases change. @@ -28,15 +30,30 @@ main process. */ -#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) - static void my_setenv(const char *name, const char *value, int *error); static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, char *env, int *err); +#ifdef HAVE_LUASCRIPT +#define LUA_COMPAT_ALL +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> + +#ifndef lua_open +#define lua_open() luaL_newstate() +#endif + +lua_State *lua; + +static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end, char *field); +#endif + + struct script_data { - unsigned char action, hwaddr_len, hwaddr_type; - unsigned char clid_len, hostname_len, ed_len; + int flags; + int action, hwaddr_len, hwaddr_type; + int clid_len, hostname_len, ed_len; struct in_addr addr, giaddr; unsigned int remaining_time; #ifdef HAVE_BROKEN_RTC @@ -44,6 +61,15 @@ struct script_data #else time_t expires; #endif +#ifdef HAVE_TFTP + off_t file_len; +#endif +#ifdef HAVE_IPV6 + struct in6_addr addr6; +#endif +#ifdef HAVE_DHCP6 + int iaid, vendorclass_count; +#endif unsigned char hwaddr[DHCP_CHADDR_MAX]; char interface[IF_NAMESIZE]; }; @@ -61,7 +87,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) then fork our process. */ if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1) { - send_event(err_fd, EVENT_PIPE_ERR, errno); + send_event(err_fd, EVENT_PIPE_ERR, errno, NULL); _exit(0); } @@ -88,37 +114,99 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) { if (option_bool(OPT_NO_FORK)) /* send error to daemon process if no-fork */ - send_event(event_fd, EVENT_HUSER_ERR, errno); + send_event(event_fd, EVENT_USER_ERR, errno, daemon->scriptuser); else { /* kill daemon */ - send_event(event_fd, EVENT_DIE, 0); + send_event(event_fd, EVENT_DIE, 0, NULL); /* return error */ - send_event(err_fd, EVENT_HUSER_ERR, errno); + send_event(err_fd, EVENT_USER_ERR, errno, daemon->scriptuser); } _exit(0); } } - /* close all the sockets etc, we don't need them here. This closes err_fd, so that - main process can return. */ + /* close all the sockets etc, we don't need them here. + Don't close err_fd, in case the lua-init fails. + Note that we have to do this before lua init + so we don't close any lua fds. */ for (max_fd--; max_fd >= 0; max_fd--) if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && - max_fd != STDIN_FILENO && max_fd != pipefd[0] && max_fd != event_fd) + max_fd != STDIN_FILENO && max_fd != pipefd[0] && + max_fd != event_fd && max_fd != err_fd) close(max_fd); +#ifdef HAVE_LUASCRIPT + if (daemon->luascript) + { + const char *lua_err = NULL; + lua = lua_open(); + luaL_openlibs(lua); + + /* get Lua to load our script file */ + if (luaL_dofile(lua, daemon->luascript) != 0) + lua_err = lua_tostring(lua, -1); + else + { + lua_getglobal(lua, "lease"); + if (lua_type(lua, -1) != LUA_TFUNCTION) + lua_err = _("lease() function missing in Lua script"); + } + + if (lua_err) + { + if (option_bool(OPT_NO_FORK) || option_bool(OPT_DEBUG)) + /* send error to daemon process if no-fork */ + send_event(event_fd, EVENT_LUA_ERR, 0, (char *)lua_err); + else + { + /* kill daemon */ + send_event(event_fd, EVENT_DIE, 0, NULL); + /* return error */ + send_event(err_fd, EVENT_LUA_ERR, 0, (char *)lua_err); + } + _exit(0); + } + + lua_pop(lua, 1); /* remove nil from stack */ + lua_getglobal(lua, "init"); + if (lua_type(lua, -1) == LUA_TFUNCTION) + lua_call(lua, 0, 0); + else + lua_pop(lua, 1); /* remove nil from stack */ + } +#endif + + /* All init done, close our copy of the error pipe, so that main process can return */ + if (err_fd != -1) + close(err_fd); + /* loop here */ while(1) { struct script_data data; - char *p, *action_str, *hostname = NULL; + char *p, *action_str, *hostname = NULL, *domain = NULL; unsigned char *buf = (unsigned char *)daemon->namebuff; - unsigned char *end, *alloc_buff = NULL; - int err = 0; + unsigned char *end, *extradata, *alloc_buff = NULL; + int is6, err = 0; + free(alloc_buff); + /* we read zero bytes when pipe closed: this is our signal to exit */ if (!read_write(pipefd[0], (unsigned char *)&data, sizeof(data), 1)) - _exit(0); + { +#ifdef HAVE_LUASCRIPT + if (daemon->luascript) + { + lua_getglobal(lua, "shutdown"); + if (lua_type(lua, -1) == LUA_TFUNCTION) + lua_call(lua, 0, 0); + } +#endif + _exit(0); + } + + is6 = !!(data.flags & (LEASE_TA | LEASE_NA)); if (data.action == ACTION_DEL) action_str = "del"; @@ -126,55 +214,244 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) action_str = "add"; else if (data.action == ACTION_OLD || data.action == ACTION_OLD_HOSTNAME) action_str = "old"; + else if (data.action == ACTION_TFTP) + { + action_str = "tftp"; + is6 = (data.flags != AF_INET); + } else continue; - + + /* stringify MAC into dhcp_buff */ p = daemon->dhcp_buff; if (data.hwaddr_type != ARPHRD_ETHER || data.hwaddr_len == 0) - p += sprintf(p, "%.2x-", data.hwaddr_type); + p += sprintf(p, "%.2x-", data.hwaddr_type); for (i = 0; (i < data.hwaddr_len) && (i < DHCP_CHADDR_MAX); i++) - { - p += sprintf(p, "%.2x", data.hwaddr[i]); - if (i != data.hwaddr_len - 1) - p += sprintf(p, ":"); - } + { + p += sprintf(p, "%.2x", data.hwaddr[i]); + if (i != data.hwaddr_len - 1) + p += sprintf(p, ":"); + } - /* and CLID into packet, avoid overwrite from bad data */ - if ((data.clid_len > daemon->packet_buff_sz) || !read_write(pipefd[0], buf, data.clid_len, 1)) + /* supplied data may just exceed normal buffer (unlikely) */ + if ((data.hostname_len + data.ed_len + data.clid_len) > MAXDNAME && + !(alloc_buff = buf = malloc(data.hostname_len + data.ed_len + data.clid_len))) continue; + + if (!read_write(pipefd[0], buf, + data.hostname_len + data.ed_len + data.clid_len, 1)) + continue; + + /* CLID into packet */ for (p = daemon->packet, i = 0; i < data.clid_len; i++) { p += sprintf(p, "%.2x", buf[i]); if (i != data.clid_len - 1) - p += sprintf(p, ":"); + p += sprintf(p, ":"); } - - /* and expiry or length into dhcp_buff2 */ -#ifdef HAVE_BROKEN_RTC - sprintf(daemon->dhcp_buff2, "%u", data.length); + +#ifdef HAVE_DHCP6 + if (is6) + { + /* or IAID and server DUID for IPv6 */ + sprintf(daemon->dhcp_buff3, "%s%u", data.flags & LEASE_TA ? "T" : "", data.iaid); + for (p = daemon->dhcp_packet.iov_base, i = 0; i < daemon->duid_len; i++) + { + p += sprintf(p, "%.2x", daemon->duid[i]); + if (i != daemon->duid_len - 1) + p += sprintf(p, ":"); + } + + } +#endif + + buf += data.clid_len; + + if (data.hostname_len != 0) + { + char *dot; + hostname = (char *)buf; + hostname[data.hostname_len - 1] = 0; + if (data.action != ACTION_TFTP) + { + if (!legal_hostname(hostname)) + hostname = NULL; + else if ((dot = strchr(hostname, '.'))) + { + domain = dot+1; + *dot = 0; + } + } + } + + extradata = buf + data.hostname_len; + + if (!is6) + inet_ntop(AF_INET, &data.addr, daemon->addrbuff, ADDRSTRLEN); +#ifdef HAVE_DHCP6 + else + inet_ntop(AF_INET6, &data.addr6, daemon->addrbuff, ADDRSTRLEN); +#endif + +#ifdef HAVE_TFTP + /* file length */ + if (data.action == ACTION_TFTP) + sprintf(is6 ? daemon->packet : daemon->dhcp_buff, "%lu", (unsigned long)data.file_len); +#endif + +#ifdef HAVE_LUASCRIPT + if (daemon->luascript) + { + if (data.action == ACTION_TFTP) + { + lua_getglobal(lua, "tftp"); + if (lua_type(lua, -1) != LUA_TFUNCTION) + lua_pop(lua, 1); /* tftp function optional */ + else + { + lua_pushstring(lua, action_str); /* arg1 - action */ + lua_newtable(lua); /* arg2 - data table */ + lua_pushstring(lua, daemon->addrbuff); + lua_setfield(lua, -2, "destination_address"); + lua_pushstring(lua, hostname); + lua_setfield(lua, -2, "file_name"); + lua_pushstring(lua, is6 ? daemon->packet : daemon->dhcp_buff); + lua_setfield(lua, -2, "file_size"); + lua_call(lua, 2, 0); /* pass 2 values, expect 0 */ + } + } + else + { + lua_getglobal(lua, "lease"); /* function to call */ + lua_pushstring(lua, action_str); /* arg1 - action */ + lua_newtable(lua); /* arg2 - data table */ + + if (is6) + { + lua_pushstring(lua, daemon->packet); + lua_setfield(lua, -2, "client_duid"); + lua_pushstring(lua, daemon->dhcp_packet.iov_base); + lua_setfield(lua, -2, "server_duid"); + lua_pushstring(lua, daemon->dhcp_buff3); + lua_setfield(lua, -2, "iaid"); + } + + if (!is6 && data.clid_len != 0) + { + lua_pushstring(lua, daemon->packet); + lua_setfield(lua, -2, "client_id"); + } + + if (strlen(data.interface) != 0) + { + lua_pushstring(lua, data.interface); + lua_setfield(lua, -2, "interface"); + } + +#ifdef HAVE_BROKEN_RTC + lua_pushnumber(lua, data.length); + lua_setfield(lua, -2, "lease_length"); #else - sprintf(daemon->dhcp_buff2, "%lu", (unsigned long)data.expires); + lua_pushnumber(lua, data.expires); + lua_setfield(lua, -2, "lease_expires"); #endif - - /* supplied data may just exceed normal buffer (unlikely) */ - if ((data.hostname_len + data.ed_len) > daemon->packet_buff_sz && - !(alloc_buff = buf = malloc(data.hostname_len + data.ed_len))) - continue; - - if (!read_write(pipefd[0], buf, - data.hostname_len + data.ed_len, 1)) + + if (hostname) + { + lua_pushstring(lua, hostname); + lua_setfield(lua, -2, "hostname"); + } + + if (domain) + { + lua_pushstring(lua, domain); + lua_setfield(lua, -2, "domain"); + } + + end = extradata + data.ed_len; + buf = extradata; + + if (!is6) + buf = grab_extradata_lua(buf, end, "vendor_class"); +#ifdef HAVE_DHCP6 + else if (data.vendorclass_count != 0) + { + sprintf(daemon->dhcp_buff2, "vendor_class_id"); + buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2); + for (i = 0; i < data.vendorclass_count - 1; i++) + { + sprintf(daemon->dhcp_buff2, "vendor_class%i", i); + buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2); + } + } +#endif + + buf = grab_extradata_lua(buf, end, "supplied_hostname"); + + if (!is6) + { + buf = grab_extradata_lua(buf, end, "cpewan_oui"); + buf = grab_extradata_lua(buf, end, "cpewan_serial"); + buf = grab_extradata_lua(buf, end, "cpewan_class"); + buf = grab_extradata_lua(buf, end, "circuit_id"); + buf = grab_extradata_lua(buf, end, "subscriber_id"); + buf = grab_extradata_lua(buf, end, "remote_id"); + } + + buf = grab_extradata_lua(buf, end, "tags"); + + if (is6) + buf = grab_extradata_lua(buf, end, "relay_address"); + else if (data.giaddr.s_addr != 0) + { + lua_pushstring(lua, inet_ntoa(data.giaddr)); + lua_setfield(lua, -2, "relay_address"); + } + + for (i = 0; buf; i++) + { + sprintf(daemon->dhcp_buff2, "user_class%i", i); + buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2); + } + + if (data.action != ACTION_DEL && data.remaining_time != 0) + { + lua_pushnumber(lua, data.remaining_time); + lua_setfield(lua, -2, "time_remaining"); + } + + if (data.action == ACTION_OLD_HOSTNAME && hostname) + { + lua_pushstring(lua, hostname); + lua_setfield(lua, -2, "old_hostname"); + } + + if (!is6 || data.hwaddr_len != 0) + { + lua_pushstring(lua, daemon->dhcp_buff); + lua_setfield(lua, -2, "mac_address"); + } + + lua_pushstring(lua, daemon->addrbuff); + lua_setfield(lua, -2, "ip_address"); + + lua_call(lua, 2, 0); /* pass 2 values, expect 0 */ + } + } +#endif + + /* no script, just lua */ + if (!daemon->lease_change_command) continue; - + /* possible fork errors are all temporary resource problems */ while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM)) sleep(2); - free(alloc_buff); - if (pid == -1) continue; - + /* wait for child to complete */ if (pid != 0) { @@ -188,9 +465,9 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) { /* On error send event back to main process for logging */ if (WIFSIGNALED(status)) - send_event(event_fd, EVENT_KILLED, WTERMSIG(status)); + send_event(event_fd, EVENT_KILLED, WTERMSIG(status), NULL); else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) - send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status)); + send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status), NULL); break; } @@ -201,62 +478,82 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) continue; } - if (data.clid_len != 0) - my_setenv("DNSMASQ_CLIENT_ID", daemon->packet, &err); - - if (strlen(data.interface) != 0) - my_setenv("DNSMASQ_INTERFACE", data.interface, &err); - + if (data.action != ACTION_TFTP) + { +#ifdef HAVE_DHCP6 + my_setenv("DNSMASQ_IAID", is6 ? daemon->dhcp_buff3 : NULL, &err); + my_setenv("DNSMASQ_SERVER_DUID", is6 ? daemon->dhcp_packet.iov_base : NULL, &err); + my_setenv("DNSMASQ_MAC", is6 && data.hwaddr_len != 0 ? daemon->dhcp_buff : NULL, &err); +#endif + + my_setenv("DNSMASQ_CLIENT_ID", !is6 && data.clid_len != 0 ? daemon->packet : NULL, &err); + my_setenv("DNSMASQ_INTERFACE", strlen(data.interface) != 0 ? data.interface : NULL, &err); + #ifdef HAVE_BROKEN_RTC - my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err); + sprintf(daemon->dhcp_buff2, "%u", data.length); + my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err); #else - my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err); + sprintf(daemon->dhcp_buff2, "%lu", (unsigned long)data.expires); + my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err); #endif - - if (data.hostname_len != 0) - { - char *dot; - hostname = (char *)buf; - hostname[data.hostname_len - 1] = 0; - if (!legal_hostname(hostname)) - hostname = NULL; - else if ((dot = strchr(hostname, '.'))) + + my_setenv("DNSMASQ_DOMAIN", domain, &err); + + end = extradata + data.ed_len; + buf = extradata; + + if (!is6) + buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS", &err); +#ifdef HAVE_DHCP6 + else { - my_setenv("DNSMASQ_DOMAIN", dot+1, &err); - *dot = 0; - } - buf += data.hostname_len; - } - - end = buf + data.ed_len; - buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS", &err); - buf = grab_extradata(buf, end, "DNSMASQ_SUPPLIED_HOSTNAME", &err); - buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_OUI", &err); - buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_SERIAL", &err); - buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_CLASS", &err); - buf = grab_extradata(buf, end, "DNSMASQ_TAGS", &err); - - for (i = 0; buf; i++) - { - sprintf(daemon->dhcp_buff2, "DNSMASQ_USER_CLASS%i", i); - buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err); - } - - if (data.giaddr.s_addr != 0) - my_setenv("DNSMASQ_RELAY_ADDRESS", inet_ntoa(data.giaddr), &err); + if (data.vendorclass_count != 0) + { + buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS_ID", &err); + for (i = 0; i < data.vendorclass_count - 1; i++) + { + sprintf(daemon->dhcp_buff2, "DNSMASQ_VENDOR_CLASS%i", i); + buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err); + } + } + } +#endif + + buf = grab_extradata(buf, end, "DNSMASQ_SUPPLIED_HOSTNAME", &err); + + if (!is6) + { + buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_OUI", &err); + buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_SERIAL", &err); + buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_CLASS", &err); + buf = grab_extradata(buf, end, "DNSMASQ_CIRCUIT_ID", &err); + buf = grab_extradata(buf, end, "DNSMASQ_SUBSCRIBER_ID", &err); + buf = grab_extradata(buf, end, "DNSMASQ_REMOTE_ID", &err); + } + + buf = grab_extradata(buf, end, "DNSMASQ_TAGS", &err); - if (data.action != ACTION_DEL) - { + if (is6) + buf = grab_extradata(buf, end, "DNSMASQ_RELAY_ADDRESS", &err); + else + my_setenv("DNSMASQ_RELAY_ADDRESS", data.giaddr.s_addr != 0 ? inet_ntoa(data.giaddr) : NULL, &err); + + for (i = 0; buf; i++) + { + sprintf(daemon->dhcp_buff2, "DNSMASQ_USER_CLASS%i", i); + buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err); + } + sprintf(daemon->dhcp_buff2, "%u", data.remaining_time); - my_setenv("DNSMASQ_TIME_REMAINING", daemon->dhcp_buff2, &err); - } - - if (data.action == ACTION_OLD_HOSTNAME && hostname) - { - my_setenv("DNSMASQ_OLD_HOSTNAME", hostname, &err); - hostname = NULL; + my_setenv("DNSMASQ_TIME_REMAINING", data.action != ACTION_DEL && data.remaining_time != 0 ? daemon->dhcp_buff2 : NULL, &err); + + my_setenv("DNSMASQ_OLD_HOSTNAME", data.action == ACTION_OLD_HOSTNAME ? hostname : NULL, &err); + if (data.action == ACTION_OLD_HOSTNAME) + hostname = NULL; } + my_setenv("DNSMASQ_LOG_DHCP", option_bool(OPT_LOG_OPTS) ? "1" : NULL, &err); + /* we need to have the event_fd around if exec fails */ if ((i = fcntl(event_fd, F_GETFD)) != -1) fcntl(event_fd, F_SETFD, i | FD_CLOEXEC); @@ -267,23 +564,61 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) { execl(daemon->lease_change_command, p ? p+1 : daemon->lease_change_command, - action_str, daemon->dhcp_buff, inet_ntoa(data.addr), hostname, (char*)NULL); + action_str, is6 ? daemon->packet : daemon->dhcp_buff, + daemon->addrbuff, hostname, (char*)NULL); err = errno; } /* failed, send event so the main process logs the problem */ - send_event(event_fd, EVENT_EXEC_ERR, err); + send_event(event_fd, EVENT_EXEC_ERR, err, NULL); _exit(0); } } static void my_setenv(const char *name, const char *value, int *error) { - if (*error == 0 && setenv(name, value, 1) != 0) - *error = errno; + if (*error == 0) + { + if (!value) + unsetenv(name); + else if (setenv(name, value, 1) != 0) + *error = errno; + } } static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, char *env, int *err) { + unsigned char *next = NULL; + char *val = NULL; + + if (buf && (buf != end)) + { + for (next = buf; ; next++) + if (next == end) + { + next = NULL; + break; + } + else if (*next == 0) + break; + + if (next && (next != buf)) + { + char *p; + /* No "=" in value */ + if ((p = strchr((char *)buf, '='))) + *p = 0; + val = (char *)buf; + } + } + + my_setenv(env, val, err); + + return next ? next + 1 : NULL; +} + +#ifdef HAVE_LUASCRIPT +static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end, char *field) +{ unsigned char *next; if (!buf || (buf == end)) @@ -295,36 +630,16 @@ static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, ch if (next != buf) { - char *p; - /* No "=" in value */ - if ((p = strchr((char *)buf, '='))) - *p = 0; - my_setenv(env, (char *)buf, err); + lua_pushstring(lua, (char *)buf); + lua_setfield(lua, -2, field); } return next + 1; } +#endif -/* pack up lease data into a buffer */ -void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now) +static void buff_alloc(size_t size) { - unsigned char *p; - size_t size; - unsigned int hostname_len = 0, clid_len = 0, ed_len = 0; - - /* no script */ - if (daemon->helperfd == -1) - return; - - if (lease->extradata) - ed_len = lease->extradata_len; - if (lease->clid) - clid_len = lease->clid_len; - if (hostname) - hostname_len = strlen(hostname) + 1; - - size = sizeof(struct script_data) + clid_len + ed_len + hostname_len; - if (size > buf_size) { struct script_data *new; @@ -340,8 +655,39 @@ void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t n buf = new; buf_size = size; } +} + +/* pack up lease data into a buffer */ +void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now) +{ + unsigned char *p; + unsigned int hostname_len = 0, clid_len = 0, ed_len = 0; + int fd = daemon->dhcpfd; +#ifdef HAVE_DHCP6 + if (!daemon->dhcp) + fd = daemon->dhcp6fd; +#endif + + /* no script */ + if (daemon->helperfd == -1) + return; + + if (lease->extradata) + ed_len = lease->extradata_len; + if (lease->clid) + clid_len = lease->clid_len; + if (hostname) + hostname_len = strlen(hostname) + 1; + + buff_alloc(sizeof(struct script_data) + clid_len + ed_len + hostname_len); buf->action = action; + buf->flags = lease->flags; +#ifdef HAVE_DHCP6 + buf->vendorclass_count = lease->vendorclass_count; + buf->addr6 = lease->addr6; + buf->iaid = lease->iaid; +#endif buf->hwaddr_len = lease->hwaddr_len; buf->hwaddr_type = lease->hwaddr_type; buf->clid_len = clid_len; @@ -349,8 +695,8 @@ void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t n buf->hostname_len = hostname_len; buf->addr = lease->addr; buf->giaddr = lease->giaddr; - memcpy(buf->hwaddr, lease->hwaddr, lease->hwaddr_len); - if (!indextoname(daemon->dhcpfd, lease->last_interface, buf->interface)) + memcpy(buf->hwaddr, lease->hwaddr, DHCP_CHADDR_MAX); + if (!indextoname(fd, lease->last_interface, buf->interface)) buf->interface[0] = 0; #ifdef HAVE_BROKEN_RTC @@ -358,7 +704,11 @@ void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t n #else buf->expires = lease->expires; #endif - buf->remaining_time = (unsigned int)difftime(lease->expires, now); + + if (lease->expires != 0) + buf->remaining_time = (unsigned int)difftime(lease->expires, now); + else + buf->remaining_time = 0; p = (unsigned char *)(buf+1); if (clid_len != 0) @@ -379,6 +729,37 @@ void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t n bytes_in_buf = p - (unsigned char *)buf; } +#ifdef HAVE_TFTP +/* This nastily re-uses DHCP-fields for TFTP stuff */ +void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer) +{ + unsigned int filename_len; + + /* no script */ + if (daemon->helperfd == -1) + return; + + filename_len = strlen(filename) + 1; + buff_alloc(sizeof(struct script_data) + filename_len); + memset(buf, 0, sizeof(struct script_data)); + + buf->action = ACTION_TFTP; + buf->hostname_len = filename_len; + buf->file_len = file_len; + + if ((buf->flags = peer->sa.sa_family) == AF_INET) + buf->addr = peer->in.sin_addr; +#ifdef HAVE_IPV6 + else + buf->addr6 = peer->in6.sin6_addr; +#endif + + memcpy((unsigned char *)(buf+1), filename, filename_len); + + bytes_in_buf = sizeof(struct script_data) + filename_len; +} +#endif + int helper_buf_empty(void) { return bytes_in_buf == 0; @@ -408,3 +789,4 @@ void helper_write(void) #endif + diff --git a/src/inotify.c b/src/inotify.c new file mode 100644 index 0000000..52d412f --- /dev/null +++ b/src/inotify.c @@ -0,0 +1,288 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" +#ifdef HAVE_INOTIFY + +#include <sys/inotify.h> +#include <sys/param.h> /* For MAXSYMLINKS */ + +/* the strategy is to set a inotify on the directories containing + resolv files, for any files in the directory which are close-write + or moved into the directory. + + When either of those happen, we look to see if the file involved + is actually a resolv-file, and if so, call poll-resolv with + the "force" argument, to ensure it's read. + + This adds one new error condition: the directories containing + all specified resolv-files must exist at start-up, even if the actual + files don't. +*/ + +static char *inotify_buffer; +#define INOTIFY_SZ (sizeof(struct inotify_event) + NAME_MAX + 1) + +/* If path is a symbolic link, return the path it + points to, made absolute if relative. + If path doesn't exist or is not a symlink, return NULL. + Return value is malloc'ed */ +static char *my_readlink(char *path) +{ + ssize_t rc, size = 64; + char *buf; + + while (1) + { + buf = safe_malloc(size); + rc = readlink(path, buf, (size_t)size); + + if (rc == -1) + { + /* Not link or doesn't exist. */ + if (errno == EINVAL || errno == ENOENT) + return NULL; + else + die(_("cannot access path %s: %s"), path, EC_MISC); + } + else if (rc < size-1) + { + char *d; + + buf[rc] = 0; + if (buf[0] != '/' && ((d = strrchr(path, '/')))) + { + /* Add path to relative link */ + char *new_buf = safe_malloc((d - path) + strlen(buf) + 2); + *(d+1) = 0; + strcpy(new_buf, path); + strcat(new_buf, buf); + free(buf); + buf = new_buf; + } + return buf; + } + + /* Buffer too small, increase and retry */ + size += 64; + free(buf); + } +} + +void inotify_dnsmasq_init() +{ + struct resolvc *res; + inotify_buffer = safe_malloc(INOTIFY_SZ); + daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + + if (daemon->inotifyfd == -1) + die(_("failed to create inotify: %s"), NULL, EC_MISC); + + for (res = daemon->resolv_files; res; res = res->next) + { + char *d, *new_path, *path = safe_malloc(strlen(res->name) + 1); + int links = MAXSYMLINKS; + + strcpy(path, res->name); + + /* Follow symlinks until we reach a non-symlink, or a non-existant file. */ + while ((new_path = my_readlink(path))) + { + if (links-- == 0) + die(_("too many symlinks following %s"), res->name, EC_MISC); + free(path); + path = new_path; + } + + res->wd = -1; + + if ((d = strrchr(path, '/'))) + { + *d = 0; /* make path just directory */ + res->wd = inotify_add_watch(daemon->inotifyfd, path, IN_CLOSE_WRITE | IN_MOVED_TO); + + res->file = d+1; /* pointer to filename */ + *d = '/'; + + if (res->wd == -1 && errno == ENOENT) + die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC); + } + + if (res->wd == -1) + die(_("failed to create inotify for %s: %s"), res->name, EC_MISC); + + } +} + + +/* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */ +void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz) +{ + struct hostsfile *ah; + + for (ah = daemon->dynamic_dirs; ah; ah = ah->next) + { + DIR *dir_stream = NULL; + struct dirent *ent; + struct stat buf; + + if (!(ah->flags & flag)) + continue; + + if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode))) + { + my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"), + ah->fname, strerror(errno)); + continue; + } + + if (!(ah->flags & AH_WD_DONE)) + { + ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO); + ah->flags |= AH_WD_DONE; + } + + /* Read contents of dir _after_ calling add_watch, in the hope of avoiding + a race which misses files being added as we start */ + if (ah->wd == -1 || !(dir_stream = opendir(ah->fname))) + { + my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"), + ah->fname, strerror(errno)); + continue; + } + + while ((ent = readdir(dir_stream))) + { + size_t lendir = strlen(ah->fname); + size_t lenfile = strlen(ent->d_name); + char *path; + + /* ignore emacs backups and dotfiles */ + if (lenfile == 0 || + ent->d_name[lenfile - 1] == '~' || + (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') || + ent->d_name[0] == '.') + continue; + + if ((path = whine_malloc(lendir + lenfile + 2))) + { + strcpy(path, ah->fname); + strcat(path, "/"); + strcat(path, ent->d_name); + + /* ignore non-regular files */ + if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode)) + { + if (ah->flags & AH_HOSTS) + total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz); +#ifdef HAVE_DHCP + else if (ah->flags & (AH_DHCP_HST | AH_DHCP_OPT)) + option_read_dynfile(path, ah->flags); +#endif + } + + free(path); + } + } + } +} + +int inotify_check(time_t now) +{ + int hit = 0; + struct hostsfile *ah; + + while (1) + { + int rc; + char *p; + struct resolvc *res; + struct inotify_event *in; + + while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR); + + if (rc <= 0) + break; + + for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len) + { + in = (struct inotify_event*)p; + + for (res = daemon->resolv_files; res; res = res->next) + if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0) + hit = 1; + + /* ignore emacs backups and dotfiles */ + if (in->len == 0 || + in->name[in->len - 1] == '~' || + (in->name[0] == '#' && in->name[in->len - 1] == '#') || + in->name[0] == '.') + continue; + + for (ah = daemon->dynamic_dirs; ah; ah = ah->next) + if (ah->wd == in->wd) + { + size_t lendir = strlen(ah->fname); + char *path; + + if ((path = whine_malloc(lendir + in->len + 2))) + { + strcpy(path, ah->fname); + strcat(path, "/"); + strcat(path, in->name); + + my_syslog(LOG_INFO, _("inotify, new or changed file %s"), path); + + if (ah->flags & AH_HOSTS) + { + read_hostsfile(path, ah->index, 0, NULL, 0); +#ifdef HAVE_DHCP + if (daemon->dhcp || daemon->doing_dhcp6) + { + /* Propogate the consequences of loading a new dhcp-host */ + dhcp_update_configs(daemon->dhcp_conf); + lease_update_from_configs(); + lease_update_file(now); + lease_update_dns(1); + } +#endif + } +#ifdef HAVE_DHCP + else if (ah->flags & AH_DHCP_HST) + { + if (option_read_dynfile(path, AH_DHCP_HST)) + { + /* Propogate the consequences of loading a new dhcp-host */ + dhcp_update_configs(daemon->dhcp_conf); + lease_update_from_configs(); + lease_update_file(now); + lease_update_dns(1); + } + } + else if (ah->flags & AH_DHCP_OPT) + option_read_dynfile(path, AH_DHCP_OPT); +#endif + + free(path); + } + } + } + } + return hit; +} + +#endif /* INOTIFY */ + diff --git a/src/ip6addr.h b/src/ip6addr.h new file mode 100644 index 0000000..f0b7e82 --- /dev/null +++ b/src/ip6addr.h @@ -0,0 +1,34 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + + +#define IN6_IS_ADDR_ULA(a) \ + ((((__const uint32_t *) (a))[0] & htonl (0xff000000)) \ + == htonl (0xfd000000)) + +#define IN6_IS_ADDR_ULA_ZERO(a) \ + (((__const uint32_t *) (a))[0] == htonl (0xfd000000) \ + && ((__const uint32_t *) (a))[1] == 0 \ + && ((__const uint32_t *) (a))[2] == 0 \ + && ((__const uint32_t *) (a))[3] == 0) + +#define IN6_IS_ADDR_LINK_LOCAL_ZERO(a) \ + (((__const uint32_t *) (a))[0] == htonl (0xfe800000) \ + && ((__const uint32_t *) (a))[1] == 0 \ + && ((__const uint32_t *) (a))[2] == 0 \ + && ((__const uint32_t *) (a))[3] == 0) + diff --git a/src/ipset.c b/src/ipset.c new file mode 100644 index 0000000..a315e86 --- /dev/null +++ b/src/ipset.c @@ -0,0 +1,229 @@ +/* ipset.c is Copyright (c) 2013 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + + 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#if defined(HAVE_IPSET) && defined(HAVE_LINUX_NETWORK) + +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/utsname.h> +#include <arpa/inet.h> +#include <linux/version.h> +#include <linux/netlink.h> + +/* We want to be able to compile against old header files + Kernel version is handled at run-time. */ + +#define NFNL_SUBSYS_IPSET 6 + +#define IPSET_ATTR_DATA 7 +#define IPSET_ATTR_IP 1 +#define IPSET_ATTR_IPADDR_IPV4 1 +#define IPSET_ATTR_IPADDR_IPV6 2 +#define IPSET_ATTR_PROTOCOL 1 +#define IPSET_ATTR_SETNAME 2 +#define IPSET_CMD_ADD 9 +#define IPSET_CMD_DEL 10 +#define IPSET_MAXNAMELEN 32 +#define IPSET_PROTOCOL 6 + +#ifndef NFNETLINK_V0 +#define NFNETLINK_V0 0 +#endif + +#ifndef NLA_F_NESTED +#define NLA_F_NESTED (1 << 15) +#endif + +#ifndef NLA_F_NET_BYTEORDER +#define NLA_F_NET_BYTEORDER (1 << 14) +#endif + +struct my_nlattr { + __u16 nla_len; + __u16 nla_type; +}; + +struct my_nfgenmsg { + __u8 nfgen_family; /* AF_xxx */ + __u8 version; /* nfnetlink version */ + __be16 res_id; /* resource id */ +}; + + +/* data structure size in here is fixed */ +#define BUFF_SZ 256 + +#define NL_ALIGN(len) (((len)+3) & ~(3)) +static const struct sockaddr_nl snl = { .nl_family = AF_NETLINK }; +static int ipset_sock, old_kernel; +static char *buffer; + +static inline void add_attr(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data) +{ + struct my_nlattr *attr = (void *)nlh + NL_ALIGN(nlh->nlmsg_len); + uint16_t payload_len = NL_ALIGN(sizeof(struct my_nlattr)) + len; + attr->nla_type = type; + attr->nla_len = payload_len; + memcpy((void *)attr + NL_ALIGN(sizeof(struct my_nlattr)), data, len); + nlh->nlmsg_len += NL_ALIGN(payload_len); +} + +void ipset_init(void) +{ + struct utsname utsname; + int version; + char *split; + + if (uname(&utsname) < 0) + die(_("failed to find kernel version: %s"), NULL, EC_MISC); + + split = strtok(utsname.release, "."); + version = (split ? atoi(split) : 0); + split = strtok(NULL, "."); + version = version * 256 + (split ? atoi(split) : 0); + split = strtok(NULL, "."); + version = version * 256 + (split ? atoi(split) : 0); + old_kernel = (version < KERNEL_VERSION(2,6,32)); + + if (old_kernel && (ipset_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) != -1) + return; + + if (!old_kernel && + (buffer = safe_malloc(BUFF_SZ)) && + (ipset_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER)) != -1 && + (bind(ipset_sock, (struct sockaddr *)&snl, sizeof(snl)) != -1)) + return; + + die (_("failed to create IPset control socket: %s"), NULL, EC_MISC); +} + +static int new_add_to_ipset(const char *setname, const struct all_addr *ipaddr, int af, int remove) +{ + struct nlmsghdr *nlh; + struct my_nfgenmsg *nfg; + struct my_nlattr *nested[2]; + uint8_t proto; + int addrsz = INADDRSZ; + +#ifdef HAVE_IPV6 + if (af == AF_INET6) + addrsz = IN6ADDRSZ; +#endif + + if (strlen(setname) >= IPSET_MAXNAMELEN) + { + errno = ENAMETOOLONG; + return -1; + } + + memset(buffer, 0, BUFF_SZ); + + nlh = (struct nlmsghdr *)buffer; + nlh->nlmsg_len = NL_ALIGN(sizeof(struct nlmsghdr)); + nlh->nlmsg_type = (remove ? IPSET_CMD_DEL : IPSET_CMD_ADD) | (NFNL_SUBSYS_IPSET << 8); + nlh->nlmsg_flags = NLM_F_REQUEST; + + nfg = (struct my_nfgenmsg *)(buffer + nlh->nlmsg_len); + nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nfgenmsg)); + nfg->nfgen_family = af; + nfg->version = NFNETLINK_V0; + nfg->res_id = htons(0); + + proto = IPSET_PROTOCOL; + add_attr(nlh, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto); + add_attr(nlh, IPSET_ATTR_SETNAME, strlen(setname) + 1, setname); + nested[0] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len)); + nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr)); + nested[0]->nla_type = NLA_F_NESTED | IPSET_ATTR_DATA; + nested[1] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len)); + nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr)); + nested[1]->nla_type = NLA_F_NESTED | IPSET_ATTR_IP; + add_attr(nlh, + (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER, + addrsz, &ipaddr->addr); + nested[1]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[1]; + nested[0]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[0]; + + while (retry_send(sendto(ipset_sock, buffer, nlh->nlmsg_len, 0, + (struct sockaddr *)&snl, sizeof(snl)))); + + return errno == 0 ? 0 : -1; +} + + +static int old_add_to_ipset(const char *setname, const struct all_addr *ipaddr, int remove) +{ + socklen_t size; + struct ip_set_req_adt_get { + unsigned op; + unsigned version; + union { + char name[IPSET_MAXNAMELEN]; + uint16_t index; + } set; + char typename[IPSET_MAXNAMELEN]; + } req_adt_get; + struct ip_set_req_adt { + unsigned op; + uint16_t index; + uint32_t ip; + } req_adt; + + if (strlen(setname) >= sizeof(req_adt_get.set.name)) + { + errno = ENAMETOOLONG; + return -1; + } + + req_adt_get.op = 0x10; + req_adt_get.version = 3; + strcpy(req_adt_get.set.name, setname); + size = sizeof(req_adt_get); + if (getsockopt(ipset_sock, SOL_IP, 83, &req_adt_get, &size) < 0) + return -1; + req_adt.op = remove ? 0x102 : 0x101; + req_adt.index = req_adt_get.set.index; + req_adt.ip = ntohl(ipaddr->addr.addr4.s_addr); + if (setsockopt(ipset_sock, SOL_IP, 83, &req_adt, sizeof(req_adt)) < 0) + return -1; + + return 0; +} + + + +int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, int remove) +{ + int af = AF_INET; + +#ifdef HAVE_IPV6 + if (flags & F_IPV6) + { + af = AF_INET6; + /* old method only supports IPv4 */ + if (old_kernel) + return -1; + } +#endif + + return old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove); +} + +#endif diff --git a/src/lease.c b/src/lease.c index cfa7543..8adb605 100644 --- a/src/lease.c +++ b/src/lease.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 @@ -24,19 +24,13 @@ static int dns_dirty, file_dirty, leases_left; void lease_init(time_t now) { unsigned long ei; - struct in_addr addr; + struct all_addr addr; struct dhcp_lease *lease; int clid_len, hw_len, hw_type; FILE *leasestream; - /* These each hold a DHCP option max size 255 - and get a terminating zero added */ - daemon->dhcp_buff = safe_malloc(256); - daemon->dhcp_buff2 = safe_malloc(256); - daemon->dhcp_buff3 = safe_malloc(256); - leases_left = daemon->dhcp_max; - + if (option_bool(OPT_LEASE_RO)) { /* run "<lease_change_script> init" once to get the @@ -73,23 +67,70 @@ void lease_init(time_t now) /* client-id max length is 255 which is 255*2 digits + 254 colons borrow DNS packet buffer which is always larger than 1000 bytes */ if (leasestream) - while (fscanf(leasestream, "%lu %255s %16s %255s %764s", - &ei, daemon->dhcp_buff2, daemon->namebuff, - daemon->dhcp_buff, daemon->packet) == 5) + while (fscanf(leasestream, "%255s %255s", daemon->dhcp_buff3, daemon->dhcp_buff2) == 2) { - hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type); - /* For backwards compatibility, no explict MAC address type means ether. */ - if (hw_type == 0 && hw_len != 0) - hw_type = ARPHRD_ETHER; +#ifdef HAVE_DHCP6 + if (strcmp(daemon->dhcp_buff3, "duid") == 0) + { + daemon->duid_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, 130, NULL, NULL); + daemon->duid = safe_malloc(daemon->duid_len); + memcpy(daemon->duid, daemon->dhcp_buff2, daemon->duid_len); + continue; + } +#endif + + ei = atol(daemon->dhcp_buff3); - addr.s_addr = inet_addr(daemon->namebuff); + if (fscanf(leasestream, " %64s %255s %764s", + daemon->namebuff, daemon->dhcp_buff, daemon->packet) != 3) + break; - /* decode hex in place */ clid_len = 0; if (strcmp(daemon->packet, "*") != 0) clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL); - if (!(lease = lease_allocate(addr))) + if (inet_pton(AF_INET, daemon->namebuff, &addr.addr.addr4) && + (lease = lease4_allocate(addr.addr.addr4))) + { + hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type); + /* For backwards compatibility, no explict MAC address type means ether. */ + if (hw_type == 0 && hw_len != 0) + hw_type = ARPHRD_ETHER; + + lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet, + hw_len, hw_type, clid_len, now, 0); + + if (strcmp(daemon->dhcp_buff, "*") != 0) + lease_set_hostname(lease, daemon->dhcp_buff, 0, get_domain(lease->addr), NULL); + } +#ifdef HAVE_DHCP6 + else if (inet_pton(AF_INET6, daemon->namebuff, &addr.addr.addr6)) + { + char *s = daemon->dhcp_buff2; + int lease_type = LEASE_NA; + int iaid; + + if (s[0] == 'T') + { + lease_type = LEASE_TA; + s++; + } + + iaid = strtoul(s, NULL, 10); + + if ((lease = lease6_allocate(&addr.addr.addr6, lease_type))) + { + lease_set_hwaddr(lease, NULL, (unsigned char *)daemon->packet, 0, 0, clid_len, now, 0); + lease_set_iaid(lease, iaid); + if (strcmp(daemon->dhcp_buff, "*") != 0) + lease_set_hostname(lease, daemon->dhcp_buff, 0, get_domain6((struct in6_addr *)lease->hwaddr), NULL); + } + } +#endif + else + break; + + if (!lease) die (_("too many stored leases"), NULL, EC_MISC); #ifdef HAVE_BROKEN_RTC @@ -104,14 +145,9 @@ void lease_init(time_t now) lease->expires = (time_t)ei; #endif - lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet, hw_len, hw_type, clid_len); - - if (strcmp(daemon->dhcp_buff, "*") != 0) - lease_set_hostname(lease, daemon->dhcp_buff, 0); - /* set these correctly: the "old" events are generated later from the startup synthesised SIGHUP. */ - lease->new = lease->changed = 0; + lease->flags &= ~(LEASE_NEW | LEASE_CHANGED); } #ifdef HAVE_SCRIPT @@ -150,17 +186,19 @@ void lease_update_from_configs(void) struct dhcp_lease *lease; struct dhcp_config *config; char *name; - + for (lease = leases; lease; lease = lease->next) - if ((config = find_config(daemon->dhcp_conf, NULL, lease->clid, lease->clid_len, - lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL)) && - (config->flags & CONFIG_NAME) && - (!(config->flags & CONFIG_ADDR) || config->addr.s_addr == lease->addr.s_addr)) - lease_set_hostname(lease, config->hostname, 1); + if (lease->flags & (LEASE_TA | LEASE_NA)) + continue; + else if ((config = find_config(daemon->dhcp_conf, NULL, lease->clid, lease->clid_len, + lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL)) && + (config->flags & CONFIG_NAME) && + (!(config->flags & CONFIG_ADDR) || config->addr.s_addr == lease->addr.s_addr)) + lease_set_hostname(lease, config->hostname, 1, get_domain(lease->addr), NULL); else if ((name = host_from_dns(lease->addr))) - lease_set_hostname(lease, name, 1); /* updates auth flag only */ + lease_set_hostname(lease, name, 1, get_domain(lease->addr), NULL); /* updates auth flag only */ } - + static void ourprintf(int *errp, char *format, ...) { va_list ap; @@ -186,11 +224,18 @@ void lease_update_file(time_t now) for (lease = leases; lease; lease = lease->next) { + +#ifdef HAVE_DHCP6 + if (lease->flags & (LEASE_TA | LEASE_NA)) + continue; +#endif + #ifdef HAVE_BROKEN_RTC ourprintf(&err, "%u ", lease->length); #else ourprintf(&err, "%lu ", (unsigned long)lease->expires); #endif + if (lease->hwaddr_type != ARPHRD_ETHER || lease->hwaddr_len == 0) ourprintf(&err, "%.2x-", lease->hwaddr_type); for (i = 0; i < lease->hwaddr_len; i++) @@ -199,8 +244,10 @@ void lease_update_file(time_t now) if (i != lease->hwaddr_len - 1) ourprintf(&err, ":"); } + + inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN); - ourprintf(&err, " %s ", inet_ntoa(lease->addr)); + ourprintf(&err, " %s ", daemon->addrbuff); ourprintf(&err, "%s ", lease->hostname ? lease->hostname : "*"); if (lease->clid && lease->clid_len != 0) @@ -213,6 +260,44 @@ void lease_update_file(time_t now) ourprintf(&err, "*\n"); } +#ifdef HAVE_DHCP6 + if (daemon->duid) + { + ourprintf(&err, "duid "); + for (i = 0; i < daemon->duid_len - 1; i++) + ourprintf(&err, "%.2x:", daemon->duid[i]); + ourprintf(&err, "%.2x\n", daemon->duid[i]); + + for (lease = leases; lease; lease = lease->next) + { + + if (!(lease->flags & (LEASE_TA | LEASE_NA))) + continue; + +#ifdef HAVE_BROKEN_RTC + ourprintf(&err, "%u ", lease->length); +#else + ourprintf(&err, "%lu ", (unsigned long)lease->expires); +#endif + + inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN); + + ourprintf(&err, "%s%u %s ", (lease->flags & LEASE_TA) ? "T" : "", + lease->iaid, daemon->addrbuff); + ourprintf(&err, "%s ", lease->hostname ? lease->hostname : "*"); + + if (lease->clid && lease->clid_len != 0) + { + for (i = 0; i < lease->clid_len - 1; i++) + ourprintf(&err, "%.2x:", lease->clid[i]); + ourprintf(&err, "%.2x\n", lease->clid[i]); + } + else + ourprintf(&err, "*\n"); + } + } +#endif + if (fflush(daemon->lease_stream) != 0 || fsync(fileno(daemon->lease_stream)) < 0) err = errno; @@ -221,11 +306,33 @@ void lease_update_file(time_t now) file_dirty = 0; } - /* Set alarm for when the first lease expires + slop. */ - for (next_event = 0, lease = leases; lease; lease = lease->next) + /* Set alarm for when the first lease expires. */ + next_event = 0; + +#ifdef HAVE_DHCP6 + /* do timed RAs and determine when the next is, also pings to potential SLAAC addresses */ + if (daemon->doing_ra) + { + time_t event; + + if ((event = periodic_slaac(now, leases)) != 0) + { + if (next_event == 0 || difftime(next_event, event) > 0.0) + next_event = event; + } + + if ((event = periodic_ra(now)) != 0) + { + if (next_event == 0 || difftime(next_event, event) > 0.0) + next_event = event; + } + } +#endif + + for (lease = leases; lease; lease = lease->next) if (lease->expires != 0 && - (next_event == 0 || difftime(next_event, lease->expires + 10) > 0.0)) - next_event = lease->expires + 10; + (next_event == 0 || difftime(next_event, lease->expires) > 0.0)) + next_event = lease->expires; if (err) { @@ -237,25 +344,168 @@ void lease_update_file(time_t now) (unsigned int)difftime(next_event, now)); } - if (next_event != 0) - alarm((unsigned)difftime(next_event, now)); + send_alarm(next_event, now); +} + + +static int find_interface_v4(struct in_addr local, int if_index, char *label, + struct in_addr netmask, struct in_addr broadcast, void *vparam) +{ + struct dhcp_lease *lease; + int prefix = netmask_length(netmask); + + (void) label; + (void) broadcast; + (void) vparam; + + for (lease = leases; lease; lease = lease->next) + if (!(lease->flags & (LEASE_TA | LEASE_NA)) && + is_same_net(local, lease->addr, netmask) && + prefix > lease->new_prefixlen) + { + lease->new_interface = if_index; + lease->new_prefixlen = prefix; + } + + return 1; } -void lease_update_dns(void) +#ifdef HAVE_DHCP6 +static int find_interface_v6(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, + int preferred, int valid, void *vparam) { struct dhcp_lease *lease; + + (void)scope; + (void)flags; + (void)preferred; + (void)valid; + (void)vparam; + + for (lease = leases; lease; lease = lease->next) + if ((lease->flags & (LEASE_TA | LEASE_NA))) + if (is_same_net6(local, &lease->addr6, prefix) && prefix > lease->new_prefixlen) { + /* save prefix length for comparison, as we might get shorter matching + * prefix in upcoming netlink GETADDR responses + * */ + lease->new_interface = if_index; + lease->new_prefixlen = prefix; + } + + return 1; +} + +void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface) +{ + /* We may be doing RA but not DHCPv4, in which case the lease + database may not exist and we have nothing to do anyway */ + if (daemon->dhcp) + slaac_ping_reply(sender, packet, interface, leases); +} + +void lease_update_slaac(time_t now) +{ + /* Called when we contruct a new RA-names context, to add putative + new SLAAC addresses to existing leases. */ + + struct dhcp_lease *lease; - if (daemon->port != 0 && dns_dirty) + if (daemon->dhcp) + for (lease = leases; lease; lease = lease->next) + slaac_add_addrs(lease, now, 0); +} + +#endif + + +/* Find interfaces associated with leases at start-up. This gets updated as + we do DHCP transactions, but information about directly-connected subnets + is useful from scrips and necessary for determining SLAAC addresses from + start-time. */ +void lease_find_interfaces(time_t now) +{ + struct dhcp_lease *lease; + + for (lease = leases; lease; lease = lease->next) + lease->new_prefixlen = lease->new_interface = 0; + + iface_enumerate(AF_INET, &now, find_interface_v4); +#ifdef HAVE_DHCP6 + iface_enumerate(AF_INET6, &now, find_interface_v6); +#endif + + for (lease = leases; lease; lease = lease->next) + if (lease->new_interface != 0) + lease_set_interface(lease, lease->new_interface, now); +} + +#ifdef HAVE_DHCP6 +void lease_make_duid(time_t now) +{ + /* If we're not doing DHCPv6, and there are not v6 leases, don't add the DUID to the database */ + if (!daemon->duid && daemon->doing_dhcp6) { - cache_unhash_dhcp(); + file_dirty = 1; + make_duid(now); + } +} +#endif + + + + +void lease_update_dns(int force) +{ + struct dhcp_lease *lease; + + if (daemon->port != 0 && (dns_dirty || force)) + { +#ifndef HAVE_BROKEN_RTC + /* force transfer to authoritative secondaries */ + daemon->soa_sn++; +#endif + cache_unhash_dhcp(); + for (lease = leases; lease; lease = lease->next) { + int prot = AF_INET; + +#ifdef HAVE_DHCP6 + if (lease->flags & (LEASE_TA | LEASE_NA)) + prot = AF_INET6; + else if (lease->hostname || lease->fqdn) + { + struct slaac_address *slaac; + + for (slaac = lease->slaac_address; slaac; slaac = slaac->next) + if (slaac->backoff == 0) + { + if (lease->fqdn) + cache_add_dhcp_entry(lease->fqdn, AF_INET6, (struct all_addr *)&slaac->addr, lease->expires); + if (!option_bool(OPT_DHCP_FQDN) && lease->hostname) + cache_add_dhcp_entry(lease->hostname, AF_INET6, (struct all_addr *)&slaac->addr, lease->expires); + } + } + if (lease->fqdn) - cache_add_dhcp_entry(lease->fqdn, &lease->addr, lease->expires); + cache_add_dhcp_entry(lease->fqdn, prot, + prot == AF_INET ? (struct all_addr *)&lease->addr : (struct all_addr *)&lease->addr6, + lease->expires); if (!option_bool(OPT_DHCP_FQDN) && lease->hostname) - cache_add_dhcp_entry(lease->hostname, &lease->addr, lease->expires); + cache_add_dhcp_entry(lease->hostname, prot, + prot == AF_INET ? (struct all_addr *)&lease->addr : (struct all_addr *)&lease->addr6, + lease->expires); + +#else + if (lease->fqdn) + cache_add_dhcp_entry(lease->fqdn, prot, (struct all_addr *)&lease->addr, lease->expires); + + if (!option_bool(OPT_DHCP_FQDN) && lease->hostname) + cache_add_dhcp_entry(lease->hostname, prot, (struct all_addr *)&lease->addr, lease->expires); +#endif } dns_dirty = 0; @@ -275,7 +525,7 @@ void lease_prune(struct dhcp_lease *target, time_t now) if (lease->hostname) dns_dirty = 1; - *up = lease->next; /* unlink */ + *up = lease->next; /* unlink */ /* Put on old_leases list 'till we can run the script */ @@ -297,18 +547,30 @@ struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int h if (clid) for (lease = leases; lease; lease = lease->next) - if (lease->clid && clid_len == lease->clid_len && - memcmp(clid, lease->clid, clid_len) == 0) - return lease; + { +#ifdef HAVE_DHCP6 + if (lease->flags & (LEASE_TA | LEASE_NA)) + continue; +#endif + if (lease->clid && clid_len == lease->clid_len && + memcmp(clid, lease->clid, clid_len) == 0) + return lease; + } for (lease = leases; lease; lease = lease->next) - if ((!lease->clid || !clid) && - hw_len != 0 && - lease->hwaddr_len == hw_len && - lease->hwaddr_type == hw_type && - memcmp(hwaddr, lease->hwaddr, hw_len) == 0) - return lease; - + { +#ifdef HAVE_DHCP6 + if (lease->flags & (LEASE_TA | LEASE_NA)) + continue; +#endif + if ((!lease->clid || !clid) && + hw_len != 0 && + lease->hwaddr_len == hw_len && + lease->hwaddr_type == hw_type && + memcmp(hwaddr, lease->hwaddr, hw_len) == 0) + return lease; + } + return NULL; } @@ -317,27 +579,156 @@ struct dhcp_lease *lease_find_by_addr(struct in_addr addr) struct dhcp_lease *lease; for (lease = leases; lease; lease = lease->next) - if (lease->addr.s_addr == addr.s_addr) + { +#ifdef HAVE_DHCP6 + if (lease->flags & (LEASE_TA | LEASE_NA)) + continue; +#endif + if (lease->addr.s_addr == addr.s_addr) + return lease; + } + + return NULL; +} + +#ifdef HAVE_DHCP6 +/* find address for {CLID, IAID, address} */ +struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, + int lease_type, int iaid, struct in6_addr *addr) +{ + struct dhcp_lease *lease; + + for (lease = leases; lease; lease = lease->next) + { + if (!(lease->flags & lease_type) || lease->iaid != iaid) + continue; + + if (!IN6_ARE_ADDR_EQUAL(&lease->addr6, addr)) + continue; + + if ((clid_len != lease->clid_len || + memcmp(clid, lease->clid, clid_len) != 0)) + continue; + + return lease; + } + + return NULL; +} + +/* reset "USED flags */ +void lease6_reset(void) +{ + struct dhcp_lease *lease; + + for (lease = leases; lease; lease = lease->next) + lease->flags &= ~LEASE_USED; +} + +/* enumerate all leases belonging to {CLID, IAID} */ +struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type, unsigned char *clid, int clid_len, int iaid) +{ + struct dhcp_lease *lease; + + if (!first) + first = leases; + else + first = first->next; + + for (lease = first; lease; lease = lease->next) + { + if (lease->flags & LEASE_USED) + continue; + + if (!(lease->flags & lease_type) || lease->iaid != iaid) + continue; + + if ((clid_len != lease->clid_len || + memcmp(clid, lease->clid, clid_len) != 0)) + continue; + return lease; + } + + return NULL; +} + +struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr) +{ + struct dhcp_lease *lease; + + for (lease = leases; lease; lease = lease->next) + { + if (!(lease->flags & (LEASE_TA | LEASE_NA))) + continue; + + if (is_same_net6(&lease->addr6, net, prefix) && + (prefix == 128 || addr6part(&lease->addr6) == addr)) + return lease; + } return NULL; +} + +/* Find largest assigned address in context */ +u64 lease_find_max_addr6(struct dhcp_context *context) +{ + struct dhcp_lease *lease; + u64 addr = addr6part(&context->start6); + + if (!(context->flags & (CONTEXT_STATIC | CONTEXT_PROXY))) + for (lease = leases; lease; lease = lease->next) + { + if (!(lease->flags & (LEASE_TA | LEASE_NA))) + continue; + + if (is_same_net6(&lease->addr6, &context->start6, 64) && + addr6part(&lease->addr6) > addr6part(&context->start6) && + addr6part(&lease->addr6) <= addr6part(&context->end6) && + addr6part(&lease->addr6) > addr) + addr = addr6part(&lease->addr6); + } + + return addr; } +#endif + +/* Find largest assigned address in context */ +struct in_addr lease_find_max_addr(struct dhcp_context *context) +{ + struct dhcp_lease *lease; + struct in_addr addr = context->start; + + if (!(context->flags & (CONTEXT_STATIC | CONTEXT_PROXY))) + for (lease = leases; lease; lease = lease->next) + { +#ifdef HAVE_DHCP6 + if (lease->flags & (LEASE_TA | LEASE_NA)) + continue; +#endif + if (((unsigned)ntohl(lease->addr.s_addr)) > ((unsigned)ntohl(context->start.s_addr)) && + ((unsigned)ntohl(lease->addr.s_addr)) <= ((unsigned)ntohl(context->end.s_addr)) && + ((unsigned)ntohl(lease->addr.s_addr)) > ((unsigned)ntohl(addr.s_addr))) + addr = lease->addr; + } + + return addr; +} -struct dhcp_lease *lease_allocate(struct in_addr addr) +static struct dhcp_lease *lease_allocate(void) { struct dhcp_lease *lease; if (!leases_left || !(lease = whine_malloc(sizeof(struct dhcp_lease)))) return NULL; memset(lease, 0, sizeof(struct dhcp_lease)); - lease->new = 1; - lease->addr = addr; - lease->hwaddr_len = 256; /* illegal value */ + lease->flags = LEASE_NEW; lease->expires = 1; #ifdef HAVE_BROKEN_RTC lease->length = 0xffffffff; /* illegal value */ #endif + lease->hwaddr_len = 256; /* illegal value */ lease->next = leases; leases = lease; @@ -347,22 +738,57 @@ struct dhcp_lease *lease_allocate(struct in_addr addr) return lease; } -void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now) +struct dhcp_lease *lease4_allocate(struct in_addr addr) { - time_t exp = now + (time_t)len; + struct dhcp_lease *lease = lease_allocate(); + if (lease) + lease->addr = addr; + return lease; +} + +#ifdef HAVE_DHCP6 +struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type) +{ + struct dhcp_lease *lease = lease_allocate(); + + if (lease) + { + lease->addr6 = *addrp; + lease->flags |= lease_type; + lease->iaid = 0; + } + + return lease; +} +#endif + +void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now) +{ + time_t exp; + if (len == 0xffffffff) { exp = 0; len = 0; } - + else + { + exp = now + (time_t)len; + /* Check for 2038 overflow. Make the lease + inifinite in that case, as the least disruptive + thing we can do. */ + if (difftime(exp, now) <= 0.0) + exp = 0; + } + if (exp != lease->expires) { dns_dirty = 1; lease->expires = exp; #ifndef HAVE_BROKEN_RTC - lease->aux_changed = file_dirty = 1; + lease->flags |= LEASE_AUX_CHANGED; + file_dirty = 1; #endif } @@ -370,22 +796,45 @@ void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now) if (len != lease->length) { lease->length = len; - lease->aux_changed = file_dirty = 1; + lease->flags |= LEASE_AUX_CHANGED; + file_dirty = 1; } #endif } -void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr, - unsigned char *clid, int hw_len, int hw_type, int clid_len) +#ifdef HAVE_DHCP6 +void lease_set_iaid(struct dhcp_lease *lease, int iaid) +{ + if (lease->iaid != iaid) + { + lease->iaid = iaid; + lease->flags |= LEASE_CHANGED; + } +} +#endif + +void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr, + const unsigned char *clid, int hw_len, int hw_type, + int clid_len, time_t now, int force) { +#ifdef HAVE_DHCP6 + int change = force; + lease->flags |= LEASE_HAVE_HWADDR; +#endif + + (void)force; + (void)now; + if (hw_len != lease->hwaddr_len || hw_type != lease->hwaddr_type || (hw_len != 0 && memcmp(lease->hwaddr, hwaddr, hw_len) != 0)) { - memcpy(lease->hwaddr, hwaddr, hw_len); + if (hw_len != 0) + memcpy(lease->hwaddr, hwaddr, hw_len); lease->hwaddr_len = hw_len; lease->hwaddr_type = hw_type; - lease->changed = file_dirty = 1; /* run script on change */ + lease->flags |= LEASE_CHANGED; + file_dirty = 1; /* run script on change */ } /* only update clid when one is available, stops packets @@ -398,18 +847,32 @@ void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr, if (lease->clid_len != clid_len) { - lease->aux_changed = file_dirty = 1; + lease->flags |= LEASE_AUX_CHANGED; + file_dirty = 1; free(lease->clid); if (!(lease->clid = whine_malloc(clid_len))) return; +#ifdef HAVE_DHCP6 + change = 1; +#endif } else if (memcmp(lease->clid, clid, clid_len) != 0) - lease->aux_changed = file_dirty = 1; - + { + lease->flags |= LEASE_AUX_CHANGED; + file_dirty = 1; +#ifdef HAVE_DHCP6 + change = 1; +#endif + } + lease->clid_len = clid_len; memcpy(lease->clid, clid, clid_len); } - + +#ifdef HAVE_DHCP6 + if (change) + slaac_add_addrs(lease, now, force); +#endif } static void kill_name(struct dhcp_lease *lease) @@ -421,7 +884,7 @@ static void kill_name(struct dhcp_lease *lease) free(lease->old_hostname); /* If we know the fqdn, pass that. The helper will derive the - unqualified name from it, free the unqulaified name here. */ + unqualified name from it, free the unqualified name here. */ if (lease->fqdn) { @@ -434,14 +897,18 @@ static void kill_name(struct dhcp_lease *lease) lease->hostname = lease->fqdn = NULL; } -void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth) +void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, char *domain, char *config_domain) { struct dhcp_lease *lease_tmp; char *new_name = NULL, *new_fqdn = NULL; + + 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, name); if (lease->hostname && name && hostname_isequal(lease->hostname, name)) { - lease->auth_name = auth; + if (auth) + lease->flags |= LEASE_AUTH_NAME; return; } @@ -451,19 +918,21 @@ void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth) /* If a machine turns up on a new net without dropping the old lease, or two machines claim the same name, then we end up with two interfaces with the same name. Check for that here and remove the name from the old lease. + Note that IPv6 leases are different. All the leases to the same DUID are + allowed the same name. + Don't allow a name from the client to override a name from dnsmasq config. */ if (name) { if ((new_name = whine_malloc(strlen(name) + 1))) { - char *suffix = get_domain(lease->addr); strcpy(new_name, name); - if (suffix && (new_fqdn = whine_malloc(strlen(new_name) + strlen(suffix) + 2))) + if (domain && (new_fqdn = whine_malloc(strlen(new_name) + strlen(domain) + 2))) { strcpy(new_fqdn, name); strcat(new_fqdn, "."); - strcat(new_fqdn, suffix); + strcat(new_fqdn, domain); } } @@ -472,7 +941,7 @@ void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth) { if (option_bool(OPT_DHCP_FQDN)) { - if (!new_fqdn || !lease_tmp->fqdn || !hostname_isequal(lease_tmp->fqdn, new_fqdn) ) + if (!new_fqdn || !lease_tmp->fqdn || !hostname_isequal(lease_tmp->fqdn, new_fqdn)) continue; } else @@ -480,8 +949,22 @@ void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth) if (!new_name || !lease_tmp->hostname || !hostname_isequal(lease_tmp->hostname, new_name) ) continue; } - - if (lease_tmp->auth_name && !auth) + + if (lease->flags & (LEASE_TA | LEASE_NA)) + { + if (!(lease_tmp->flags & (LEASE_TA | LEASE_NA))) + continue; + + /* another lease for the same DUID is OK for IPv6 */ + if (lease->clid_len == lease_tmp->clid_len && + lease->clid && lease_tmp->clid && + memcmp(lease->clid, lease_tmp->clid, lease->clid_len) == 0) + continue; + } + else if (lease_tmp->flags & (LEASE_TA | LEASE_NA)) + continue; + + if ((lease_tmp->flags & LEASE_AUTH_NAME) && !auth) { free(new_name); free(new_fqdn); @@ -498,20 +981,28 @@ void lease_set_hostname(struct dhcp_lease *lease, char *name, int auth) lease->hostname = new_name; lease->fqdn = new_fqdn; - lease->auth_name = auth; + + if (auth) + lease->flags |= LEASE_AUTH_NAME; file_dirty = 1; dns_dirty = 1; - lease->changed = 1; /* run script on change */ + lease->flags |= LEASE_CHANGED; /* run script on change */ } -void lease_set_interface(struct dhcp_lease *lease, int interface) +void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now) { + (void)now; + if (lease->last_interface == interface) return; lease->last_interface = interface; - lease->changed = 1; + lease->flags |= LEASE_CHANGED; + +#ifdef HAVE_DHCP6 + slaac_add_addrs(lease, now, 0); +#endif } void rerun_scripts(void) @@ -519,7 +1010,7 @@ void rerun_scripts(void) struct dhcp_lease *lease; for (lease = leases; lease; lease = lease->next) - lease->changed = 1; + lease->flags |= LEASE_CHANGED; } /* deleted leases get transferred to the old_leases list. @@ -531,6 +1022,8 @@ int do_script_run(time_t now) { struct dhcp_lease *lease; + (void)now; + #ifdef HAVE_DBUS /* If we're going to be sending DBus signals, but the connection is not yet up, delay everything until it is. */ @@ -554,6 +1047,14 @@ int do_script_run(time_t now) } else { +#ifdef HAVE_DHCP6 + struct slaac_address *slaac, *tmp; + for (slaac = lease->slaac_address; slaac; slaac = tmp) + { + tmp = slaac->next; + free(slaac); + } +#endif kill_name(lease); #ifdef HAVE_SCRIPT queue_script(ACTION_DEL, lease, lease->old_hostname, now); @@ -585,18 +1086,18 @@ int do_script_run(time_t now) } for (lease = leases; lease; lease = lease->next) - if (lease->new || lease->changed || - (lease->aux_changed && option_bool(OPT_LEASE_RO))) + if ((lease->flags & (LEASE_NEW | LEASE_CHANGED)) || + ((lease->flags & LEASE_AUX_CHANGED) && option_bool(OPT_LEASE_RO))) { #ifdef HAVE_SCRIPT - queue_script(lease->new ? ACTION_ADD : ACTION_OLD, lease, + queue_script((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease, lease->fqdn ? lease->fqdn : lease->hostname, now); #endif #ifdef HAVE_DBUS - emit_dbus_signal(lease->new ? ACTION_ADD : ACTION_OLD, lease, + emit_dbus_signal((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease, lease->fqdn ? lease->fqdn : lease->hostname); #endif - lease->new = lease->changed = lease->aux_changed = 0; + lease->flags &= ~(LEASE_NEW | LEASE_CHANGED | LEASE_AUX_CHANGED); /* this is used for the "add" call, then junked, since they're not in the database */ free(lease->extradata); @@ -608,6 +1109,44 @@ int do_script_run(time_t now) return 0; /* nothing to do */ } +#ifdef HAVE_SCRIPT +void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, unsigned int len, int delim) +{ + unsigned int i; + + /* check for embeded NULLs */ + for (i = 0; i < len; i++) + if (data[i] == 0) + { + len = i; + break; + } + + 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; +} +#endif + #endif @@ -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 @@ -84,7 +84,7 @@ int log_start(struct passwd *ent_pw, int errfd) if (!log_reopen(daemon->log_file)) { - send_event(errfd, EVENT_LOG_ERR, errno); + send_event(errfd, EVENT_LOG_ERR, errno, daemon->log_file ? daemon->log_file : ""); _exit(0); } @@ -154,6 +154,19 @@ static void log_write(void) while (entries) { + /* The data in the payoad is written with a terminating zero character + and the length reflects this. For a stream connection we need to + send the zero as a record terminator, but this isn't done for a + datagram connection, so treat the length as one less than reality + to elide the zero. If we're logging to a file, turn the zero into + a newline, and leave the length alone. */ + int len_adjust = 0; + + if (log_to_file) + entries->payload[entries->offset + entries->length - 1] = '\n'; + else if (connection_type == SOCK_DGRAM) + len_adjust = 1; + /* Avoid duplicates over a fork() */ if (entries->pid != getpid()) { @@ -163,11 +176,11 @@ static void log_write(void) connection_good = 1; - if ((rc = write(log_fd, entries->payload + entries->offset, entries->length)) != -1) + if ((rc = write(log_fd, entries->payload + entries->offset, entries->length - len_adjust)) != -1) { entries->length -= rc; entries->offset += rc; - if (entries->length == 0) + if (entries->length == len_adjust) { free_entry(); if (entries_lost != 0) @@ -183,7 +196,7 @@ static void log_write(void) if (errno == EINTR) continue; - if (errno == EAGAIN) + if (errno == EAGAIN || errno == EWOULDBLOCK) return; /* syslogd busy, go again when select() or poll() says so */ if (errno == ENOBUFS) @@ -231,7 +244,8 @@ static void log_write(void) errno == ECONNREFUSED || errno == EISCONN || errno == EINTR || - errno == EAGAIN) + errno == EAGAIN || + errno == EWOULDBLOCK) { /* try again on next syslog() call */ connection_good = 0; @@ -366,10 +380,6 @@ void my_syslog(int priority, const char *format, ...) entry->length = len > MAX_MESSAGE ? MAX_MESSAGE : len; entry->offset = 0; entry->pid = pid; - - /* replace terminator with \n */ - if (log_to_file) - entry->payload[entry->length - 1] = '\n'; } /* almost always, logging won't block, so try and write this now, @@ -411,18 +421,15 @@ void my_syslog(int priority, const char *format, ...) } } -void set_log_writer(fd_set *set, int *maxfdp) +void set_log_writer(void) { if (entries && log_fd != -1 && connection_good) - { - FD_SET(log_fd, set); - bump_maxfd(log_fd, maxfdp); - } + poll_listen(log_fd, POLLOUT); } -void check_log_writer(fd_set *set) +void check_log_writer(int force) { - if (log_fd != -1 && (!set || FD_ISSET(log_fd, set))) + if (log_fd != -1 && (force || poll_check(log_fd, POLLOUT))) log_write(); } diff --git a/src/loop.c b/src/loop.c new file mode 100644 index 0000000..c9ed075 --- /dev/null +++ b/src/loop.c @@ -0,0 +1,117 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_LOOP +static ssize_t loop_make_probe(u32 uid); + +void loop_send_probes() +{ + struct server *serv; + + if (!option_bool(OPT_LOOP_DETECT)) + return; + + /* Loop through all upstream servers not for particular domains, and send a query to that server which is + identifiable, via the uid. If we see that query back again, then the server is looping, and we should not use it. */ + for (serv = daemon->servers; serv; serv = serv->next) + if (!(serv->flags & + (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP))) + { + ssize_t len = loop_make_probe(serv->uid); + int fd; + struct randfd *rfd = NULL; + + if (serv->sfd) + fd = serv->sfd->fd; + else + { + if (!(rfd = allocate_rfd(serv->addr.sa.sa_family))) + continue; + fd = rfd->fd; + } + + while (retry_send(sendto(fd, daemon->packet, len, 0, + &serv->addr.sa, sa_len(&serv->addr)))); + + free_rfd(rfd); + } +} + +static ssize_t loop_make_probe(u32 uid) +{ + struct dns_header *header = (struct dns_header *)daemon->packet; + unsigned char *p = (unsigned char *)(header+1); + + /* packet buffer overwritten */ + daemon->srv_save = NULL; + + header->id = rand16(); + header->ancount = header->nscount = header->arcount = htons(0); + header->qdcount = htons(1); + header->hb3 = HB3_RD; + header->hb4 = 0; + SET_OPCODE(header, QUERY); + + *p++ = 8; + sprintf((char *)p, "%.8x", uid); + p += 8; + *p++ = strlen(LOOP_TEST_DOMAIN); + strcpy((char *)p, LOOP_TEST_DOMAIN); /* Add terminating zero */ + p += strlen(LOOP_TEST_DOMAIN) + 1; + + PUTSHORT(LOOP_TEST_TYPE, p); + PUTSHORT(C_IN, p); + + return p - (unsigned char *)header; +} + + +int detect_loop(char *query, int type) +{ + int i; + u32 uid; + struct server *serv; + + if (!option_bool(OPT_LOOP_DETECT)) + return 0; + + if (type != LOOP_TEST_TYPE || + strlen(LOOP_TEST_DOMAIN) + 9 != strlen(query) || + strstr(query, LOOP_TEST_DOMAIN) != query + 9) + return 0; + + for (i = 0; i < 8; i++) + if (!isxdigit(query[i])) + return 0; + + uid = strtol(query, NULL, 16); + + for (serv = daemon->servers; serv; serv = serv->next) + if (!(serv->flags & + (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP)) && + uid == serv->uid) + { + serv->flags |= SERV_LOOP; + check_servers(); /* log new state */ + return 1; + } + + return 0; +} + +#endif diff --git a/src/netlink.c b/src/netlink.c index f6da7db..753784d 100644 --- a/src/netlink.c +++ b/src/netlink.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 @@ -34,11 +34,11 @@ # define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) #endif + static struct iovec iov; static u32 netlink_pid; -static void nl_err(struct nlmsghdr *h); -static void nl_routechange(struct nlmsghdr *h); +static void nl_async(struct nlmsghdr *h); void netlink_init(void) { @@ -48,10 +48,17 @@ void netlink_init(void) addr.nl_family = AF_NETLINK; addr.nl_pad = 0; addr.nl_pid = 0; /* autobind */ -#ifdef HAVE_IPV6 - addr.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE; -#else addr.nl_groups = RTMGRP_IPV4_ROUTE; + if (option_bool(OPT_CLEVERBIND)) + addr.nl_groups |= RTMGRP_IPV4_IFADDR; +#ifdef HAVE_IPV6 + addr.nl_groups |= RTMGRP_IPV6_ROUTE; + if (option_bool(OPT_CLEVERBIND)) + addr.nl_groups |= RTMGRP_IPV6_IFADDR; +#endif +#ifdef HAVE_DHCP6 + if (daemon->doing_ra || daemon->doing_dhcp6) + addr.nl_groups |= RTMGRP_IPV6_IFADDR; #endif /* May not be able to have permission to set multicast groups don't die in that case */ @@ -127,13 +134,15 @@ static ssize_t netlink_recv(void) } -/* family = AF_UNSPEC finds ARP table entries. */ +/* family = AF_UNSPEC finds ARP table entries. + family = AF_LOCAL finds MAC addresses. */ int iface_enumerate(int family, void *parm, int (*callback)()) { struct sockaddr_nl addr; struct nlmsghdr *h; ssize_t len; static unsigned int seq = 0; + int callback_ok = 1; struct { struct nlmsghdr nlh; @@ -144,20 +153,26 @@ int iface_enumerate(int family, void *parm, int (*callback)()) addr.nl_pad = 0; addr.nl_groups = 0; addr.nl_pid = 0; /* address to kernel */ + + again: + if (family == AF_UNSPEC) + req.nlh.nlmsg_type = RTM_GETNEIGH; + else if (family == AF_LOCAL) + req.nlh.nlmsg_type = RTM_GETLINK; + else + req.nlh.nlmsg_type = RTM_GETADDR; - again: req.nlh.nlmsg_len = sizeof(req); - req.nlh.nlmsg_type = family == AF_UNSPEC ? RTM_GETNEIGH : RTM_GETADDR; req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST | NLM_F_ACK; req.nlh.nlmsg_pid = 0; req.nlh.nlmsg_seq = ++seq; req.g.rtgen_family = family; /* Don't block in recvfrom if send fails */ - while((len = sendto(daemon->netlinkfd, (void *)&req, sizeof(req), 0, - (struct sockaddr *)&addr, sizeof(addr))) == -1 && retry_send()); - - if (len == -1) + while(retry_send(sendto(daemon->netlinkfd, (void *)&req, sizeof(req), 0, + (struct sockaddr *)&addr, sizeof(addr)))); + + if (errno != 0) return 0; while (1) @@ -173,13 +188,14 @@ int iface_enumerate(int family, void *parm, int (*callback)()) } for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) - if (h->nlmsg_seq != seq || h->nlmsg_pid != netlink_pid) - nl_routechange(h); /* May be multicast arriving async */ - else if (h->nlmsg_type == NLMSG_ERROR) - nl_err(h); + if (h->nlmsg_seq != seq || h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR) + { + /* May be multicast arriving async */ + nl_async(h); + } else if (h->nlmsg_type == NLMSG_DONE) - return 1; - else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC) + return callback_ok; + else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC && family != AF_LOCAL) { struct ifaddrmsg *ifa = NLMSG_DATA(h); struct rtattr *rta = IFA_RTA(ifa); @@ -190,8 +206,10 @@ int iface_enumerate(int family, void *parm, int (*callback)()) if (ifa->ifa_family == AF_INET) { struct in_addr netmask, addr, broadcast; - - netmask.s_addr = htonl(0xffffffff << (32 - ifa->ifa_prefixlen)); + char *label = NULL; + + netmask.s_addr = htonl(~(in_addr_t)0 << (32 - ifa->ifa_prefixlen)); + addr.s_addr = 0; broadcast.s_addr = 0; @@ -201,29 +219,50 @@ int iface_enumerate(int family, void *parm, int (*callback)()) addr = *((struct in_addr *)(rta+1)); else if (rta->rta_type == IFA_BROADCAST) broadcast = *((struct in_addr *)(rta+1)); + else if (rta->rta_type == IFA_LABEL) + label = RTA_DATA(rta); rta = RTA_NEXT(rta, len1); } - if (addr.s_addr) - if (!((*callback)(addr, ifa->ifa_index, netmask, broadcast, parm))) - return 0; + if (addr.s_addr && callback_ok) + if (!((*callback)(addr, ifa->ifa_index, label, netmask, broadcast, parm))) + callback_ok = 0; } #ifdef HAVE_IPV6 else if (ifa->ifa_family == AF_INET6) { struct in6_addr *addrp = NULL; + u32 valid = 0, preferred = 0; + int flags = 0; + while (RTA_OK(rta, len1)) { if (rta->rta_type == IFA_ADDRESS) addrp = ((struct in6_addr *)(rta+1)); - + else if (rta->rta_type == IFA_CACHEINFO) + { + struct ifa_cacheinfo *ifc = (struct ifa_cacheinfo *)(rta+1); + preferred = ifc->ifa_prefered; + valid = ifc->ifa_valid; + } rta = RTA_NEXT(rta, len1); } - if (addrp) - if (!((*callback)(addrp, ifa->ifa_index, ifa->ifa_index, parm))) - return 0; + if (ifa->ifa_flags & IFA_F_TENTATIVE) + flags |= IFACE_TENTATIVE; + + if (ifa->ifa_flags & IFA_F_DEPRECATED) + flags |= IFACE_DEPRECATED; + + if (!(ifa->ifa_flags & IFA_F_TEMPORARY)) + flags |= IFACE_PERMANENT; + + if (addrp && callback_ok) + if (!((*callback)(addrp, (int)(ifa->ifa_prefixlen), (int)(ifa->ifa_scope), + (int)(ifa->ifa_index), flags, + (int) preferred, (int)valid, parm))) + callback_ok = 0; } #endif } @@ -249,10 +288,35 @@ int iface_enumerate(int family, void *parm, int (*callback)()) rta = RTA_NEXT(rta, len1); } - if (inaddr && mac) + if (inaddr && mac && callback_ok) if (!((*callback)(neigh->ndm_family, inaddr, mac, maclen, parm))) - return 0; - } + callback_ok = 0; + } +#ifdef HAVE_DHCP6 + else if (h->nlmsg_type == RTM_NEWLINK && family == AF_LOCAL) + { + struct ifinfomsg *link = NLMSG_DATA(h); + struct rtattr *rta = IFLA_RTA(link); + unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*link)); + char *mac = NULL; + size_t maclen = 0; + + while (RTA_OK(rta, len1)) + { + if (rta->rta_type == IFLA_ADDRESS) + { + maclen = rta->rta_len - sizeof(struct rtattr); + mac = (char *)(rta+1); + } + + rta = RTA_NEXT(rta, len1); + } + + if (mac && callback_ok && !((link->ifi_flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) && + !((*callback)((int)link->ifi_index, (unsigned int)link->ifi_type, mac, maclen, parm))) + callback_ok = 0; + } +#endif } } @@ -268,59 +332,36 @@ void netlink_multicast(void) return; if ((len = netlink_recv()) != -1) - { - for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) - if (h->nlmsg_type == NLMSG_ERROR) - nl_err(h); - else - nl_routechange(h); - } - - /* restore non-blocking status */ - fcntl(daemon->netlinkfd, F_SETFL, flags); -} - -static void nl_err(struct nlmsghdr *h) -{ - struct nlmsgerr *err = NLMSG_DATA(h); + for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) + nl_async(h); - if (err->error != 0) - my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error))); + /* restore non-blocking status */ + fcntl(daemon->netlinkfd, F_SETFL, flags); } -/* We arrange to receive netlink multicast messages whenever the network route is added. - If this happens and we still have a DNS packet in the buffer, we re-send it. - This helps on DoD links, where frequently the packet which triggers dialling is - a DNS query, which then gets lost. By re-sending, we can avoid the lookup - failing. Note that we only accept these messages from the kernel (pid == 0) */ -static void nl_routechange(struct nlmsghdr *h) +static void nl_async(struct nlmsghdr *h) { - if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) + if (h->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *err = NLMSG_DATA(h); + if (err->error != 0) + my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error))); + } + else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) { + /* We arrange to receive netlink multicast messages whenever the network route is added. + If this happens and we still have a DNS packet in the buffer, we re-send it. + This helps on DoD links, where frequently the packet which triggers dialling is + a DNS query, which then gets lost. By re-sending, we can avoid the lookup + failing. */ struct rtmsg *rtm = NLMSG_DATA(h); - int fd; - - if (rtm->rtm_type != RTN_UNICAST || rtm->rtm_scope != RT_SCOPE_LINK) - return; - - /* Force re-reading resolv file right now, for luck. */ - daemon->last_resolv = 0; - if (daemon->srv_save) - { - if (daemon->srv_save->sfd) - fd = daemon->srv_save->sfd->fd; - else if (daemon->rfd_save && daemon->rfd_save->refcount != 0) - fd = daemon->rfd_save->fd; - else - return; - - while(sendto(fd, daemon->packet, daemon->packet_len, 0, - &daemon->srv_save->addr.sa, sa_len(&daemon->srv_save->addr)) == -1 && retry_send()); - } + if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK) + queue_event(EVENT_NEWROUTE); } + else if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) + queue_event(EVENT_NEWADDR); } - #endif diff --git a/src/network.c b/src/network.c index 7b2e905..a1d90c8 100644 --- a/src/network.c +++ b/src/network.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 @@ -99,6 +99,8 @@ int indextoname(int fd, int index, char *name) int indextoname(int fd, int index, char *name) { + (void)fd; + if (index == 0 || !if_indextoname(index, name)) return 0; @@ -107,187 +109,388 @@ int indextoname(int fd, int index, char *name) #endif -int iface_check(int family, struct all_addr *addr, char *name, int *indexp) +int iface_check(int family, struct all_addr *addr, char *name, int *auth) { struct iname *tmp; - int ret = 1; + int ret = 1, match_addr = 0; /* Note: have to check all and not bail out early, so that we set the - "used" flags. */ + "used" flags. + + May be called with family == AF_LOCALto check interface by name only. */ - if (daemon->if_names || (addr && daemon->if_addrs)) + if (auth) + *auth = 0; + + if (daemon->if_names || daemon->if_addrs) { -#ifdef HAVE_DHCP - struct dhcp_context *range; -#endif - ret = 0; -#ifdef HAVE_DHCP - for (range = daemon->dhcp; range; range = range->next) - if (range->interface && strcmp(range->interface, name) == 0) - ret = 1; -#endif - for (tmp = daemon->if_names; tmp; tmp = tmp->next) - if (tmp->name && (strcmp(tmp->name, name) == 0)) + if (tmp->name && wildcard_match(tmp->name, name)) ret = tmp->used = 1; - for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) - if (addr && tmp->addr.sa.sa_family == family) - { - if (family == AF_INET && - tmp->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) - ret = tmp->used = 1; + if (addr) + for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) + if (tmp->addr.sa.sa_family == family) + { + if (family == AF_INET && + tmp->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) + ret = match_addr = tmp->used = 1; #ifdef HAVE_IPV6 - else if (family == AF_INET6 && - IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, - &addr->addr.addr6)) - ret = tmp->used = 1; + else if (family == AF_INET6 && + IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, + &addr->addr.addr6)) + ret = match_addr = tmp->used = 1; #endif - } + } } - for (tmp = daemon->if_except; tmp; tmp = tmp->next) - if (tmp->name && (strcmp(tmp->name, name) == 0)) - ret = 0; - - if (indexp) + if (!match_addr) + for (tmp = daemon->if_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, name)) + ret = 0; + + + for (tmp = daemon->authinterface; tmp; tmp = tmp->next) + if (tmp->name) + { + if (strcmp(tmp->name, name) == 0 && + (tmp->addr.sa.sa_family == 0 || tmp->addr.sa.sa_family == family)) + break; + } + else if (addr && tmp->addr.sa.sa_family == AF_INET && family == AF_INET && + tmp->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) + break; +#ifdef HAVE_IPV6 + else if (addr && tmp->addr.sa.sa_family == AF_INET6 && family == AF_INET6 && + IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, &addr->addr.addr6)) + break; +#endif + + if (tmp && auth) { - /* One form of bridging on BSD has the property that packets - can be recieved on bridge interfaces which do not have an IP address. - We allow these to be treated as aliases of another interface which does have - an IP address with --dhcp-bridge=interface,alias,alias */ - struct dhcp_bridge *bridge, *alias; - for (bridge = daemon->bridges; bridge; bridge = bridge->next) - { - for (alias = bridge->alias; alias; alias = alias->next) - if (strncmp(name, alias->iface, IF_NAMESIZE) == 0) + *auth = 1; + ret = 1; + } + + return ret; +} + + +/* Fix for problem that the kernel sometimes reports the loopback inerface as the + arrival interface when a packet originates locally, even when sent to address of + an interface other than the loopback. Accept packet if it arrived via a loopback + interface, even when we're not accepting packets that way, as long as the destination + address is one we're believing. Interface list must be up-to-date before calling. */ +int loopback_exception(int fd, int family, struct all_addr *addr, char *name) +{ + struct ifreq ifr; + struct irec *iface; + + strncpy(ifr.ifr_name, name, IF_NAMESIZE); + if (ioctl(fd, SIOCGIFFLAGS, &ifr) != -1 && + ifr.ifr_flags & IFF_LOOPBACK) + { + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->addr.sa.sa_family == family) + { + if (family == AF_INET) { - int newindex; - - if (!(newindex = if_nametoindex(bridge->iface))) - { - my_syslog(LOG_WARNING, _("unknown interface %s in bridge-interface"), name); - return 0; - } - else - { - *indexp = newindex; - strncpy(name, bridge->iface, IF_NAMESIZE); - break; - } + if (iface->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) + return 1; } - if (alias) - break; - } +#ifdef HAVE_IPV6 + else if (IN6_ARE_ADDR_EQUAL(&iface->addr.in6.sin6_addr, &addr->addr.addr6)) + return 1; +#endif + + } } - - return ret; + return 0; } - -static int iface_allowed(struct irec **irecp, int if_index, - union mysockaddr *addr, struct in_addr netmask) + +/* If we're configured with something like --interface=eth0:0 then we'll listen correctly + on the relevant address, but the name of the arrival interface, derived from the + index won't match the config. Check that we found an interface address for the arrival + interface: daemon->interfaces must be up-to-date. */ +int label_exception(int index, int family, struct all_addr *addr) { struct irec *iface; - int fd, mtu = 0, loopback; + + /* labels only supported on IPv4 addresses. */ + if (family != AF_INET) + return 0; + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->index == index && iface->addr.sa.sa_family == AF_INET && + iface->addr.in.sin_addr.s_addr == addr->addr.addr4.s_addr) + return 1; + + return 0; +} + +struct iface_param { + struct addrlist *spare; + int fd; +}; + +static int iface_allowed(struct iface_param *param, int if_index, char *label, + union mysockaddr *addr, struct in_addr netmask, int prefixlen, int iface_flags) +{ + struct irec *iface; + int mtu = 0, loopback; struct ifreq ifr; - int tftp_ok = daemon->tftp_unlimited; -#ifdef HAVE_DHCP + int tftp_ok = !!option_bool(OPT_TFTP); + int dhcp_ok = 1; + int auth_dns = 0; +#if defined(HAVE_DHCP) || defined(HAVE_TFTP) struct iname *tmp; #endif - struct interface_list *ir = NULL; - /* check whether the interface IP has been added already - we call this routine multiple times. */ - for (iface = *irecp; iface; iface = iface->next) - if (sockaddr_isequal(&iface->addr, addr)) - return 1; + (void)prefixlen; + + if (!indextoname(param->fd, if_index, ifr.ifr_name) || + ioctl(param->fd, SIOCGIFFLAGS, &ifr) == -1) + return 0; + + loopback = ifr.ifr_flags & IFF_LOOPBACK; + + if (loopback) + dhcp_ok = 0; - if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1 || - !indextoname(fd, if_index, ifr.ifr_name) || - ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) + if (ioctl(param->fd, SIOCGIFMTU, &ifr) != -1) + mtu = ifr.ifr_mtu; + + if (!label) + label = ifr.ifr_name; + + /* maintain a list of all addresses on all interfaces for --local-service option */ + if (option_bool(OPT_LOCAL_SERVICE)) { - if (fd != -1) + struct addrlist *al; + + if (param->spare) { - int errsave = errno; - close(fd); - errno = errsave; + al = param->spare; + param->spare = al->next; + } + else + al = whine_malloc(sizeof(struct addrlist)); + + if (al) + { + al->next = daemon->interface_addrs; + daemon->interface_addrs = al; + al->prefixlen = prefixlen; + + if (addr->sa.sa_family == AF_INET) + { + al->addr.addr.addr4 = addr->in.sin_addr; + al->flags = 0; + } +#ifdef HAVE_IPV6 + else + { + al->addr.addr.addr6 = addr->in6.sin6_addr; + al->flags = ADDRLIST_IPV6; + } +#endif } - return 0; } - - loopback = ifr.ifr_flags & IFF_LOOPBACK; - - if (ioctl(fd, SIOCGIFMTU, &ifr) != -1) - mtu = ifr.ifr_mtu; - - close(fd); - /* If we are restricting the set of interfaces to use, make +#ifdef HAVE_IPV6 + if (addr->sa.sa_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr)) +#endif + { + struct interface_name *int_name; + struct addrlist *al; +#ifdef HAVE_AUTH + struct auth_zone *zone; + struct auth_name_list *name; + + /* Find subnets in auth_zones */ + for (zone = daemon->auth_zones; zone; zone = zone->next) + for (name = zone->interface_names; name; name = name->next) + if (wildcard_match(name->name, label)) + { + if (addr->sa.sa_family == AF_INET && (name->flags & AUTH4)) + { + if (param->spare) + { + al = param->spare; + param->spare = al->next; + } + else + al = whine_malloc(sizeof(struct addrlist)); + + if (al) + { + al->next = zone->subnet; + zone->subnet = al; + al->prefixlen = prefixlen; + al->addr.addr.addr4 = addr->in.sin_addr; + al->flags = 0; + } + } + +#ifdef HAVE_IPV6 + if (addr->sa.sa_family == AF_INET6 && (name->flags & AUTH6)) + { + if (param->spare) + { + al = param->spare; + param->spare = al->next; + } + else + al = whine_malloc(sizeof(struct addrlist)); + + if (al) + { + al->next = zone->subnet; + zone->subnet = al; + al->prefixlen = prefixlen; + al->addr.addr.addr6 = addr->in6.sin6_addr; + al->flags = ADDRLIST_IPV6; + } + } +#endif + + } +#endif + + /* Update addresses from interface_names. These are a set independent + of the set we're listening on. */ + for (int_name = daemon->int_names; int_name; int_name = int_name->next) + if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0 && + (addr->sa.sa_family == int_name->family || int_name->family == 0)) + { + if (param->spare) + { + al = param->spare; + param->spare = al->next; + } + else + al = whine_malloc(sizeof(struct addrlist)); + + if (al) + { + al->next = int_name->addr; + int_name->addr = al; + + if (addr->sa.sa_family == AF_INET) + { + al->addr.addr.addr4 = addr->in.sin_addr; + al->flags = 0; + } +#ifdef HAVE_IPV6 + else + { + al->addr.addr.addr6 = addr->in6.sin6_addr; + al->flags = ADDRLIST_IPV6; + /* Privacy addresses and addresses still undergoing DAD and deprecated addresses + don't appear in forward queries, but will in reverse ones. */ + if (!(iface_flags & IFACE_PERMANENT) || (iface_flags & (IFACE_DEPRECATED | IFACE_TENTATIVE))) + al->flags |= ADDRLIST_REVONLY; + } +#endif + } + } + } + + /* check whether the interface IP has been added already + we call this routine multiple times. */ + for (iface = daemon->interfaces; iface; iface = iface->next) + if (sockaddr_isequal(&iface->addr, addr)) + { + iface->dad = !!(iface_flags & IFACE_TENTATIVE); + iface->found = 1; /* for garbage collection */ + return 1; + } + + /* If we are restricting the set of interfaces to use, make sure that loopback interfaces are in that set. */ if (daemon->if_names && loopback) { struct iname *lo; for (lo = daemon->if_names; lo; lo = lo->next) if (lo->name && strcmp(lo->name, ifr.ifr_name) == 0) - { - lo->isloop = 1; - break; - } + break; - if (!lo && - (lo = whine_malloc(sizeof(struct iname))) && - (lo->name = whine_malloc(strlen(ifr.ifr_name)+1))) + if (!lo && (lo = whine_malloc(sizeof(struct iname)))) { - strcpy(lo->name, ifr.ifr_name); - lo->isloop = lo->used = 1; - lo->next = daemon->if_names; - daemon->if_names = lo; + if ((lo->name = whine_malloc(strlen(ifr.ifr_name)+1))) + { + strcpy(lo->name, ifr.ifr_name); + lo->used = 1; + lo->next = daemon->if_names; + daemon->if_names = lo; + } + else + free(lo); } } -#ifdef HAVE_TFTP - /* implement wierd TFTP service rules */ - for (ir = daemon->tftp_interfaces; ir; ir = ir->next) - if (strcmp(ir->interface, ifr.ifr_name) == 0) - { - tftp_ok = 1; - break; - } + if (addr->sa.sa_family == AF_INET && + !iface_check(AF_INET, (struct all_addr *)&addr->in.sin_addr, label, &auth_dns)) + return 1; + +#ifdef HAVE_IPV6 + if (addr->sa.sa_family == AF_INET6 && + !iface_check(AF_INET6, (struct all_addr *)&addr->in6.sin6_addr, label, &auth_dns)) + return 1; #endif - - if (!ir) - { - if (addr->sa.sa_family == AF_INET && - !iface_check(AF_INET, (struct all_addr *)&addr->in.sin_addr, ifr.ifr_name, NULL)) - return 1; - + #ifdef HAVE_DHCP - for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) - if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0)) + /* No DHCP where we're doing auth DNS. */ + if (auth_dns) + { + tftp_ok = 0; + dhcp_ok = 0; + } + else + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + { tftp_ok = 0; + dhcp_ok = 0; + } #endif - -#ifdef HAVE_IPV6 - if (addr->sa.sa_family == AF_INET6 && - !iface_check(AF_INET6, (struct all_addr *)&addr->in6.sin6_addr, ifr.ifr_name, NULL)) - return 1; -#endif + + +#ifdef HAVE_TFTP + if (daemon->tftp_interfaces) + { + /* dedicated tftp interface list */ + tftp_ok = 0; + for (tmp = daemon->tftp_interfaces; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + tftp_ok = 1; } - +#endif + /* add to list */ if ((iface = whine_malloc(sizeof(struct irec)))) { iface->addr = *addr; iface->netmask = netmask; iface->tftp_ok = tftp_ok; + iface->dhcp_ok = dhcp_ok; + iface->dns_auth = auth_dns; iface->mtu = mtu; + iface->dad = !!(iface_flags & IFACE_TENTATIVE); + iface->found = 1; + iface->done = iface->multicast_done = iface->warned = 0; + iface->index = if_index; if ((iface->name = whine_malloc(strlen(ifr.ifr_name)+1))) - strcpy(iface->name, ifr.ifr_name); - iface->next = *irecp; - *irecp = iface; - return 1; + { + strcpy(iface->name, ifr.ifr_name); + iface->next = daemon->interfaces; + daemon->interfaces = iface; + return 1; + } + free(iface); + } errno = ENOMEM; @@ -295,13 +498,17 @@ static int iface_allowed(struct irec **irecp, int if_index, } #ifdef HAVE_IPV6 -static int iface_allowed_v6(struct in6_addr *local, - int scope, int if_index, void *vparam) +static int iface_allowed_v6(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, + int preferred, int valid, void *vparam) { union mysockaddr addr; struct in_addr netmask; /* dummy */ - netmask.s_addr = 0; + + (void)scope; /* warning */ + (void)preferred; + (void)valid; memset(&addr, 0, sizeof(addr)); #ifdef HAVE_SOCKADDR_SA_LEN @@ -310,16 +517,21 @@ static int iface_allowed_v6(struct in6_addr *local, addr.in6.sin6_family = AF_INET6; addr.in6.sin6_addr = *local; addr.in6.sin6_port = htons(daemon->port); - addr.in6.sin6_scope_id = scope; + /* FreeBSD insists this is zero for non-linklocal addresses */ + if (IN6_IS_ADDR_LINKLOCAL(local)) + addr.in6.sin6_scope_id = if_index; + else + addr.in6.sin6_scope_id = 0; - return iface_allowed((struct irec **)vparam, if_index, &addr, netmask); + return iface_allowed((struct iface_param *)vparam, if_index, NULL, &addr, netmask, prefix, flags); } #endif -static int iface_allowed_v4(struct in_addr local, int if_index, +static int iface_allowed_v4(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam) { union mysockaddr addr; + int prefix, bit; memset(&addr, 0, sizeof(addr)); #ifdef HAVE_SOCKADDR_SA_LEN @@ -330,17 +542,141 @@ static int iface_allowed_v4(struct in_addr local, int if_index, addr.in.sin_addr = local; addr.in.sin_port = htons(daemon->port); - return iface_allowed((struct irec **)vparam, if_index, &addr, netmask); + /* determine prefix length from netmask */ + for (prefix = 32, bit = 1; (bit & ntohl(netmask.s_addr)) == 0 && prefix != 0; bit = bit << 1, prefix--); + + return iface_allowed((struct iface_param *)vparam, if_index, label, &addr, netmask, prefix, 0); } -int enumerate_interfaces(void) +int enumerate_interfaces(int reset) { + static struct addrlist *spare = NULL; + static int done = 0; + struct iface_param param; + int errsave, ret = 1; + struct addrlist *addr, *tmp; + struct interface_name *intname; + struct irec *iface; +#ifdef HAVE_AUTH + struct auth_zone *zone; +#endif + + /* Do this max once per select cycle - also inhibits netlink socket use + in TCP child processes. */ + + if (reset) + { + done = 0; + return 1; + } + + if (done) + return 1; + + done = 1; + + if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return 0; + + /* Mark interfaces for garbage collection */ + for (iface = daemon->interfaces; iface; iface = iface->next) + iface->found = 0; + + /* remove addresses stored against interface_names */ + for (intname = daemon->int_names; intname; intname = intname->next) + { + for (addr = intname->addr; addr; addr = tmp) + { + tmp = addr->next; + addr->next = spare; + spare = addr; + } + + intname->addr = NULL; + } + + /* Remove list of addresses of local interfaces */ + for (addr = daemon->interface_addrs; addr; addr = tmp) + { + tmp = addr->next; + addr->next = spare; + spare = addr; + } + daemon->interface_addrs = NULL; + +#ifdef HAVE_AUTH + /* remove addresses stored against auth_zone subnets, but not + ones configured as address literals */ + for (zone = daemon->auth_zones; zone; zone = zone->next) + if (zone->interface_names) + { + struct addrlist **up; + for (up = &zone->subnet, addr = zone->subnet; addr; addr = tmp) + { + tmp = addr->next; + if (addr->flags & ADDRLIST_LITERAL) + up = &addr->next; + else + { + *up = addr->next; + addr->next = spare; + spare = addr; + } + } + } +#endif + + param.spare = spare; + #ifdef HAVE_IPV6 - if (!iface_enumerate(AF_INET6, &daemon->interfaces, iface_allowed_v6)) - return 0; + ret = iface_enumerate(AF_INET6, ¶m, iface_allowed_v6); #endif - return iface_enumerate(AF_INET, &daemon->interfaces, iface_allowed_v4); + if (ret) + ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); + + errsave = errno; + close(param.fd); + + if (option_bool(OPT_CLEVERBIND)) + { + /* Garbage-collect listeners listening on addresses that no longer exist. + Does nothing when not binding interfaces or for listeners on localhost, + since the ->iface field is NULL. Note that this needs the protections + against re-entrancy, hence it's here. It also means there's a possibility, + in OPT_CLEVERBIND mode, that at listener will just disappear after + a call to enumerate_interfaces, this is checked OK on all calls. */ + struct listener *l, *tmp, **up; + + for (up = &daemon->listeners, l = daemon->listeners; l; l = tmp) + { + tmp = l->next; + + if (!l->iface || l->iface->found) + up = &l->next; + else + { + *up = l->next; + + /* In case it ever returns */ + l->iface->done = 0; + + if (l->fd != -1) + close(l->fd); + if (l->tcpfd != -1) + close(l->tcpfd); + if (l->tftpfd != -1) + close(l->tftpfd); + + free(l); + } + } + } + + errno = errsave; + spare = param.spare; + + return ret; } /* set NONBLOCK bit on fd: See Stevens 16.6 */ @@ -355,17 +691,15 @@ int fix_fd(int fd) return 1; } -static int make_sock(union mysockaddr *addr, int type) +static int make_sock(union mysockaddr *addr, int type, int dienow) { int family = addr->sa.sa_family; int fd, rc, opt = 1; -#ifdef HAVE_IPV6 - static int dad_count = 0; -#endif if ((fd = socket(family, type, 0)) == -1) { - int port; + int port, errsav; + char *s; /* No error if the kernel just doesn't support this IP flavour */ if (errno == EPROTONOSUPPORT || @@ -374,42 +708,39 @@ static int make_sock(union mysockaddr *addr, int type) return -1; err: - port = prettyprint_addr(addr, daemon->namebuff); - if (!option_bool(OPT_NOWILD)) - sprintf(daemon->namebuff, "port %d", port); - die(_("failed to create listening socket for %s: %s"), - daemon->namebuff, EC_BADNET); - } + errsav = errno; + port = prettyprint_addr(addr, daemon->addrbuff); + if (!option_bool(OPT_NOWILD) && !option_bool(OPT_CLEVERBIND)) + sprintf(daemon->addrbuff, "port %d", port); + s = _("failed to create listening socket for %s: %s"); + + if (fd != -1) + close (fd); + + errno = errsav; + if (dienow) + { + /* failure to bind addresses given by --listen-address at this point + is OK if we're doing bind-dynamic */ + if (!option_bool(OPT_CLEVERBIND)) + die(s, daemon->addrbuff, EC_BADNET); + } + else + my_syslog(LOG_WARNING, s, daemon->addrbuff, strerror(errno)); + + return -1; + } + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || !fix_fd(fd)) goto err; #ifdef HAVE_IPV6 - if (family == AF_INET6 && setsockopt(fd, IPV6_LEVEL, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) + if (family == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) goto err; #endif - while (1) - { - if ((rc = bind(fd, (struct sockaddr *)addr, sa_len(addr))) != -1) - break; - -#ifdef HAVE_IPV6 - /* An interface may have an IPv6 address which is still undergoing DAD. - If so, the bind will fail until the DAD completes, so we try over 20 seconds - before failing. */ - if (family == AF_INET6 && - (errno == ENODEV || errno == EADDRNOTAVAIL) && - dad_count++ < DAD_WAIT) - { - sleep(1); - continue; - } -#endif - break; - } - - if (rc == -1) + if ((rc = bind(fd, (struct sockaddr *)addr, sa_len(addr))) == -1) goto err; if (type == SOCK_STREAM) @@ -417,12 +748,12 @@ static int make_sock(union mysockaddr *addr, int type) if (listen(fd, 5) == -1) goto err; } - else if (!option_bool(OPT_NOWILD)) + else if (family == AF_INET) { - if (family == AF_INET) + if (!option_bool(OPT_NOWILD)) { #if defined(HAVE_LINUX_NETWORK) - if (setsockopt(fd, SOL_IP, IP_PKTINFO, &opt, sizeof(opt)) == -1) + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) == -1) goto err; #elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt)) == -1 || @@ -430,47 +761,126 @@ static int make_sock(union mysockaddr *addr, int type) goto err; #endif } + } #ifdef HAVE_IPV6 - else - { - /* The API changed around Linux 2.6.14 but the old ABI is still supported: - handle all combinations of headers and kernel. - OpenWrt note that this fixes the problem addressed by your very broken patch. */ - daemon->v6pktinfo = IPV6_PKTINFO; - -# ifdef IPV6_RECVPKTINFO -# ifdef IPV6_2292PKTINFO - if (setsockopt(fd, IPV6_LEVEL, IPV6_RECVPKTINFO, &opt, sizeof(opt)) == -1) - { - if (errno == ENOPROTOOPT && setsockopt(fd, IPV6_LEVEL, IPV6_2292PKTINFO, &opt, sizeof(opt)) != -1) - daemon->v6pktinfo = IPV6_2292PKTINFO; - else - goto err; - } -# else - if (setsockopt(fd, IPV6_LEVEL, IPV6_RECVPKTINFO, &opt, sizeof(opt)) == -1) - goto err; -# endif -# else - if (setsockopt(fd, IPV6_LEVEL, IPV6_PKTINFO, &opt, sizeof(opt)) == -1) - goto err; -# endif - } + else if (!set_ipv6pktinfo(fd)) + goto err; #endif + + return fd; +} + +#ifdef HAVE_IPV6 +int set_ipv6pktinfo(int fd) +{ + int opt = 1; + + /* The API changed around Linux 2.6.14 but the old ABI is still supported: + handle all combinations of headers and kernel. + OpenWrt note that this fixes the problem addressed by your very broken patch. */ + daemon->v6pktinfo = IPV6_PKTINFO; + +#ifdef IPV6_RECVPKTINFO + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof(opt)) != -1) + return 1; +# ifdef IPV6_2292PKTINFO + else if (errno == ENOPROTOOPT && setsockopt(fd, IPPROTO_IPV6, IPV6_2292PKTINFO, &opt, sizeof(opt)) != -1) + { + daemon->v6pktinfo = IPV6_2292PKTINFO; + return 1; } +# endif +#else + if (setsockopt(fd, IPPROTO_IPV6, IPV6_PKTINFO, &opt, sizeof(opt)) != -1) + return 1; +#endif - return fd; + return 0; +} +#endif + + +/* Find the interface on which a TCP connection arrived, if possible, or zero otherwise. */ +int tcp_interface(int fd, int af) +{ + int if_index = 0; + +#ifdef HAVE_LINUX_NETWORK + int opt = 1; + struct cmsghdr *cmptr; + struct msghdr msg; + + /* use mshdr do that the CMSDG_* macros are available */ + msg.msg_control = daemon->packet; + msg.msg_controllen = daemon->packet_buff_sz; + + /* we overwrote the buffer... */ + daemon->srv_save = NULL; + + if (af == AF_INET) + { + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 && + getsockopt(fd, IPPROTO_IP, IP_PKTOPTIONS, msg.msg_control, (socklen_t *)&msg.msg_controllen) != -1) + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) + { + union { + unsigned char *c; + struct in_pktinfo *p; + } p; + + p.c = CMSG_DATA(cmptr); + if_index = p.p->ipi_ifindex; + } + } +#ifdef HAVE_IPV6 + else + { + /* Only the RFC-2292 API has the ability to find the interface for TCP connections, + it was removed in RFC-3542 !!!! + + Fortunately, Linux kept the 2292 ABI when it moved to 3542. The following code always + uses the old ABI, and should work with pre- and post-3542 kernel headers */ + +#ifdef IPV6_2292PKTOPTIONS +# define PKTOPTIONS IPV6_2292PKTOPTIONS +#else +# define PKTOPTIONS IPV6_PKTOPTIONS +#endif + + if (set_ipv6pktinfo(fd) && + getsockopt(fd, IPPROTO_IPV6, PKTOPTIONS, msg.msg_control, (socklen_t *)&msg.msg_controllen) != -1) + { + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) + { + union { + unsigned char *c; + struct in6_pktinfo *p; + } p; + p.c = CMSG_DATA(cmptr); + + if_index = p.p->ipi6_ifindex; + } + } + } +#endif /* IPV6 */ +#endif /* Linux */ + + return if_index; } -static struct listener *create_listeners(union mysockaddr *addr, int do_tftp) +static struct listener *create_listeners(union mysockaddr *addr, int do_tftp, int dienow) { struct listener *l = NULL; int fd = -1, tcpfd = -1, tftpfd = -1; + (void)do_tftp; + if (daemon->port != 0) { - fd = make_sock(addr, SOCK_DGRAM); - tcpfd = make_sock(addr, SOCK_STREAM); + fd = make_sock(addr, SOCK_DGRAM, dienow); + tcpfd = make_sock(addr, SOCK_STREAM, dienow); } #ifdef HAVE_TFTP @@ -481,7 +891,7 @@ static struct listener *create_listeners(union mysockaddr *addr, int do_tftp) /* port must be restored to DNS port for TCP code */ short save = addr->in.sin_port; addr->in.sin_port = htons(TFTP_PORT); - tftpfd = make_sock(addr, SOCK_DGRAM); + tftpfd = make_sock(addr, SOCK_DGRAM, dienow); addr->in.sin_port = save; } # ifdef HAVE_IPV6 @@ -489,7 +899,7 @@ static struct listener *create_listeners(union mysockaddr *addr, int do_tftp) { short save = addr->in6.sin6_port; addr->in6.sin6_port = htons(TFTP_PORT); - tftpfd = make_sock(addr, SOCK_DGRAM); + tftpfd = make_sock(addr, SOCK_DGRAM, dienow); addr->in6.sin6_port = save; } # endif @@ -503,17 +913,17 @@ static struct listener *create_listeners(union mysockaddr *addr, int do_tftp) l->family = addr->sa.sa_family; l->fd = fd; l->tcpfd = tcpfd; - l->tftpfd = tftpfd; + l->tftpfd = tftpfd; + l->iface = NULL; } return l; } -struct listener *create_wildcard_listeners(void) +void create_wildcard_listeners(void) { union mysockaddr addr; - struct listener *l; - int tftp_enabled = daemon->tftp_unlimited || daemon->tftp_interfaces; + struct listener *l, *l6; memset(&addr, 0, sizeof(addr)); #ifdef HAVE_SOCKADDR_SA_LEN @@ -523,7 +933,7 @@ struct listener *create_wildcard_listeners(void) addr.in.sin_addr.s_addr = INADDR_ANY; addr.in.sin_port = htons(daemon->port); - l = create_listeners(&addr, tftp_enabled); + l = create_listeners(&addr, !!option_bool(OPT_TFTP), 1); #ifdef HAVE_IPV6 memset(&addr, 0, sizeof(addr)); @@ -533,32 +943,165 @@ struct listener *create_wildcard_listeners(void) addr.in6.sin6_family = AF_INET6; addr.in6.sin6_addr = in6addr_any; addr.in6.sin6_port = htons(daemon->port); - + + l6 = create_listeners(&addr, !!option_bool(OPT_TFTP), 1); if (l) - l->next = create_listeners(&addr, tftp_enabled); + l->next = l6; else - l = create_listeners(&addr, tftp_enabled); + l = l6; #endif - return l; + daemon->listeners = l; } -struct listener *create_bound_listeners(void) +void create_bound_listeners(int dienow) { - struct listener *new, *listeners = NULL; + struct listener *new; struct irec *iface; + struct iname *if_tmp; for (iface = daemon->interfaces; iface; iface = iface->next) - if ((new = create_listeners(&iface->addr, iface->tftp_ok))) + if (!iface->done && !iface->dad && iface->found && + (new = create_listeners(&iface->addr, iface->tftp_ok, dienow))) { new->iface = iface; - new->next = listeners; - listeners = new; + new->next = daemon->listeners; + daemon->listeners = new; + iface->done = 1; + } + + /* Check for --listen-address options that haven't been used because there's + no interface with a matching address. These may be valid: eg it's possible + to listen on 127.0.1.1 even if the loopback interface is 127.0.0.1 + + If the address isn't valid the bind() will fail and we'll die() + (except in bind-dynamic mode, when we'll complain but keep trying.) + + The resulting listeners have the ->iface field NULL, and this has to be + handled by the DNS and TFTP code. It disables --localise-queries processing + (no netmask) and some MTU login the tftp code. */ + + for (if_tmp = daemon->if_addrs; if_tmp; if_tmp = if_tmp->next) + if (!if_tmp->used && + (new = create_listeners(&if_tmp->addr, !!option_bool(OPT_TFTP), dienow))) + { + new->next = daemon->listeners; + daemon->listeners = new; + } +} + +/* In --bind-interfaces, the only access control is the addresses we're listening on. + There's nothing to avoid a query to the address of an internal interface arriving via + an external interface where we don't want to accept queries, except that in the usual + case the addresses of internal interfaces are RFC1918. When bind-interfaces in use, + and we listen on an address that looks like it's probably globally routeable, shout. + + The fix is to use --bind-dynamic, which actually checks the arrival interface too. + Tough if your platform doesn't support this. + + Note that checking the arrival interface is supported in the standard IPv6 API and + always done, so we don't warn about any IPv6 addresses here. +*/ + +void warn_bound_listeners(void) +{ + struct irec *iface; + int advice = 0; + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (!iface->dns_auth) + { + if (iface->addr.sa.sa_family == AF_INET) + { + if (!private_net(iface->addr.in.sin_addr, 1)) + { + inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN); + iface->warned = advice = 1; + my_syslog(LOG_WARNING, + _("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"), + daemon->addrbuff, iface->name); + } + } } - return listeners; + if (advice) + my_syslog(LOG_WARNING, _("LOUD WARNING: use --bind-dynamic rather than --bind-interfaces to avoid DNS amplification attacks via these interface(s)")); } +void warn_int_names(void) +{ + struct interface_name *intname; + + for (intname = daemon->int_names; intname; intname = intname->next) + if (!intname->addr) + my_syslog(LOG_WARNING, _("warning: no addresses found for interface %s"), intname->intr); +} + +int is_dad_listeners(void) +{ + struct irec *iface; + + if (option_bool(OPT_NOWILD)) + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->dad && !iface->done) + return 1; + + return 0; +} + +#ifdef HAVE_DHCP6 +void join_multicast(int dienow) +{ + struct irec *iface, *tmp; + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->addr.sa.sa_family == AF_INET6 && iface->dhcp_ok && !iface->multicast_done) + { + /* There's an irec per address but we only want to join for multicast + once per interface. Weed out duplicates. */ + for (tmp = daemon->interfaces; tmp; tmp = tmp->next) + if (tmp->multicast_done && tmp->index == iface->index) + break; + + iface->multicast_done = 1; + + if (!tmp) + { + struct ipv6_mreq mreq; + int err = 0; + + mreq.ipv6mr_interface = iface->index; + + inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr); + + if ((daemon->doing_dhcp6 || daemon->relay6) && + setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) + err = 1; + + inet_pton(AF_INET6, ALL_SERVERS, &mreq.ipv6mr_multiaddr); + + if (daemon->doing_dhcp6 && + setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) + err = 1; + + inet_pton(AF_INET6, ALL_ROUTERS, &mreq.ipv6mr_multiaddr); + + if (daemon->doing_ra && + setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) + err = 1; + + if (err) + { + char *s = _("interface %s failed to join DHCPv6 multicast group: %s"); + if (dienow) + die(s, iface->name, EC_BADNET); + else + my_syslog(LOG_ERR, s, iface->name, strerror(errno)); + } + } + } +} +#endif /* return a UDP socket bound to a random port, have to cope with straying into occupied port nos and reserved ones. */ @@ -748,87 +1291,206 @@ void pre_allocate_sfds(void) } } +void mark_servers(int flag) +{ + struct server *serv; + + /* mark everything with argument flag */ + for (serv = daemon->servers; serv; serv = serv->next) + { + if (serv->flags & flag) + serv->flags |= SERV_MARK; +#ifdef HAVE_LOOP + /* Give looped servers another chance */ + serv->flags &= ~SERV_LOOP; +#endif + } +} + +void cleanup_servers(void) +{ + struct server *serv, *tmp, **up; + + /* unlink and free anything still marked. */ + for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) + { + tmp = serv->next; + if (serv->flags & SERV_MARK) + { + server_gone(serv); + *up = serv->next; + if (serv->domain) + free(serv->domain); + free(serv); + } + else + up = &serv->next; + } + +#ifdef HAVE_LOOP + /* Now we have a new set of servers, test for loops. */ + loop_send_probes(); +#endif +} + +void add_update_server(int flags, + union mysockaddr *addr, + union mysockaddr *source_addr, + const char *interface, + const char *domain) +{ + struct server *serv, *next = NULL; + char *domain_str = NULL; + + /* See if there is a suitable candidate, and unmark */ + for (serv = daemon->servers; serv; serv = serv->next) + if (serv->flags & SERV_MARK) + { + if (domain) + { + if (!(serv->flags & SERV_HAS_DOMAIN) || !hostname_isequal(domain, serv->domain)) + continue; + } + else + { + if (serv->flags & SERV_HAS_DOMAIN) + continue; + } + + break; + } + + if (serv) + { + domain_str = serv->domain; + next = serv->next; + } + else if ((serv = whine_malloc(sizeof (struct server)))) + { + /* Not found, create a new one. */ + if (domain && !(domain_str = whine_malloc(strlen(domain)+1))) + { + free(serv); + serv = NULL; + } + else + { + struct server *s; + /* Add to the end of the chain, for order */ + if (!daemon->servers) + daemon->servers = serv; + else + { + for (s = daemon->servers; s->next; s = s->next); + s->next = serv; + } + if (domain) + strcpy(domain_str, domain); + } + } + + if (serv) + { + memset(serv, 0, sizeof(struct server)); + serv->flags = flags; + serv->domain = domain_str; + serv->next = next; + serv->queries = serv->failed_queries = 0; + serv->edns_pktsz = daemon->edns_pktsz; +#ifdef HAVE_LOOP + serv->uid = rand32(); +#endif + + if (domain) + serv->flags |= SERV_HAS_DOMAIN; + + if (interface) + strcpy(serv->interface, interface); + if (addr) + serv->addr = *addr; + if (source_addr) + serv->source_addr = *source_addr; + } +} void check_servers(void) { struct irec *iface; - struct server *new, *tmp, *ret = NULL; + struct server *serv; int port = 0; /* interface may be new since startup */ if (!option_bool(OPT_NOWILD)) - enumerate_interfaces(); + enumerate_interfaces(0); - for (new = daemon->servers; new; new = tmp) + for (serv = daemon->servers; serv; serv = serv->next) { - tmp = new->next; - - if (!(new->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND))) + if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND))) { - port = prettyprint_addr(&new->addr, daemon->namebuff); + port = prettyprint_addr(&serv->addr, daemon->namebuff); /* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */ - if (new->addr.sa.sa_family == AF_INET && - new->addr.in.sin_addr.s_addr == 0) + if (serv->addr.sa.sa_family == AF_INET && + serv->addr.in.sin_addr.s_addr == 0) { - free(new); + serv->flags |= SERV_MARK; continue; } for (iface = daemon->interfaces; iface; iface = iface->next) - if (sockaddr_isequal(&new->addr, &iface->addr)) + if (sockaddr_isequal(&serv->addr, &iface->addr)) break; if (iface) { my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff); - free(new); + serv->flags |= SERV_MARK; continue; } /* Do we need a socket set? */ - if (!new->sfd && - !(new->sfd = allocate_sfd(&new->source_addr, new->interface)) && + if (!serv->sfd && + !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) && errno != 0) { my_syslog(LOG_WARNING, _("ignoring nameserver %s - cannot make/bind socket: %s"), daemon->namebuff, strerror(errno)); - free(new); + serv->flags |= SERV_MARK; continue; } } - /* reverse order - gets it right. */ - new->next = ret; - ret = new; - - if (!(new->flags & SERV_NO_REBIND)) + if (!(serv->flags & SERV_NO_REBIND) && !(serv->flags & SERV_LITERAL_ADDRESS)) { - if (new->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV)) + if (serv->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV)) { char *s1, *s2; - if (!(new->flags & SERV_HAS_DOMAIN)) + if (!(serv->flags & SERV_HAS_DOMAIN)) s1 = _("unqualified"), s2 = _("names"); - else if (strlen(new->domain) == 0) + else if (strlen(serv->domain) == 0) s1 = _("default"), s2 = ""; else - s1 = _("domain"), s2 = new->domain; + s1 = _("domain"), s2 = serv->domain; - if (new->flags & SERV_NO_ADDR) + if (serv->flags & SERV_NO_ADDR) my_syslog(LOG_INFO, _("using local addresses only for %s %s"), s1, s2); - else if (new->flags & SERV_USE_RESOLV) + else if (serv->flags & SERV_USE_RESOLV) my_syslog(LOG_INFO, _("using standard nameservers for %s %s"), s1, s2); - else if (!(new->flags & SERV_LITERAL_ADDRESS)) + else my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s"), daemon->namebuff, port, s1, s2); } - else if (new->interface[0] != 0) - my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, new->interface); +#ifdef HAVE_LOOP + else if (serv->flags & SERV_LOOP) + my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port); +#endif + else if (serv->interface[0] != 0) + my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface); else my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port); } } - - daemon->servers = ret; + + cleanup_servers(); } /* Return zero if no servers found, in that case we keep polling. @@ -837,9 +1499,6 @@ int reload_servers(char *fname) { FILE *f; char *line; - struct server *old_servers = NULL; - struct server *new_servers = NULL; - struct server *serv; int gotone = 0; /* buff happens to be MAXDNAME long... */ @@ -848,28 +1507,9 @@ int reload_servers(char *fname) my_syslog(LOG_ERR, _("failed to read %s: %s"), fname, strerror(errno)); return 0; } - - /* move old servers to free list - we can reuse the memory - and not risk malloc if there are the same or fewer new servers. - Servers which were specced on the command line go to the new list. */ - for (serv = daemon->servers; serv;) - { - struct server *tmp = serv->next; - if (serv->flags & SERV_FROM_RESOLV) - { - serv->next = old_servers; - old_servers = serv; - /* forward table rules reference servers, so have to blow them away */ - server_gone(serv); - } - else - { - serv->next = new_servers; - new_servers = serv; - } - serv = tmp; - } - + + mark_servers(SERV_FROM_RESOLV); + while ((line = fgets(daemon->namebuff, MAXDNAME, f))) { union mysockaddr addr, source_addr; @@ -896,78 +1536,73 @@ int reload_servers(char *fname) source_addr.in.sin_port = htons(daemon->query_port); } #ifdef HAVE_IPV6 - else if (inet_pton(AF_INET6, token, &addr.in6.sin6_addr) > 0) - { + else + { + int scope_index = 0; + char *scope_id = strchr(token, '%'); + + if (scope_id) + { + *(scope_id++) = 0; + scope_index = if_nametoindex(scope_id); + } + + if (inet_pton(AF_INET6, token, &addr.in6.sin6_addr) > 0) + { #ifdef HAVE_SOCKADDR_SA_LEN - source_addr.in6.sin6_len = addr.in6.sin6_len = sizeof(source_addr.in6); + source_addr.in6.sin6_len = addr.in6.sin6_len = sizeof(source_addr.in6); #endif - source_addr.in6.sin6_family = addr.in6.sin6_family = AF_INET6; - addr.in6.sin6_port = htons(NAMESERVER_PORT); - source_addr.in6.sin6_addr = in6addr_any; - source_addr.in6.sin6_port = htons(daemon->query_port); + source_addr.in6.sin6_family = addr.in6.sin6_family = AF_INET6; + source_addr.in6.sin6_flowinfo = addr.in6.sin6_flowinfo = 0; + addr.in6.sin6_port = htons(NAMESERVER_PORT); + addr.in6.sin6_scope_id = scope_index; + source_addr.in6.sin6_addr = in6addr_any; + source_addr.in6.sin6_port = htons(daemon->query_port); + source_addr.in6.sin6_scope_id = 0; + } + else + continue; } -#endif /* IPV6 */ +#else /* IPV6 */ else continue; - - if (old_servers) - { - serv = old_servers; - old_servers = old_servers->next; - } - else if (!(serv = whine_malloc(sizeof (struct server)))) - continue; - - /* this list is reverse ordered: - it gets reversed again in check_servers */ - serv->next = new_servers; - new_servers = serv; - serv->addr = addr; - serv->source_addr = source_addr; - serv->domain = NULL; - serv->interface[0] = 0; - serv->sfd = NULL; - serv->flags = SERV_FROM_RESOLV; - serv->queries = serv->failed_queries = 0; +#endif + + add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL); gotone = 1; } - /* Free any memory not used. */ - while (old_servers) - { - struct server *tmp = old_servers->next; - free(old_servers); - old_servers = tmp; - } - - daemon->servers = new_servers; fclose(f); + cleanup_servers(); return gotone; } - -/* Use an IPv4 listener socket for ioctling */ -struct in_addr get_ifaddr(char *intr) +/* Called when addresses are added or deleted from an interface */ +void newaddress(time_t now) { - struct listener *l; - struct ifreq ifr; - struct sockaddr_in ret; + (void)now; - ret.sin_addr.s_addr = -1; - - for (l = daemon->listeners; - l && (l->family != AF_INET || l->fd == -1); - l = l->next); + if (option_bool(OPT_CLEVERBIND) || option_bool(OPT_LOCAL_SERVICE) || + daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra) + enumerate_interfaces(0); + + if (option_bool(OPT_CLEVERBIND)) + create_bound_listeners(0); - strncpy(ifr.ifr_name, intr, IF_NAMESIZE); - ifr.ifr_addr.sa_family = AF_INET; +#ifdef HAVE_DHCP6 + if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra) + join_multicast(0); - if (l && ioctl(l->fd, SIOCGIFADDR, &ifr) != -1) - memcpy(&ret, &ifr.ifr_addr, sizeof(ret)); + if (daemon->doing_dhcp6 || daemon->doing_ra) + dhcp_construct_contexts(now); - return ret.sin_addr; + if (daemon->doing_dhcp6) + lease_find_interfaces(now); +#endif } + + diff --git a/src/option.c b/src/option.c index 4cee0a2..ecc2619 100644 --- a/src/option.c +++ b/src/option.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 @@ -21,7 +21,7 @@ static volatile int mem_recover = 0; static jmp_buf mem_jmp; -static void one_file(char *file, int hard_opt); +static int one_file(char *file, int hard_opt); /* Solaris headers don't have facility names. */ #ifdef HAVE_SOLARIS_NETWORK @@ -64,52 +64,96 @@ struct myoption { #define OPTSTRING "951yZDNLERKzowefnbvhdkqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:V:U:j:P:J:W:Y:2:4:6:7:8:0:3:" /* options which don't have a one-char version */ -#define LOPT_RELOAD 256 -#define LOPT_NO_NAMES 257 -#define LOPT_TFTP 258 -#define LOPT_SECURE 259 -#define LOPT_PREFIX 260 -#define LOPT_PTR 261 -#define LOPT_BRIDGE 262 -#define LOPT_TFTP_MAX 263 -#define LOPT_FORCE 264 -#define LOPT_NOBLOCK 265 -#define LOPT_LOG_OPTS 266 -#define LOPT_MAX_LOGS 267 -#define LOPT_CIRCUIT 268 -#define LOPT_REMOTE 269 -#define LOPT_SUBSCR 270 -#define LOPT_INTNAME 271 -#define LOPT_BANK 272 -#define LOPT_DHCP_HOST 273 -#define LOPT_APREF 274 -#define LOPT_OVERRIDE 275 -#define LOPT_TFTPPORTS 276 -#define LOPT_REBIND 277 -#define LOPT_NOLAST 278 -#define LOPT_OPTS 279 -#define LOPT_DHCP_OPTS 280 -#define LOPT_MATCH 281 -#define LOPT_BROADCAST 282 -#define LOPT_NEGTTL 283 -#define LOPT_ALTPORT 284 -#define LOPT_SCRIPTUSR 285 -#define LOPT_LOCAL 286 -#define LOPT_NAPTR 287 -#define LOPT_MINPORT 288 -#define LOPT_DHCP_FQDN 289 -#define LOPT_CNAME 290 -#define LOPT_PXE_PROMT 291 -#define LOPT_PXE_SERV 292 -#define LOPT_TEST 293 -#define LOPT_TAG_IF 294 -#define LOPT_PROXY 295 -#define LOPT_GEN_NAMES 296 -#define LOPT_MAXTTL 297 -#define LOPT_NO_REBIND 298 -#define LOPT_LOC_REBND 299 -#define LOPT_ADD_MAC 300 -#define LOPT_DNSSEC 301 +#define LOPT_RELOAD 256 +#define LOPT_NO_NAMES 257 +#define LOPT_TFTP 258 +#define LOPT_SECURE 259 +#define LOPT_PREFIX 260 +#define LOPT_PTR 261 +#define LOPT_BRIDGE 262 +#define LOPT_TFTP_MAX 263 +#define LOPT_FORCE 264 +#define LOPT_NOBLOCK 265 +#define LOPT_LOG_OPTS 266 +#define LOPT_MAX_LOGS 267 +#define LOPT_CIRCUIT 268 +#define LOPT_REMOTE 269 +#define LOPT_SUBSCR 270 +#define LOPT_INTNAME 271 +#define LOPT_BANK 272 +#define LOPT_DHCP_HOST 273 +#define LOPT_APREF 274 +#define LOPT_OVERRIDE 275 +#define LOPT_TFTPPORTS 276 +#define LOPT_REBIND 277 +#define LOPT_NOLAST 278 +#define LOPT_OPTS 279 +#define LOPT_DHCP_OPTS 280 +#define LOPT_MATCH 281 +#define LOPT_BROADCAST 282 +#define LOPT_NEGTTL 283 +#define LOPT_ALTPORT 284 +#define LOPT_SCRIPTUSR 285 +#define LOPT_LOCAL 286 +#define LOPT_NAPTR 287 +#define LOPT_MINPORT 288 +#define LOPT_DHCP_FQDN 289 +#define LOPT_CNAME 290 +#define LOPT_PXE_PROMT 291 +#define LOPT_PXE_SERV 292 +#define LOPT_TEST 293 +#define LOPT_TAG_IF 294 +#define LOPT_PROXY 295 +#define LOPT_GEN_NAMES 296 +#define LOPT_MAXTTL 297 +#define LOPT_NO_REBIND 298 +#define LOPT_LOC_REBND 299 +#define LOPT_ADD_MAC 300 +#define LOPT_DNSSEC 301 +#define LOPT_INCR_ADDR 302 +#define LOPT_CONNTRACK 303 +#define LOPT_FQDN 304 +#define LOPT_LUASCRIPT 305 +#define LOPT_RA 306 +#define LOPT_DUID 307 +#define LOPT_HOST_REC 308 +#define LOPT_TFTP_LC 309 +#define LOPT_RR 310 +#define LOPT_CLVERBIND 311 +#define LOPT_MAXCTTL 312 +#define LOPT_AUTHZONE 313 +#define LOPT_AUTHSERV 314 +#define LOPT_AUTHTTL 315 +#define LOPT_AUTHSOA 316 +#define LOPT_AUTHSFS 317 +#define LOPT_AUTHPEER 318 +#define LOPT_IPSET 319 +#define LOPT_SYNTH 320 +#ifdef OPTION6_PREFIX_CLASS +#define LOPT_PREF_CLSS 321 +#endif +#define LOPT_RELAY 323 +#define LOPT_RA_PARAM 324 +#define LOPT_ADD_SBNET 325 +#define LOPT_QUIET_DHCP 326 +#define LOPT_QUIET_DHCP6 327 +#define LOPT_QUIET_RA 328 +#define LOPT_SEC_VALID 329 +#define LOPT_TRUST_ANCHOR 330 +#define LOPT_DNSSEC_DEBUG 331 +#define LOPT_REV_SERV 332 +#define LOPT_SERVERS_FILE 333 +#define LOPT_DNSSEC_CHECK 334 +#define LOPT_LOCAL_SERVICE 335 +#define LOPT_DNSSEC_TIME 336 +#define LOPT_LOOP_DETECT 337 +#define LOPT_IGNORE_ADDR 338 +#define LOPT_MINCTTL 339 +#define LOPT_DHCP_INOTIFY 340 +#define LOPT_DHOPT_INOTIFY 341 +#define LOPT_HOST_INOTIFY 342 +#define LOPT_DNSSEC_STAMP 343 +#define LOPT_TFTP_NO_FAIL 344 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -122,10 +166,11 @@ static const struct myoption opts[] = { "no-poll", 0, 0, 'n' }, { "help", 0, 0, 'w' }, { "no-daemon", 0, 0, 'd' }, - { "log-queries", 0, 0, 'q' }, + { "log-queries", 2, 0, 'q' }, { "user", 2, 0, 'u' }, { "group", 2, 0, 'g' }, { "resolv-file", 2, 0, 'r' }, + { "servers-file", 1, 0, LOPT_SERVERS_FILE }, { "mx-host", 1, 0, 'm' }, { "mx-target", 1, 0, 't' }, { "cache-size", 2, 0, 'c' }, @@ -140,13 +185,16 @@ static const struct myoption opts[] = { "domain-suffix", 1, 0, 's' }, { "interface", 1, 0, 'i' }, { "listen-address", 1, 0, 'a' }, + { "local-service", 0, 0, LOPT_LOCAL_SERVICE }, { "bogus-priv", 0, 0, 'b' }, { "bogus-nxdomain", 1, 0, 'B' }, + { "ignore-address", 1, 0, LOPT_IGNORE_ADDR }, { "selfmx", 0, 0, 'e' }, { "filterwin2k", 0, 0, 'f' }, { "pid-file", 2, 0, 'x' }, { "strict-order", 0, 0, 'o' }, { "server", 1, 0, 'S' }, + { "rev-server", 1, 0, LOPT_REV_SERV }, { "local", 1, 0, LOPT_LOCAL }, { "address", 1, 0, 'A' }, { "conf-file", 2, 0, 'C' }, @@ -156,6 +204,7 @@ static const struct myoption opts[] = { "local-ttl", 1, 0, 'T' }, { "no-negcache", 0, 0, 'N' }, { "addn-hosts", 1, 0, 'H' }, + { "hostsdir", 1, 0, LOPT_HOST_INOTIFY }, { "query-port", 1, 0, 'Q' }, { "except-interface", 1, 0, 'I' }, { "no-dhcp-interface", 1, 0, '2' }, @@ -173,7 +222,8 @@ static const struct myoption opts[] = { "srv-host", 1, 0, 'W' }, { "localise-queries", 0, 0, 'y' }, { "txt-record", 1, 0, 'Y' }, - { "enable-dbus", 0, 0, '1' }, + { "dns-rr", 1, 0, LOPT_RR }, + { "enable-dbus", 2, 0, '1' }, { "bootp-dynamic", 2, 0, '3' }, { "dhcp-mac", 1, 0, '4' }, { "no-ping", 0, 0, '5' }, @@ -186,9 +236,11 @@ static const struct myoption opts[] = { "dhcp-ignore-names", 2, 0, LOPT_NO_NAMES }, { "enable-tftp", 2, 0, LOPT_TFTP }, { "tftp-secure", 0, 0, LOPT_SECURE }, + { "tftp-no-fail", 0, 0, LOPT_TFTP_NO_FAIL }, { "tftp-unique-root", 0, 0, LOPT_APREF }, { "tftp-root", 1, 0, LOPT_PREFIX }, { "tftp-max", 1, 0, LOPT_TFTP_MAX }, + { "tftp-lowercase", 0, 0, LOPT_TFTP_LC }, { "ptr-record", 1, 0, LOPT_PTR }, { "naptr-record", 1, 0, LOPT_NAPTR }, { "bridge-interface", 1, 0 , LOPT_BRIDGE }, @@ -202,6 +254,8 @@ static const struct myoption opts[] = { "interface-name", 1, 0, LOPT_INTNAME }, { "dhcp-hostsfile", 1, 0, LOPT_DHCP_HOST }, { "dhcp-optsfile", 1, 0, LOPT_DHCP_OPTS }, + { "dhcp-hostsdir", 1, 0, LOPT_DHCP_INOTIFY }, + { "dhcp-optsdir", 1, 0, LOPT_DHOPT_INOTIFY }, { "dhcp-no-override", 0, 0, LOPT_OVERRIDE }, { "tftp-port-range", 1, 0, LOPT_TFTPPORTS }, { "stop-dns-rebind", 0, 0, LOPT_REBIND }, @@ -211,6 +265,8 @@ static const struct myoption opts[] = { "dhcp-broadcast", 2, 0, LOPT_BROADCAST }, { "neg-ttl", 1, 0, LOPT_NEGTTL }, { "max-ttl", 1, 0, LOPT_MAXTTL }, + { "min-cache-ttl", 1, 0, LOPT_MINCTTL }, + { "max-cache-ttl", 1, 0, LOPT_MAXCTTL }, { "dhcp-alternate-port", 2, 0, LOPT_ALTPORT }, { "dhcp-scriptuser", 1, 0, LOPT_SCRIPTUSR }, { "min-port", 1, 0, LOPT_MINPORT }, @@ -224,7 +280,39 @@ static const struct myoption opts[] = { "dhcp-generate-names", 2, 0, LOPT_GEN_NAMES }, { "rebind-localhost-ok", 0, 0, LOPT_LOC_REBND }, { "add-mac", 0, 0, LOPT_ADD_MAC }, + { "add-subnet", 2, 0, LOPT_ADD_SBNET }, { "proxy-dnssec", 0, 0, LOPT_DNSSEC }, + { "dhcp-sequential-ip", 0, 0, LOPT_INCR_ADDR }, + { "conntrack", 0, 0, LOPT_CONNTRACK }, + { "dhcp-client-update", 0, 0, LOPT_FQDN }, + { "dhcp-luascript", 1, 0, LOPT_LUASCRIPT }, + { "enable-ra", 0, 0, LOPT_RA }, + { "dhcp-duid", 1, 0, LOPT_DUID }, + { "host-record", 1, 0, LOPT_HOST_REC }, + { "bind-dynamic", 0, 0, LOPT_CLVERBIND }, + { "auth-zone", 1, 0, LOPT_AUTHZONE }, + { "auth-server", 1, 0, LOPT_AUTHSERV }, + { "auth-ttl", 1, 0, LOPT_AUTHTTL }, + { "auth-soa", 1, 0, LOPT_AUTHSOA }, + { "auth-sec-servers", 1, 0, LOPT_AUTHSFS }, + { "auth-peer", 1, 0, LOPT_AUTHPEER }, + { "ipset", 1, 0, LOPT_IPSET }, + { "synth-domain", 1, 0, LOPT_SYNTH }, + { "dnssec", 0, 0, LOPT_SEC_VALID }, + { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR }, + { "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG }, + { "dnssec-check-unsigned", 0, 0, LOPT_DNSSEC_CHECK }, + { "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME }, + { "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP }, +#ifdef OPTION6_PREFIX_CLASS + { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, +#endif + { "dhcp-relay", 1, 0, LOPT_RELAY }, + { "ra-param", 1, 0, LOPT_RA_PARAM }, + { "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP }, + { "quiet-dhcp6", 0, 0, LOPT_QUIET_DHCP6 }, + { "quiet-ra", 0, 0, LOPT_QUIET_RA }, + { "dns-loop-detect", 0, 0, LOPT_LOOP_DETECT }, { NULL, 0, 0, 0 } }; @@ -241,27 +329,30 @@ static struct { char * const desc; char * const arg; } usage[] = { - { 'a', ARG_DUP, "ipaddr", gettext_noop("Specify local address(es) to listen on."), NULL }, - { 'A', ARG_DUP, "/domain/ipaddr", gettext_noop("Return ipaddr for all hosts in specified domains."), NULL }, + { 'a', ARG_DUP, "<ipaddr>", gettext_noop("Specify local address(es) to listen on."), NULL }, + { 'A', ARG_DUP, "/<domain>/<ipaddr>", gettext_noop("Return ipaddr for all hosts in specified domains."), NULL }, { 'b', OPT_BOGUSPRIV, NULL, gettext_noop("Fake reverse lookups for RFC1918 private address ranges."), NULL }, - { 'B', ARG_DUP, "ipaddr", gettext_noop("Treat ipaddr as NXDOMAIN (defeats Verisign wildcard)."), NULL }, - { 'c', ARG_ONE, "cachesize", gettext_noop("Specify the size of the cache in entries (defaults to %s)."), "$" }, - { 'C', ARG_DUP, "path", gettext_noop("Specify configuration file (defaults to %s)."), CONFFILE }, + { 'B', ARG_DUP, "<ipaddr>", gettext_noop("Treat ipaddr as NXDOMAIN (defeats Verisign wildcard)."), NULL }, + { 'c', ARG_ONE, "<integer>", gettext_noop("Specify the size of the cache in entries (defaults to %s)."), "$" }, + { 'C', ARG_DUP, "<path>", gettext_noop("Specify configuration file (defaults to %s)."), CONFFILE }, { 'd', OPT_DEBUG, NULL, gettext_noop("Do NOT fork into the background: run in debug mode."), NULL }, { 'D', OPT_NODOTS_LOCAL, NULL, gettext_noop("Do NOT forward queries with no domain part."), NULL }, { 'e', OPT_SELFMX, NULL, gettext_noop("Return self-pointing MX records for local hosts."), NULL }, { 'E', OPT_EXPAND, NULL, gettext_noop("Expand simple names in /etc/hosts with domain-suffix."), NULL }, { 'f', OPT_FILTER, NULL, gettext_noop("Don't forward spurious DNS requests from Windows hosts."), NULL }, - { 'F', ARG_DUP, "ipaddr,ipaddr,time", gettext_noop("Enable DHCP in the range given with lease duration."), NULL }, - { 'g', ARG_ONE, "groupname", gettext_noop("Change to this group after startup (defaults to %s)."), CHGRP }, + { 'F', ARG_DUP, "<ipaddr>,...", gettext_noop("Enable DHCP in the range given with lease duration."), NULL }, + { 'g', ARG_ONE, "<groupname>", gettext_noop("Change to this group after startup (defaults to %s)."), CHGRP }, { 'G', ARG_DUP, "<hostspec>", gettext_noop("Set address or hostname for a specified machine."), NULL }, - { LOPT_DHCP_HOST, ARG_DUP, "<filename>", gettext_noop("Read DHCP host specs from file."), NULL }, - { LOPT_DHCP_OPTS, ARG_DUP, "<filename>", gettext_noop("Read DHCP option specs from file."), NULL }, + { LOPT_DHCP_HOST, ARG_DUP, "<path>", gettext_noop("Read DHCP host specs from file."), NULL }, + { LOPT_DHCP_OPTS, ARG_DUP, "<path>", gettext_noop("Read DHCP option specs from file."), NULL }, + { LOPT_DHCP_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read DHCP host specs from a directory."), NULL }, + { LOPT_DHOPT_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read DHCP options from a directory."), NULL }, { LOPT_TAG_IF, ARG_DUP, "tag-expression", gettext_noop("Evaluate conditional tag expression."), NULL }, { 'h', OPT_NO_HOSTS, NULL, gettext_noop("Do NOT load %s file."), HOSTSFILE }, - { 'H', ARG_DUP, "path", gettext_noop("Specify a hosts file to be read in addition to %s."), HOSTSFILE }, - { 'i', ARG_DUP, "interface", gettext_noop("Specify interface(s) to listen on."), NULL }, - { 'I', ARG_DUP, "int", gettext_noop("Specify interface(s) NOT to listen on.") , NULL }, + { 'H', ARG_DUP, "<path>", gettext_noop("Specify a hosts file to be read in addition to %s."), HOSTSFILE }, + { LOPT_HOST_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read hosts files from a directory."), NULL }, + { 'i', ARG_DUP, "<interface>", gettext_noop("Specify interface(s) to listen on."), NULL }, + { 'I', ARG_DUP, "<interface>", gettext_noop("Specify interface(s) NOT to listen on.") , NULL }, { 'j', ARG_DUP, "set:<tag>,<class>", gettext_noop("Map DHCP user class to tag."), NULL }, { LOPT_CIRCUIT, ARG_DUP, "set:<tag>,<circuit>", gettext_noop("Map RFC3046 circuit-id to tag."), NULL }, { LOPT_REMOTE, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3046 remote-id to tag."), NULL }, @@ -270,190 +361,127 @@ static struct { { LOPT_BROADCAST, ARG_DUP, "[=tag:<tag>...]", gettext_noop("Force broadcast replies for hosts with tag set."), NULL }, { 'k', OPT_NO_FORK, NULL, gettext_noop("Do NOT fork into the background, do NOT run in debug mode."), NULL }, { 'K', OPT_AUTHORITATIVE, NULL, gettext_noop("Assume we are the only DHCP server on the local network."), NULL }, - { 'l', ARG_ONE, "path", gettext_noop("Specify where to store DHCP leases (defaults to %s)."), LEASEFILE }, + { 'l', ARG_ONE, "<path>", gettext_noop("Specify where to store DHCP leases (defaults to %s)."), LEASEFILE }, { 'L', OPT_LOCALMX, NULL, gettext_noop("Return MX records for local hosts."), NULL }, - { 'm', ARG_DUP, "host_name,target,pref", gettext_noop("Specify an MX record."), NULL }, + { 'm', ARG_DUP, "<host_name>,<target>,<pref>", gettext_noop("Specify an MX record."), NULL }, { 'M', ARG_DUP, "<bootp opts>", gettext_noop("Specify BOOTP options to DHCP server."), NULL }, { 'n', OPT_NO_POLL, NULL, gettext_noop("Do NOT poll %s file, reload only on SIGHUP."), RESOLVFILE }, { 'N', OPT_NO_NEG, NULL, gettext_noop("Do NOT cache failed search results."), NULL }, { 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE }, { 'O', ARG_DUP, "<optspec>", gettext_noop("Specify options to be sent to DHCP clients."), NULL }, { LOPT_FORCE, ARG_DUP, "<optspec>", gettext_noop("DHCP option sent even if the client does not request it."), NULL}, - { 'p', ARG_ONE, "number", gettext_noop("Specify port to listen for DNS requests on (defaults to 53)."), NULL }, - { 'P', ARG_ONE, "<size>", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" }, - { 'q', OPT_LOG, NULL, gettext_noop("Log DNS queries."), NULL }, - { 'Q', ARG_ONE, "number", gettext_noop("Force the originating port for upstream DNS queries."), NULL }, + { 'p', ARG_ONE, "<integer>", gettext_noop("Specify port to listen for DNS requests on (defaults to 53)."), NULL }, + { 'P', ARG_ONE, "<integer>", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" }, + { 'q', ARG_DUP, NULL, gettext_noop("Log DNS queries."), NULL }, + { 'Q', ARG_ONE, "<integer>", gettext_noop("Force the originating port for upstream DNS queries."), NULL }, { 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL }, - { 'r', ARG_DUP, "path", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE }, - { 'S', ARG_DUP, "/domain/ipaddr", gettext_noop("Specify address(es) of upstream servers with optional domains."), NULL }, - { LOPT_LOCAL, ARG_DUP, "/domain/", gettext_noop("Never forward queries to specified domains."), NULL }, + { 'r', ARG_DUP, "<path>", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE }, + { LOPT_SERVERS_FILE, ARG_ONE, "<path>", gettext_noop("Specify path to file with server= options"), NULL }, + { 'S', ARG_DUP, "/<domain>/<ipaddr>", gettext_noop("Specify address(es) of upstream servers with optional domains."), NULL }, + { LOPT_REV_SERV, ARG_DUP, "<addr>/<prefix>,<ipaddr>", gettext_noop("Specify address of upstream servers for reverse address queries"), NULL }, + { LOPT_LOCAL, ARG_DUP, "/<domain>/", gettext_noop("Never forward queries to specified domains."), NULL }, { 's', ARG_DUP, "<domain>[,<range>]", gettext_noop("Specify the domain to be assigned in DHCP leases."), NULL }, - { 't', ARG_ONE, "host_name", gettext_noop("Specify default target in an MX record."), NULL }, - { 'T', ARG_ONE, "time", gettext_noop("Specify time-to-live in seconds for replies from /etc/hosts."), NULL }, - { LOPT_NEGTTL, ARG_ONE, "time", gettext_noop("Specify time-to-live in seconds for negative caching."), NULL }, - { LOPT_MAXTTL, ARG_ONE, "time", gettext_noop("Specify time-to-live in seconds for maximum TTL to send to clients."), NULL }, - { 'u', ARG_ONE, "username", gettext_noop("Change to this user after startup. (defaults to %s)."), CHUSER }, + { 't', ARG_ONE, "<host_name>", gettext_noop("Specify default target in an MX record."), NULL }, + { 'T', ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for replies from /etc/hosts."), NULL }, + { LOPT_NEGTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for negative caching."), NULL }, + { LOPT_MAXTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for maximum TTL to send to clients."), NULL }, + { LOPT_MAXCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live ceiling for cache."), NULL }, + { LOPT_MINCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live floor for cache."), NULL }, + { 'u', ARG_ONE, "<username>", gettext_noop("Change to this user after startup. (defaults to %s)."), CHUSER }, { 'U', ARG_DUP, "set:<tag>,<class>", gettext_noop("Map DHCP vendor class to tag."), NULL }, { 'v', 0, NULL, gettext_noop("Display dnsmasq version and copyright information."), NULL }, - { 'V', ARG_DUP, "addr,addr,mask", gettext_noop("Translate IPv4 addresses from upstream servers."), NULL }, - { 'W', ARG_DUP, "name,target,...", gettext_noop("Specify a SRV record."), NULL }, + { 'V', ARG_DUP, "<ipaddr>,<ipaddr>,<netmask>", gettext_noop("Translate IPv4 addresses from upstream servers."), NULL }, + { 'W', ARG_DUP, "<name>,<target>,...", gettext_noop("Specify a SRV record."), NULL }, { 'w', 0, NULL, gettext_noop("Display this message. Use --help dhcp for known DHCP options."), NULL }, - { 'x', ARG_ONE, "path", gettext_noop("Specify path of PID file (defaults to %s)."), RUNFILE }, - { 'X', ARG_ONE, "number", gettext_noop("Specify maximum number of DHCP leases (defaults to %s)."), "&" }, + { 'x', ARG_ONE, "<path>", gettext_noop("Specify path of PID file (defaults to %s)."), RUNFILE }, + { 'X', ARG_ONE, "<integer>", gettext_noop("Specify maximum number of DHCP leases (defaults to %s)."), "&" }, { 'y', OPT_LOCALISE, NULL, gettext_noop("Answer DNS queries based on the interface a query was sent to."), NULL }, - { 'Y', ARG_DUP, "name,txt....", gettext_noop("Specify TXT DNS record."), NULL }, - { LOPT_PTR, ARG_DUP, "name,target", gettext_noop("Specify PTR DNS record."), NULL }, - { LOPT_INTNAME, ARG_DUP, "name,interface", gettext_noop("Give DNS name to IPv4 address of interface."), NULL }, + { 'Y', ARG_DUP, "<name>,<txt>[,<txt]", gettext_noop("Specify TXT DNS record."), NULL }, + { LOPT_PTR, ARG_DUP, "<name>,<target>", gettext_noop("Specify PTR DNS record."), NULL }, + { LOPT_INTNAME, ARG_DUP, "<name>,<interface>", gettext_noop("Give DNS name to IPv4 address of interface."), NULL }, { 'z', OPT_NOWILD, NULL, gettext_noop("Bind only to interfaces in use."), NULL }, { 'Z', OPT_ETHERS, NULL, gettext_noop("Read DHCP static host information from %s."), ETHERSFILE }, - { '1', OPT_DBUS, NULL, gettext_noop("Enable the DBus interface for setting upstream servers, etc."), NULL }, - { '2', ARG_DUP, "interface", gettext_noop("Do not provide DHCP on this interface, only provide DNS."), NULL }, + { '1', ARG_ONE, "[=<busname>]", gettext_noop("Enable the DBus interface for setting upstream servers, etc."), NULL }, + { '2', ARG_DUP, "<interface>", gettext_noop("Do not provide DHCP on this interface, only provide DNS."), NULL }, { '3', ARG_DUP, "[=tag:<tag>]...", gettext_noop("Enable dynamic address allocation for bootp."), NULL }, { '4', ARG_DUP, "set:<tag>,<mac address>", gettext_noop("Map MAC address (with wildcards) to option set."), NULL }, - { LOPT_BRIDGE, ARG_DUP, "iface,alias,..", gettext_noop("Treat DHCP requests on aliases as arriving from interface."), NULL }, + { LOPT_BRIDGE, ARG_DUP, "<iface>,<alias>..", gettext_noop("Treat DHCP requests on aliases as arriving from interface."), NULL }, { '5', OPT_NO_PING, NULL, gettext_noop("Disable ICMP echo address checking in the DHCP server."), NULL }, - { '6', ARG_ONE, "path", gettext_noop("Script to run on DHCP lease creation and destruction."), NULL }, - { '7', ARG_DUP, "path", gettext_noop("Read configuration from all the files in this directory."), NULL }, + { '6', ARG_ONE, "<path>", gettext_noop("Shell script to run on DHCP lease creation and destruction."), NULL }, + { LOPT_LUASCRIPT, ARG_DUP, "path", gettext_noop("Lua script to run on DHCP lease creation and destruction."), NULL }, + { LOPT_SCRIPTUSR, ARG_ONE, "<username>", gettext_noop("Run lease-change scripts as this user."), NULL }, + { '7', ARG_DUP, "<path>", gettext_noop("Read configuration from all the files in this directory."), NULL }, { '8', ARG_ONE, "<facilty>|<file>", gettext_noop("Log to this syslog facility or file. (defaults to DAEMON)"), NULL }, { '9', OPT_LEASE_RO, NULL, gettext_noop("Do not use leasefile."), NULL }, - { '0', ARG_ONE, "<queries>", gettext_noop("Maximum number of concurrent DNS queries. (defaults to %s)"), "!" }, + { '0', ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent DNS queries. (defaults to %s)"), "!" }, { LOPT_RELOAD, OPT_RELOAD, NULL, gettext_noop("Clear DNS cache when reloading %s."), RESOLVFILE }, { LOPT_NO_NAMES, ARG_DUP, "[=tag:<tag>]...", gettext_noop("Ignore hostnames provided by DHCP clients."), NULL }, { LOPT_OVERRIDE, OPT_NO_OVERRIDE, NULL, gettext_noop("Do NOT reuse filename and server fields for extra DHCP options."), NULL }, - { LOPT_TFTP, ARG_DUP, "[=<interface>]", gettext_noop("Enable integrated read-only TFTP server."), NULL }, - { LOPT_PREFIX, ARG_ONE, "<dir>[,<iface>]", gettext_noop("Export files by TFTP only from the specified subtree."), NULL }, + { LOPT_TFTP, ARG_DUP, "[=<intr>[,<intr>]]", gettext_noop("Enable integrated read-only TFTP server."), NULL }, + { LOPT_PREFIX, ARG_DUP, "<dir>[,<iface>]", gettext_noop("Export files by TFTP only from the specified subtree."), NULL }, { LOPT_APREF, OPT_TFTP_APREF, NULL, gettext_noop("Add client IP address to tftp-root."), NULL }, { LOPT_SECURE, OPT_TFTP_SECURE, NULL, gettext_noop("Allow access only to files owned by the user running dnsmasq."), NULL }, - { LOPT_TFTP_MAX, ARG_ONE, "<connections>", gettext_noop("Maximum number of conncurrent TFTP transfers (defaults to %s)."), "#" }, + { LOPT_TFTP_NO_FAIL, OPT_TFTP_NO_FAIL, NULL, gettext_noop("Do not terminate the service if TFTP directories are inaccessible."), NULL }, + { LOPT_TFTP_MAX, ARG_ONE, "<integer>", gettext_noop("Maximum number of conncurrent TFTP transfers (defaults to %s)."), "#" }, { LOPT_NOBLOCK, OPT_TFTP_NOBLOCK, NULL, gettext_noop("Disable the TFTP blocksize extension."), NULL }, + { LOPT_TFTP_LC, OPT_TFTP_LC, NULL, gettext_noop("Convert TFTP filenames to lowercase"), NULL }, { LOPT_TFTPPORTS, ARG_ONE, "<start>,<end>", gettext_noop("Ephemeral port range for use by TFTP transfers."), NULL }, { LOPT_LOG_OPTS, OPT_LOG_OPTS, NULL, gettext_noop("Extra logging for DHCP."), NULL }, - { LOPT_MAX_LOGS, ARG_ONE, "[=<log lines>]", gettext_noop("Enable async. logging; optionally set queue length."), NULL }, + { LOPT_MAX_LOGS, ARG_ONE, "[=<integer>]", gettext_noop("Enable async. logging; optionally set queue length."), NULL }, { LOPT_REBIND, OPT_NO_REBIND, NULL, gettext_noop("Stop DNS rebinding. Filter private IP ranges when resolving."), NULL }, { LOPT_LOC_REBND, OPT_LOCAL_REBIND, NULL, gettext_noop("Allow rebinding of 127.0.0.0/8, for RBL servers."), NULL }, - { LOPT_NO_REBIND, ARG_DUP, "/domain/", gettext_noop("Inhibit DNS-rebind protection on this domain."), NULL }, + { LOPT_NO_REBIND, ARG_DUP, "/<domain>/", gettext_noop("Inhibit DNS-rebind protection on this domain."), NULL }, { LOPT_NOLAST, OPT_ALL_SERVERS, NULL, gettext_noop("Always perform DNS queries to all servers."), NULL }, { LOPT_MATCH, ARG_DUP, "set:<tag>,<optspec>", gettext_noop("Set tag if client includes matching option in request."), NULL }, { LOPT_ALTPORT, ARG_ONE, "[=<ports>]", gettext_noop("Use alternative ports for DHCP."), NULL }, - { LOPT_SCRIPTUSR, ARG_ONE, "<username>", gettext_noop("Run lease-change script as this user."), NULL }, { LOPT_NAPTR, ARG_DUP, "<name>,<naptr>", gettext_noop("Specify NAPTR DNS record."), NULL }, { LOPT_MINPORT, ARG_ONE, "<port>", gettext_noop("Specify lowest port available for DNS query transmission."), NULL }, { LOPT_DHCP_FQDN, OPT_DHCP_FQDN, NULL, gettext_noop("Use only fully qualified domain names for DHCP clients."), NULL }, - { LOPT_GEN_NAMES, ARG_DUP, "[=tag:<tag>]...", gettext_noop("Generate hostnames based on MAC address for nameless clients."), NULL}, - { LOPT_PROXY, ARG_DUP, "[=<ip_address>]...", gettext_noop("Use these DHCP relays as full proxies."), NULL }, + { LOPT_GEN_NAMES, ARG_DUP, "[=tag:<tag>]", gettext_noop("Generate hostnames based on MAC address for nameless clients."), NULL}, + { LOPT_PROXY, ARG_DUP, "[=<ipaddr>]...", gettext_noop("Use these DHCP relays as full proxies."), NULL }, + { LOPT_RELAY, ARG_DUP, "<local-addr>,<server>[,<interface>]", gettext_noop("Relay DHCP requests to a remote server"), NULL}, { LOPT_CNAME, ARG_DUP, "<alias>,<target>", gettext_noop("Specify alias name for LOCAL DNS name."), NULL }, { LOPT_PXE_PROMT, ARG_DUP, "<prompt>,[<timeout>]", gettext_noop("Prompt to send to PXE clients."), NULL }, { LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL }, { LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL }, - { LOPT_ADD_MAC, OPT_ADD_MAC, NULL, gettext_noop("Add requestor's MAC address to forwarded DNS queries"), NULL }, - { LOPT_DNSSEC, OPT_DNSSEC, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers"), NULL }, + { LOPT_ADD_MAC, OPT_ADD_MAC, NULL, gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL }, + { LOPT_ADD_SBNET, ARG_ONE, "<v4 pref>[,<v6 pref>]", gettext_noop("Add requestor's IP subnet to forwarded DNS queries."), NULL }, + { LOPT_DNSSEC, OPT_DNSSEC_PROXY, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL }, + { LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL }, + { LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL }, + { LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL }, + { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL }, + { LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, + { LOPT_HOST_REC, ARG_DUP, "<name>,<address>", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, + { LOPT_RR, ARG_DUP, "<name>,<RR-number>,[<data>]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, + { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL }, + { LOPT_AUTHSERV, ARG_ONE, "<NS>,<interface>", gettext_noop("Export local names to global DNS"), NULL }, + { LOPT_AUTHZONE, ARG_DUP, "<domain>,[<subnet>...]", gettext_noop("Domain to export to global DNS"), NULL }, + { LOPT_AUTHTTL, ARG_ONE, "<integer>", gettext_noop("Set TTL for authoritative replies"), NULL }, + { LOPT_AUTHSOA, ARG_ONE, "<serial>[,...]", gettext_noop("Set authoritive zone information"), NULL }, + { LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL }, + { LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL }, + { LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL }, + { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL }, + { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, + { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL }, + { LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL }, + { LOPT_DNSSEC_CHECK, OPT_DNSSEC_NO_SIGN, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL }, + { LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL }, + { LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL }, +#ifdef OPTION6_PREFIX_CLASS + { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL }, +#endif + { LOPT_RA_PARAM, ARG_DUP, "<interface>,[high,|low,]<interval>[,<lifetime>]", gettext_noop("Set priority, resend-interval and router-lifetime"), NULL }, + { LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL }, + { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL }, + { LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL }, + { LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks"), NULL }, + { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops"), NULL }, + { LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS responses containing ipaddr."), NULL }, { 0, 0, NULL, NULL, NULL } }; -#ifdef HAVE_DHCP -#define OT_ADDR_LIST 0x80 -#define OT_RFC1035_NAME 0x40 -#define OT_INTERNAL 0x20 -#define OT_NAME 0x10 - - -static const struct { - char *name; - unsigned char val, size; -} opttab[] = { - { "netmask", 1, OT_ADDR_LIST }, - { "time-offset", 2, 4 }, - { "router", 3, OT_ADDR_LIST }, - { "dns-server", 6, OT_ADDR_LIST }, - { "log-server", 7, OT_ADDR_LIST }, - { "lpr-server", 9, OT_ADDR_LIST }, - { "hostname", 12, OT_INTERNAL | OT_NAME }, - { "boot-file-size", 13, 2 }, - { "domain-name", 15, OT_NAME }, - { "swap-server", 16, OT_ADDR_LIST }, - { "root-path", 17, OT_NAME }, - { "extension-path", 18, OT_NAME }, - { "ip-forward-enable", 19, 1 }, - { "non-local-source-routing", 20, 1 }, - { "policy-filter", 21, OT_ADDR_LIST }, - { "max-datagram-reassembly", 22, 2 }, - { "default-ttl", 23, 1 }, - { "mtu", 26, 2 }, - { "all-subnets-local", 27, 1 }, - { "broadcast", 28, OT_INTERNAL | OT_ADDR_LIST }, - { "router-discovery", 31, 1 }, - { "router-solicitation", 32, OT_ADDR_LIST }, - { "static-route", 33, OT_ADDR_LIST }, - { "trailer-encapsulation", 34, 1 }, - { "arp-timeout", 35, 4 }, - { "ethernet-encap", 36, 1 }, - { "tcp-ttl", 37, 1 }, - { "tcp-keepalive", 38, 4 }, - { "nis-domain", 40, OT_NAME }, - { "nis-server", 41, OT_ADDR_LIST }, - { "ntp-server", 42, OT_ADDR_LIST }, - { "vendor-encap", 43, OT_INTERNAL }, - { "netbios-ns", 44, OT_ADDR_LIST }, - { "netbios-dd", 45, OT_ADDR_LIST }, - { "netbios-nodetype", 46, 1 }, - { "netbios-scope", 47, 0 }, - { "x-windows-fs", 48, OT_ADDR_LIST }, - { "x-windows-dm", 49, OT_ADDR_LIST }, - { "requested-address", 50, OT_INTERNAL | OT_ADDR_LIST }, - { "lease-time", 51, OT_INTERNAL }, - { "option-overload", 52, OT_INTERNAL }, - { "message-type", 53, OT_INTERNAL, }, - { "server-identifier", 54, OT_INTERNAL | OT_ADDR_LIST }, - { "parameter-request", 55, OT_INTERNAL }, - { "message", 56, OT_INTERNAL }, - { "max-message-size", 57, OT_INTERNAL }, - { "T1", 58, OT_INTERNAL }, - { "T2", 59, OT_INTERNAL }, - { "vendor-class", 60, 0 }, - { "client-id", 61,OT_INTERNAL }, - { "nis+-domain", 64, OT_NAME }, - { "nis+-server", 65, OT_ADDR_LIST }, - { "tftp-server", 66, OT_NAME }, - { "bootfile-name", 67, OT_NAME }, - { "mobile-ip-home", 68, OT_ADDR_LIST }, - { "smtp-server", 69, OT_ADDR_LIST }, - { "pop3-server", 70, OT_ADDR_LIST }, - { "nntp-server", 71, OT_ADDR_LIST }, - { "irc-server", 74, OT_ADDR_LIST }, - { "user-class", 77, 0 }, - { "FQDN", 81, OT_INTERNAL }, - { "agent-id", 82, OT_INTERNAL }, - { "client-arch", 93, 2 }, - { "client-interface-id", 94, 0 }, - { "client-machine-id", 97, 0 }, - { "subnet-select", 118, OT_INTERNAL }, - { "domain-search", 119, OT_RFC1035_NAME }, - { "sip-server", 120, 0 }, - { "classless-static-route", 121, 0 }, - { "vendor-id-encap", 125, 0 }, - { "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */ - { NULL, 0, 0 } -}; - -char *option_string(unsigned char opt, int *is_ip, int *is_name) -{ - int i; - - for (i = 0; opttab[i].name; i++) - if (opttab[i].val == opt) - { - if (is_ip) - *is_ip = !!(opttab[i].size & OT_ADDR_LIST); - if (is_name) - *is_name = !!(opttab[i].size & OT_NAME); - return opttab[i].name; - } - - return NULL; -} - -#endif - /* We hide metacharaters in quoted strings by mapping them into the ASCII control character space. Note that the \0, \t \b \r \033 and \n characters are carefully placed in the following sequence so that they map to themselves: it is therefore possible to call @@ -602,20 +630,37 @@ static int atoi_check16(char *a, int *res) return 1; } + +#ifdef HAVE_DNSSEC +static int atoi_check8(char *a, int *res) +{ + if (!(atoi_check(a, res)) || + *res < 0 || + *res > 0xff) + return 0; + + return 1; +} +#endif -static void add_txt(char *name, char *txt) +static void add_txt(char *name, char *txt, int stat) { - size_t len = strlen(txt); struct txt_record *r = opt_malloc(sizeof(struct txt_record)); + + if (txt) + { + size_t len = strlen(txt); + r->txt = opt_malloc(len+1); + r->len = len+1; + *(r->txt) = len; + memcpy((r->txt)+1, txt, len); + } + r->stat = stat; r->name = opt_string_alloc(name); r->next = daemon->txt; daemon->txt = r; r->class = C_CHAOS; - r->txt = opt_malloc(len+1); - r->len = len+1; - *(r->txt) = len; - memcpy((r->txt)+1, txt, len); } static void do_usage(void) @@ -675,18 +720,152 @@ static void do_usage(void) } } -#ifdef HAVE_DHCP -static void display_opts(void) +#define ret_err(x) do { strcpy(errstr, (x)); return 0; } while (0) + +char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, int *flags) +{ + int source_port = 0, serv_port = NAMESERVER_PORT; + char *portno, *source; +#ifdef HAVE_IPV6 + int scope_index = 0; + char *scope_id; +#endif + + if (!arg || strlen(arg) == 0) + { + *flags |= SERV_NO_ADDR; + *interface = 0; + return NULL; + } + + if ((source = split_chr(arg, '@')) && /* is there a source. */ + (portno = split_chr(source, '#')) && + !atoi_check16(portno, &source_port)) + return _("bad port"); + + if ((portno = split_chr(arg, '#')) && /* is there a port no. */ + !atoi_check16(portno, &serv_port)) + return _("bad port"); + +#ifdef HAVE_IPV6 + scope_id = split_chr(arg, '%'); +#endif + + if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0) + { + addr->in.sin_port = htons(serv_port); + addr->sa.sa_family = source_addr->sa.sa_family = AF_INET; +#ifdef HAVE_SOCKADDR_SA_LEN + source_addr->in.sin_len = addr->in.sin_len = sizeof(struct sockaddr_in); +#endif + source_addr->in.sin_addr.s_addr = INADDR_ANY; + source_addr->in.sin_port = htons(daemon->query_port); + + if (source) + { + if (flags) + *flags |= SERV_HAS_SOURCE; + source_addr->in.sin_port = htons(source_port); + if (!(inet_pton(AF_INET, source, &source_addr->in.sin_addr) > 0)) + { +#if defined(SO_BINDTODEVICE) + source_addr->in.sin_addr.s_addr = INADDR_ANY; + strncpy(interface, source, IF_NAMESIZE - 1); +#else + return _("interface binding not supported"); +#endif + } + } + } +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0) + { + if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0) + return _("bad interface name"); + + addr->in6.sin6_port = htons(serv_port); + addr->in6.sin6_scope_id = scope_index; + source_addr->in6.sin6_addr = in6addr_any; + source_addr->in6.sin6_port = htons(daemon->query_port); + source_addr->in6.sin6_scope_id = 0; + addr->sa.sa_family = source_addr->sa.sa_family = AF_INET6; + addr->in6.sin6_flowinfo = source_addr->in6.sin6_flowinfo = 0; +#ifdef HAVE_SOCKADDR_SA_LEN + addr->in6.sin6_len = source_addr->in6.sin6_len = sizeof(addr->in6); +#endif + if (source) + { + if (flags) + *flags |= SERV_HAS_SOURCE; + source_addr->in6.sin6_port = htons(source_port); + if (inet_pton(AF_INET6, source, &source_addr->in6.sin6_addr) == 0) + { +#if defined(SO_BINDTODEVICE) + source_addr->in6.sin6_addr = in6addr_any; + strncpy(interface, source, IF_NAMESIZE - 1); +#else + return _("interface binding not supported"); +#endif + } + } + } +#endif + else + return _("bad address"); + + return NULL; +} + +static struct server *add_rev4(struct in_addr addr, int msize) +{ + struct server *serv = opt_malloc(sizeof(struct server)); + in_addr_t a = ntohl(addr.s_addr) >> 8; + char *p; + + memset(serv, 0, sizeof(struct server)); + p = serv->domain = opt_malloc(25); /* strlen("xxx.yyy.zzz.in-addr.arpa")+1 */ + + if (msize == 24) + p += sprintf(p, "%d.", a & 0xff); + a = a >> 8; + if (msize != 8) + p += sprintf(p, "%d.", a & 0xff); + a = a >> 8; + p += sprintf(p, "%d.in-addr.arpa", a & 0xff); + + serv->flags = SERV_HAS_DOMAIN; + serv->next = daemon->servers; + daemon->servers = serv; + + return serv; + +} + +static struct server *add_rev6(struct in6_addr *addr, int msize) { + struct server *serv = opt_malloc(sizeof(struct server)); + char *p; int i; + + memset(serv, 0, sizeof(struct server)); + p = serv->domain = opt_malloc(73); /* strlen("32*<n.>ip6.arpa")+1 */ + + for (i = msize-1; i >= 0; i -= 4) + { + int dig = ((unsigned char *)addr)[i>>3]; + p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4); + } + p += sprintf(p, "ip6.arpa"); - printf(_("Known DHCP options:\n")); + serv->flags = SERV_HAS_DOMAIN; + serv->next = daemon->servers; + daemon->servers = serv; - for (i = 0; opttab[i].name; i++) - if (!(opttab[i].size & OT_INTERNAL)) - printf("%3d %s\n", opttab[i].val, opttab[i].name); + return serv; } +#ifdef HAVE_DHCP + static int is_tag_prefix(char *arg) { if (arg && (strstr(arg, "net:") == arg || strstr(arg, "tag:") == arg)) @@ -704,14 +883,16 @@ static char *set_prefix(char *arg) } /* This is too insanely large to keep in-line in the switch */ -static char *parse_dhcp_opt(char *arg, int flags) +static int parse_dhcp_opt(char *errstr, char *arg, int flags) { struct dhcp_opt *new = opt_malloc(sizeof(struct dhcp_opt)); char lenchar = 0, *cp; - int i, addrs, digs, is_addr, is_hex, is_dec, is_string, dots; - char *comma = NULL, *problem = NULL; + int addrs, digs, is_addr, is_addr6, is_hex, is_dec, is_string, dots; + char *comma = NULL; struct dhcp_netid *np = NULL; - unsigned char opt_len = 0; + u16 opt_len = 0; + int is6 = 0; + int option_ok = 0; new->len = 0; new->flags = flags; @@ -731,22 +912,48 @@ static char *parse_dhcp_opt(char *arg, int flags) { new->opt = atoi(arg); opt_len = 0; + option_ok = 1; break; } if (strstr(arg, "option:") == arg) { - for (i = 0; opttab[i].name; i++) - if (!(opttab[i].size & OT_INTERNAL) && - strcasecmp(opttab[i].name, arg+7) == 0) - { - new->opt = opttab[i].val; - opt_len = opttab[i].size; - break; - } - /* option:<optname> must follow tag and vendor string. */ + if ((new->opt = lookup_dhcp_opt(AF_INET, arg+7)) != -1) + { + opt_len = lookup_dhcp_len(AF_INET, new->opt); + /* option:<optname> must follow tag and vendor string. */ + if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH) + option_ok = 1; + } + break; + } +#ifdef HAVE_DHCP6 + else if (strstr(arg, "option6:") == arg) + { + for (cp = arg+8; *cp; cp++) + if (*cp < '0' || *cp > '9') + break; + + if (!*cp) + { + new->opt = atoi(arg+8); + opt_len = 0; + option_ok = 1; + } + else + { + if ((new->opt = lookup_dhcp_opt(AF_INET6, arg+8)) != -1) + { + opt_len = lookup_dhcp_len(AF_INET6, new->opt); + if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH) + option_ok = 1; + } + } + /* option6:<opt>|<optname> must follow tag and vendor string. */ + is6 = 1; break; } +#endif else if (strstr(arg, "vendor:") == arg) { new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7); @@ -763,7 +970,7 @@ static char *parse_dhcp_opt(char *arg, int flags) new->flags |= DHOPT_RFC3925; if (flags == DHOPT_MATCH) { - new->opt = 1; /* avoid error below */ + option_ok = 1; break; } } @@ -781,27 +988,33 @@ static char *parse_dhcp_opt(char *arg, int flags) arg = comma; } - - if (opt_len == 0 && - !(new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE | DHOPT_RFC3925))) - for (i = 0; opttab[i].name; i++) - if (new->opt == opttab[i].val) - { - opt_len = opttab[i].size; - if (opt_len & OT_INTERNAL) - opt_len = 0; - break; - } +#ifdef HAVE_DHCP6 + if (is6) + { + if (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE)) + ret_err(_("unsupported encapsulation for IPv6 option")); + + if (opt_len == 0 && + !(new->flags & DHOPT_RFC3925)) + opt_len = lookup_dhcp_len(AF_INET6, new->opt); + } + else +#endif + if (opt_len == 0 && + !(new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE | DHOPT_RFC3925))) + opt_len = lookup_dhcp_len(AF_INET, new->opt); + /* option may be missing with rfc3925 match */ - if (new->opt == 0) - problem = _("bad dhcp-option"); - else if (comma) + if (!option_ok) + ret_err(_("bad dhcp-option")); + + if (comma) { /* characterise the value */ char c; int found_dig = 0; - is_addr = is_hex = is_dec = is_string = 1; + is_addr = is_addr6 = is_hex = is_dec = is_string = 1; addrs = digs = 1; dots = 0; for (cp = comma; (c = *cp); cp++) @@ -817,17 +1030,17 @@ static char *parse_dhcp_opt(char *arg, int flags) } else if (c == '/') { - is_dec = is_hex = 0; + is_addr6 = is_dec = is_hex = 0; if (cp == comma) /* leading / means a pathname */ is_addr = 0; } else if (c == '.') { - is_dec = is_hex = 0; + is_addr6 = is_dec = is_hex = 0; dots++; } else if (c == '-') - is_hex = is_addr = 0; + is_hex = is_addr = is_addr6 = 0; else if (c == ' ') is_dec = is_hex = 0; else if (!(c >='0' && c <= '9')) @@ -844,26 +1057,66 @@ static char *parse_dhcp_opt(char *arg, int flags) if (!((c >='A' && c <= 'F') || (c >='a' && c <= 'f') || (c == '*' && (flags & DHOPT_MATCH)))) - is_hex = 0; + { + is_hex = 0; + if (c != '[' && c != ']') + is_addr6 = 0; + } } else found_dig = 1; if (!found_dig) is_dec = is_addr = 0; - + /* We know that some options take addresses */ if (opt_len & OT_ADDR_LIST) { is_string = is_dec = is_hex = 0; - if (!is_addr || dots == 0) - problem = _("bad IP address"); + + if (!is6 && (!is_addr || dots == 0)) + ret_err(_("bad IP address")); + + if (is6 && !is_addr6) + ret_err(_("bad IPv6 address")); } /* or names */ - else if (opt_len & (OT_NAME | OT_RFC1035_NAME)) - is_addr = is_dec = is_hex = 0; + else if (opt_len & (OT_NAME | OT_RFC1035_NAME | OT_CSTRING)) + is_addr6 = is_addr = is_dec = is_hex = 0; + + if (found_dig && (opt_len & OT_TIME) && strlen(comma) > 0) + { + int val, fac = 1; + + switch (comma[strlen(comma) - 1]) + { + case 'w': + case 'W': + fac *= 7; + /* fall through */ + case 'd': + case 'D': + fac *= 24; + /* fall though */ + case 'h': + case 'H': + fac *= 60; + /* fall through */ + case 'm': + case 'M': + fac *= 60; + /* fall through */ + case 's': + case 'S': + comma[strlen(comma) - 1] = 0; + } - if (is_hex && digs > 1) + new->len = 4; + new->val = opt_malloc(4); + val = atoi(comma); + *((int *)new->val) = htonl(val * fac); + } + else if (is_hex && digs > 1) { new->len = digs; new->val = opt_malloc(new->len); @@ -896,7 +1149,7 @@ static char *parse_dhcp_opt(char *arg, int flags) for (i=0; i<new->len; i++) new->val[i] = val>>((new->len - i - 1)*8); } - else if (is_addr) + else if (is_addr && !is6) { struct in_addr in; unsigned char *op; @@ -917,7 +1170,7 @@ static char *parse_dhcp_opt(char *arg, int flags) cp = comma; comma = split(cp); slash = split_chr(cp, '/'); - in.s_addr = inet_addr(cp); + inet_pton(AF_INET, cp, &in); if (!slash) { memcpy(op, &in, INADDRSZ); @@ -941,11 +1194,37 @@ static char *parse_dhcp_opt(char *arg, int flags) } new->len = op - new->val; } + else if (is_addr6 && is6) + { + unsigned char *op; + new->val = op = opt_malloc(16 * addrs); + new->flags |= DHOPT_ADDR6; + while (addrs--) + { + cp = comma; + comma = split(cp); + + /* check for [1234::7] */ + if (*cp == '[') + cp++; + if (strlen(cp) > 1 && cp[strlen(cp)-1] == ']') + cp[strlen(cp)-1] = 0; + + if (inet_pton(AF_INET6, cp, op)) + { + op += IN6ADDRSZ; + continue; + } + + ret_err(_("bad IPv6 address")); + } + new->len = op - new->val; + } else if (is_string) { - /* text arg */ + /* text arg */ if ((new->opt == OPTION_DOMAIN_SEARCH || new->opt == OPTION_SIP_SERVER) && - !(new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925))) + !is6 && !(new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925))) { /* dns search, RFC 3397, or SIP, RFC 3361 */ unsigned char *q, *r, *tail; @@ -964,10 +1243,8 @@ static char *parse_dhcp_opt(char *arg, int flags) if (strcmp (arg, ".") != 0) { if (!(dom = canonicalise_opt(arg))) - { - problem = _("bad domain in dhcp-option"); - break; - } + ret_err(_("bad domain in dhcp-option")); + domlen = strlen(dom) + 2; } @@ -1018,6 +1295,72 @@ static char *parse_dhcp_opt(char *arg, int flags) new->len = (int) len + header_size; new->val = m; } +#ifdef HAVE_DHCP6 + else if (comma && (opt_len & OT_CSTRING)) + { + /* length fields are two bytes so need 16 bits for each string */ + int i, commas = 1; + unsigned char *p, *newp; + + for (i = 0; comma[i]; i++) + if (comma[i] == ',') + commas++; + + newp = opt_malloc(strlen(comma)+(2*commas)); + p = newp; + arg = comma; + comma = split(arg); + + while (arg && *arg) + { + u16 len = strlen(arg); + unhide_metas(arg); + PUTSHORT(len, p); + memcpy(p, arg, len); + p += len; + + arg = comma; + comma = split(arg); + } + + new->val = newp; + new->len = p - newp; + } + else if (comma && (opt_len & OT_RFC1035_NAME)) + { + unsigned char *p = NULL, *newp, *end; + int len = 0; + arg = comma; + comma = split(arg); + + while (arg && *arg) + { + char *dom = canonicalise_opt(arg); + if (!dom) + ret_err(_("bad domain in dhcp-option")); + + newp = opt_malloc(len + strlen(dom) + 2); + + if (p) + { + memcpy(newp, p, len); + free(p); + } + + p = newp; + end = do_rfc1035_name(p + len, dom); + *end++ = 0; + len = end - p; + free(dom); + + arg = comma; + comma = split(arg); + } + + new->val = p; + new->len = len; + } +#endif else { new->len = strlen(comma); @@ -1028,33 +1371,42 @@ static char *parse_dhcp_opt(char *arg, int flags) } } - if ((new->len > 255) || + if (!is6 && + ((new->len > 255) || (new->len > 253 && (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))) || - (new->len > 250 && (new->flags & DHOPT_RFC3925))) - problem = _("dhcp-option too long"); + (new->len > 250 && (new->flags & DHOPT_RFC3925)))) + ret_err(_("dhcp-option too long")); - if (!problem) + if (flags == DHOPT_MATCH) { - if (flags == DHOPT_MATCH) + if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) || + !new->netid || + new->netid->next) + ret_err(_("illegal dhcp-match")); + + if (is6) { - if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) || - !new->netid || - new->netid->next) - problem = _("illegal dhcp-match"); - else - { - new->next = daemon->dhcp_match; - daemon->dhcp_match = new; - } + new->next = daemon->dhcp_match6; + daemon->dhcp_match6 = new; } - else + else { - new->next = daemon->dhcp_opts; - daemon->dhcp_opts = new; + new->next = daemon->dhcp_match; + daemon->dhcp_match = new; } } - - return problem; + else if (is6) + { + new->next = daemon->dhcp_opts6; + daemon->dhcp_opts6 = new; + } + else + { + new->next = daemon->dhcp_opts; + daemon->dhcp_opts = new; + } + + return 1; } #endif @@ -1067,13 +1419,21 @@ void set_option_bool(unsigned int opt) daemon->options2 |= 1u << (opt - 32); } -static char *one_opt(int option, char *arg, char *gen_prob, int command_line) +void reset_option_bool(unsigned int opt) +{ + if (opt < 32) + daemon->options &= ~(1u << opt); + else + daemon->options2 &= ~(1u << (opt - 32)); +} + +static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only) { int i; - char *comma, *problem = NULL;; + char *comma; if (option == '?') - return gen_prob; + ret_err(gen_err); for (i=0; usage[i].opt != 0; i++) if (usage[i].opt == option) @@ -1084,7 +1444,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) { /* command line */ if (rept == ARG_USED_CL) - return _("illegal repeated flag"); + ret_err(_("illegal repeated flag")); if (rept == ARG_ONE) usage[i].rept = ARG_USED_CL; } @@ -1092,7 +1452,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) { /* allow file to override command line */ if (rept == ARG_USED_FILE) - return _("illegal repeated keyword"); + ret_err(_("illegal repeated keyword")); if (rept == ARG_USED_CL || rept == ARG_ONE) usage[i].rept = ARG_USED_FILE; } @@ -1100,7 +1460,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) if (rept != ARG_DUP && rept != ARG_ONE && rept != ARG_USED_CL) { set_option_bool(rept); - return NULL; + return 1; } break; @@ -1127,7 +1487,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) struct list { char *suffix; struct list *next; - } *ignore_suffix = NULL, *li; + } *ignore_suffix = NULL, *match_suffix = NULL, *li; comma = split(arg); if (!(directory = opt_string_alloc(arg))) @@ -1136,12 +1496,25 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) for (arg = comma; arg; arg = comma) { comma = split(arg); - li = opt_malloc(sizeof(struct list)); - li->next = ignore_suffix; - ignore_suffix = li; - /* Have to copy: buffer is overwritten */ - li->suffix = opt_string_alloc(arg); - }; + if (strlen(arg) != 0) + { + li = opt_malloc(sizeof(struct list)); + if (*arg == '*') + { + li->next = match_suffix; + match_suffix = li; + /* Have to copy: buffer is overwritten */ + li->suffix = opt_string_alloc(arg+1); + } + else + { + li->next = ignore_suffix; + ignore_suffix = li; + /* Have to copy: buffer is overwritten */ + li->suffix = opt_string_alloc(arg); + } + } + } if (!(dir_stream = opendir(directory))) die(_("cannot access directory %s: %s"), directory, EC_FILE); @@ -1158,6 +1531,20 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) ent->d_name[0] == '.') continue; + if (match_suffix) + { + for (li = match_suffix; li; li = li->next) + { + /* check for required suffices */ + size_t ls = strlen(li->suffix); + if (len > ls && + strcmp(li->suffix, &ent->d_name[len - ls]) == 0) + break; + } + if (!li) + continue; + } + for (li = ignore_suffix; li; li = li->next) { /* check for proscribed suffices */ @@ -1174,14 +1561,14 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) strcat(path, "/"); strcat(path, ent->d_name); + /* files must be readable */ if (stat(path, &buf) == -1) die(_("cannot access %s: %s"), path, EC_FILE); + /* only reg files allowed. */ - if (!S_ISREG(buf.st_mode)) - continue; + if (S_ISREG(buf.st_mode)) + one_file(path, 0); - /* files must be readable */ - one_file(path, 0); free(path); } @@ -1193,10 +1580,34 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) free(ignore_suffix->suffix); free(ignore_suffix); } - + for(; match_suffix; match_suffix = li) + { + li = match_suffix->next; + free(match_suffix->suffix); + free(match_suffix); + } break; } + case LOPT_ADD_SBNET: /* --add-subnet */ + set_option_bool(OPT_CLIENT_SUBNET); + if (arg) + { + comma = split(arg); + if (!atoi_check(arg, &daemon->addr4_netmask) || + (comma && !atoi_check(comma, &daemon->addr6_netmask))) + ret_err(gen_err); + } + break; + + case '1': /* --enable-dbus */ + set_option_bool(OPT_DBUS); + if (arg) + daemon->dbus_name = opt_string_alloc(arg); + else + daemon->dbus_name = DNSMASQ_SERVICE; + break; + case '8': /* --log-facility */ /* may be a filename */ if (strchr(arg, '/') || strcmp (arg, "-") == 0) @@ -1204,7 +1615,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) else { #ifdef __ANDROID__ - problem = _("setting log facility is not possible under Android"); + ret_err(_("setting log facility is not possible under Android")); #else for (i = 0; facilitynames[i].c_name; i++) if (hostname_isequal((char *)facilitynames[i].c_name, arg)) @@ -1213,7 +1624,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) if (facilitynames[i].c_name) daemon->log_fac = facilitynames[i].c_val; else - problem = _("bad log facility"); + ret_err(_("bad log facility")); #endif } break; @@ -1251,6 +1662,10 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) daemon->resolv_files = list; break; } + + case LOPT_SERVERS_FILE: + daemon->servers_file = opt_string_alloc(arg); + break; case 'm': /* --mx-host */ { @@ -1262,12 +1677,12 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) { char *prefstr; if ((prefstr = split(comma)) && !atoi_check16(prefstr, &pref)) - problem = _("bad MX preference"); + ret_err(_("bad MX preference")); } if (!(name = canonicalise_opt(arg)) || (comma && !(target = canonicalise_opt(comma)))) - problem = _("bad MX name"); + ret_err(_("bad MX name")); new = opt_malloc(sizeof(struct mx_srv_record)); new->next = daemon->mxnames; @@ -1281,7 +1696,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) case 't': /* --mx-target */ if (!(daemon->mxtarget = canonicalise_opt(arg))) - problem = _("bad MX target"); + ret_err(_("bad MX target")); break; #ifdef HAVE_DHCP @@ -1289,23 +1704,35 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) daemon->lease_file = opt_string_alloc(arg); break; - case '6': /* --dhcp-script */ + /* Sorry about the gross pre-processor abuse */ + case '6': /* --dhcp-script */ + case LOPT_LUASCRIPT: /* --dhcp-luascript */ # if defined(NO_FORK) - problem = _("cannot run scripts under uClinux"); + ret_err(_("cannot run scripts under uClinux")); # elif !defined(HAVE_SCRIPT) - problem = _("recompile with HAVE_SCRIPT defined to enable lease-change scripts"); + ret_err(_("recompile with HAVE_SCRIPT defined to enable lease-change scripts")); # else - daemon->lease_change_command = opt_string_alloc(arg); + if (option == LOPT_LUASCRIPT) +# if !defined(HAVE_LUASCRIPT) + ret_err(_("recompile with HAVE_LUASCRIPT defined to enable Lua scripts")); +# else + daemon->luascript = opt_string_alloc(arg); +# endif + else + daemon->lease_change_command = opt_string_alloc(arg); # endif break; -#endif - - case LOPT_DHCP_HOST: /* --dhcp-hostfile */ - case LOPT_DHCP_OPTS: /* --dhcp-optsfile */ - case 'H': /* --addn-hosts */ +#endif /* HAVE_DHCP */ + + case LOPT_DHCP_HOST: /* --dhcp-hostsfile */ + case LOPT_DHCP_OPTS: /* --dhcp-optsfile */ + case LOPT_DHCP_INOTIFY: /* --dhcp-hostsdir */ + case LOPT_DHOPT_INOTIFY: /* --dhcp-optsdir */ + case LOPT_HOST_INOTIFY: /* --hostsdir */ + case 'H': /* --addn-hosts */ { struct hostsfile *new = opt_malloc(sizeof(struct hostsfile)); - static int hosts_index = 1; + static unsigned int hosts_index = SRC_AH; new->fname = opt_string_alloc(arg); new->index = hosts_index++; new->flags = 0; @@ -1319,15 +1746,193 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) new->next = daemon->dhcp_hosts_file; daemon->dhcp_hosts_file = new; } - else if (option == LOPT_DHCP_OPTS) + else if (option == LOPT_DHCP_OPTS) { new->next = daemon->dhcp_opts_file; daemon->dhcp_opts_file = new; } + else + { + new->next = daemon->dynamic_dirs; + daemon->dynamic_dirs = new; + if (option == LOPT_DHCP_INOTIFY) + new->flags |= AH_DHCP_HST; + else if (option == LOPT_DHOPT_INOTIFY) + new->flags |= AH_DHCP_OPT; + else if (option == LOPT_HOST_INOTIFY) + new->flags |= AH_HOSTS; + } + break; } - case 's': /* --domain */ + +#ifdef HAVE_AUTH + case LOPT_AUTHSERV: /* --auth-server */ + if (!(comma = split(arg))) + ret_err(gen_err); + + daemon->authserver = opt_string_alloc(arg); + arg = comma; + do { + struct iname *new = opt_malloc(sizeof(struct iname)); + comma = split(arg); + new->name = NULL; + unhide_metas(arg); + if (inet_pton(AF_INET, arg, &new->addr.in.sin_addr) > 0) + new->addr.sa.sa_family = AF_INET; +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, arg, &new->addr.in6.sin6_addr) > 0) + new->addr.sa.sa_family = AF_INET6; +#endif + else + { + char *fam = split_chr(arg, '/'); + new->name = opt_string_alloc(arg); + new->addr.sa.sa_family = 0; + if (fam) + { + if (strcmp(fam, "4") == 0) + new->addr.sa.sa_family = AF_INET; +#ifdef HAVE_IPV6 + else if (strcmp(fam, "6") == 0) + new->addr.sa.sa_family = AF_INET6; +#endif + else + ret_err(gen_err); + } + } + new->next = daemon->authinterface; + daemon->authinterface = new; + + arg = comma; + } while (arg); + + break; + + case LOPT_AUTHSFS: /* --auth-sec-servers */ + { + struct name_list *new; + + do { + comma = split(arg); + new = opt_malloc(sizeof(struct name_list)); + new->name = opt_string_alloc(arg); + new->next = daemon->secondary_forward_server; + daemon->secondary_forward_server = new; + arg = comma; + } while (arg); + break; + } + + case LOPT_AUTHZONE: /* --auth-zone */ + { + struct auth_zone *new; + + comma = split(arg); + + new = opt_malloc(sizeof(struct auth_zone)); + new->domain = opt_string_alloc(arg); + new->subnet = NULL; + new->interface_names = NULL; + new->next = daemon->auth_zones; + daemon->auth_zones = new; + + while ((arg = comma)) + { + int prefixlen = 0; + char *prefix; + struct addrlist *subnet = NULL; + struct all_addr addr; + + comma = split(arg); + prefix = split_chr(arg, '/'); + + if (prefix && !atoi_check(prefix, &prefixlen)) + ret_err(gen_err); + + if (inet_pton(AF_INET, arg, &addr.addr.addr4)) + { + subnet = opt_malloc(sizeof(struct addrlist)); + subnet->prefixlen = (prefixlen == 0) ? 24 : prefixlen; + subnet->flags = ADDRLIST_LITERAL; + } +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, arg, &addr.addr.addr6)) + { + subnet = opt_malloc(sizeof(struct addrlist)); + subnet->prefixlen = (prefixlen == 0) ? 64 : prefixlen; + subnet->flags = ADDRLIST_LITERAL | ADDRLIST_IPV6; + } +#endif + else + { + struct auth_name_list *name = opt_malloc(sizeof(struct auth_name_list)); + name->name = opt_string_alloc(arg); + name->flags = AUTH4 | AUTH6; + name->next = new->interface_names; + new->interface_names = name; + if (prefix) + { + if (prefixlen == 4) + name->flags &= ~AUTH6; +#ifdef HAVE_IPV6 + else if (prefixlen == 6) + name->flags &= ~AUTH4; +#endif + else + ret_err(gen_err); + } + } + + if (subnet) + { + subnet->addr = addr; + subnet->next = new->subnet; + new->subnet = subnet; + } + } + break; + } + + case LOPT_AUTHSOA: /* --auth-soa */ + comma = split(arg); + daemon->soa_sn = (u32)atoi(arg); + if (comma) + { + char *cp; + arg = comma; + comma = split(arg); + daemon->hostmaster = opt_string_alloc(arg); + for (cp = daemon->hostmaster; *cp; cp++) + if (*cp == '@') + *cp = '.'; + + if (comma) + { + arg = comma; + comma = split(arg); + daemon->soa_refresh = (u32)atoi(arg); + if (comma) + { + arg = comma; + comma = split(arg); + daemon->soa_retry = (u32)atoi(arg); + if (comma) + { + arg = comma; + comma = split(arg); + daemon->soa_expiry = (u32)atoi(arg); + } + } + } + } + + break; +#endif + + case 's': /* --domain */ + case LOPT_SYNTH: /* --synth-domain */ if (strcmp (arg, "#") == 0) set_option_bool(OPT_RESOLV_DOMAIN); else @@ -1335,82 +1940,157 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) char *d; comma = split(arg); if (!(d = canonicalise_opt(arg))) - option = '?'; + ret_err(gen_err); else { if (comma) { - struct cond_domain *new = safe_malloc(sizeof(struct cond_domain)); + struct cond_domain *new = opt_malloc(sizeof(struct cond_domain)); char *netpart; + + new->prefix = NULL; unhide_metas(comma); if ((netpart = split_chr(comma, '/'))) { - int msize, mask; + int msize; + arg = split(netpart); - if ((new->start.s_addr = inet_addr(comma)) == (in_addr_t)-1 || - !atoi_check(netpart, &msize)) - option = '?'; - else + if (!atoi_check(netpart, &msize)) + ret_err(gen_err); + else if (inet_pton(AF_INET, comma, &new->start)) { - mask = (1 << (32 - msize)) - 1; + int mask = (1 << (32 - msize)) - 1; + new->is6 = 0; new->start.s_addr = ntohl(htonl(new->start.s_addr) & ~mask); new->end.s_addr = new->start.s_addr | htonl(mask); if (arg) { - /* generate the equivalent of - local=/<domain>/ - local=/xxx.yyy.zzz.in-addr.arpa/ */ - - if (strcmp(arg, "local") != 0 || - (msize != 8 && msize != 16 && msize != 24)) - option = '?'; + if (option != 's') + { + if (!(new->prefix = canonicalise_opt(arg)) || + strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN) + ret_err(_("bad prefix")); + } + else if (strcmp(arg, "local") != 0 || + (msize != 8 && msize != 16 && msize != 24)) + ret_err(gen_err); else { - struct server *serv = opt_malloc(sizeof(struct server)); - in_addr_t a = ntohl(new->start.s_addr) >> 8; - char *p; + /* generate the equivalent of + local=/xxx.yyy.zzz.in-addr.arpa/ */ + struct server *serv = add_rev4(new->start, msize); + serv->flags |= SERV_NO_ADDR; + /* local=/<domain>/ */ + serv = opt_malloc(sizeof(struct server)); memset(serv, 0, sizeof(struct server)); serv->domain = d; serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR; serv->next = daemon->servers; daemon->servers = serv; - + } + } + } +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, comma, &new->start6)) + { + u64 mask = (1LLU << (128 - msize)) - 1LLU; + u64 addrpart = addr6part(&new->start6); + new->is6 = 1; + + /* prefix==64 overflows the mask calculation above */ + if (msize == 64) + mask = (u64)-1LL; + + new->end6 = new->start6; + setaddr6part(&new->start6, addrpart & ~mask); + setaddr6part(&new->end6, addrpart | mask); + + if (msize < 64) + ret_err(gen_err); + else if (arg) + { + if (option != 's') + { + if (!(new->prefix = canonicalise_opt(arg)) || + strlen(new->prefix) > MAXLABEL - INET6_ADDRSTRLEN) + ret_err(_("bad prefix")); + } + else if (strcmp(arg, "local") != 0 || ((msize & 4) != 0)) + ret_err(gen_err); + else + { + /* generate the equivalent of + local=/xxx.yyy.zzz.ip6.arpa/ */ + struct server *serv = add_rev6(&new->start6, msize); + serv->flags |= SERV_NO_ADDR; + + /* local=/<domain>/ */ serv = opt_malloc(sizeof(struct server)); memset(serv, 0, sizeof(struct server)); - p = serv->domain = opt_malloc(25); /* strlen("xxx.yyy.zzz.in-addr.arpa")+1 */ - - if (msize == 24) - p += sprintf(p, "%d.", a & 0xff); - a = a >> 8; - if (msize != 8) - p += sprintf(p, "%d.", a & 0xff); - a = a >> 8; - p += sprintf(p, "%d.in-addr.arpa", a & 0xff); - + serv->domain = d; serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR; serv->next = daemon->servers; daemon->servers = serv; } } } +#endif + else + ret_err(gen_err); } - else if ((arg = split(comma))) + else { - if ((new->start.s_addr = inet_addr(comma)) == (in_addr_t)-1 || - (new->end.s_addr = inet_addr(arg)) == (in_addr_t)-1) - option = '?'; + char *prefstr; + arg = split(comma); + prefstr = split(arg); + + if (inet_pton(AF_INET, comma, &new->start)) + { + new->is6 = 0; + if (!arg) + new->end.s_addr = new->start.s_addr; + else if (!inet_pton(AF_INET, arg, &new->end)) + ret_err(gen_err); + } +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, comma, &new->start6)) + { + new->is6 = 1; + if (!arg) + memcpy(&new->end6, &new->start6, IN6ADDRSZ); + else if (!inet_pton(AF_INET6, arg, &new->end6)) + ret_err(gen_err); + } +#endif + else + ret_err(gen_err); + + if (option != 's' && prefstr) + { + if (!(new->prefix = canonicalise_opt(prefstr)) || + strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN) + ret_err(_("bad prefix")); + } } - else if ((new->start.s_addr = new->end.s_addr = inet_addr(comma)) == (in_addr_t)-1) - option = '?'; new->domain = d; - new->next = daemon->cond_domain; - daemon->cond_domain = new; + if (option == 's') + { + new->next = daemon->cond_domain; + daemon->cond_domain = new; + } + else + { + new->next = daemon->synth_domains; + daemon->synth_domains = new; + } } - else + else if (option == 's') daemon->domain_suffix = d; + else + ret_err(gen_err); } } break; @@ -1439,11 +2119,17 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) /* new->name may be NULL if someone does "interface=" to disable all interfaces except loop. */ new->name = opt_string_alloc(arg); - new->isloop = new->used = 0; + new->used = 0; arg = comma; } while (arg); break; + case LOPT_TFTP: /* --enable-tftp */ + set_option_bool(OPT_TFTP); + if (!arg) + break; + /* fall through */ + case 'I': /* --except-interface */ case '2': /* --no-dhcp-interface */ do { @@ -1455,6 +2141,11 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) new->next = daemon->if_except; daemon->if_except = new; } + else if (option == LOPT_TFTP) + { + new->next = daemon->tftp_interfaces; + daemon->tftp_interfaces = new; + } else { new->next = daemon->dhcp_except; @@ -1465,30 +2156,40 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) break; case 'B': /* --bogus-nxdomain */ - { + case LOPT_IGNORE_ADDR: /* --ignore-address */ + { struct in_addr addr; unhide_metas(arg); - if (arg && (addr.s_addr = inet_addr(arg)) != (in_addr_t)-1) + if (arg && (inet_pton(AF_INET, arg, &addr) > 0)) { struct bogus_addr *baddr = opt_malloc(sizeof(struct bogus_addr)); - baddr->next = daemon->bogus_addr; - daemon->bogus_addr = baddr; + if (option == 'B') + { + baddr->next = daemon->bogus_addr; + daemon->bogus_addr = baddr; + } + else + { + baddr->next = daemon->ignore_addr; + daemon->ignore_addr = baddr; + } baddr->addr = addr; } else - option = '?'; /* error */ + ret_err(gen_err); /* error */ break; } case 'a': /* --listen-address */ + case LOPT_AUTHPEER: /* --auth-peer */ do { struct iname *new = opt_malloc(sizeof(struct iname)); comma = split(arg); unhide_metas(arg); - new->next = daemon->if_addrs; - if (arg && (new->addr.in.sin_addr.s_addr = inet_addr(arg)) != (in_addr_t)-1) + if (arg && (inet_pton(AF_INET, arg, &new->addr.in.sin_addr) > 0)) { new->addr.sa.sa_family = AF_INET; + new->addr.in.sin_port = 0; #ifdef HAVE_SOCKADDR_SA_LEN new->addr.in.sin_len = sizeof(new->addr.in); #endif @@ -1499,18 +2200,26 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) new->addr.sa.sa_family = AF_INET6; new->addr.in6.sin6_flowinfo = 0; new->addr.in6.sin6_scope_id = 0; + new->addr.in6.sin6_port = 0; #ifdef HAVE_SOCKADDR_SA_LEN new->addr.in6.sin6_len = sizeof(new->addr.in6); #endif } #endif else + ret_err(gen_err); + + new->used = 0; + if (option == 'a') { - option = '?'; /* error */ - break; + new->next = daemon->if_addrs; + daemon->if_addrs = new; } - - daemon->if_addrs = new; + else + { + new->next = daemon->auth_peers; + daemon->auth_peers = new; + } arg = comma; } while (arg); break; @@ -1551,23 +2260,25 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) break; } if (!newlist) - { - option = '?'; - break; - } - + ret_err(gen_err); } else { newlist = opt_malloc(sizeof(struct server)); memset(newlist, 0, sizeof(struct server)); +#ifdef HAVE_LOOP + newlist->uid = rand32(); +#endif } + if (servers_only && option == 'S') + newlist->flags |= SERV_FROM_FILE; + if (option == 'A') { newlist->flags |= SERV_LITERAL_ADDRESS; if (!(newlist->flags & SERV_TYPE)) - option = '?'; + ret_err(gen_err); } else if (option == LOPT_NO_REBIND) newlist->flags |= SERV_NO_REBIND; @@ -1576,83 +2287,19 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) { if (!(newlist->flags & SERV_NO_REBIND)) newlist->flags |= SERV_NO_ADDR; /* no server */ - if (newlist->flags & SERV_LITERAL_ADDRESS) - option = '?'; } else if (strcmp(arg, "#") == 0) { newlist->flags |= SERV_USE_RESOLV; /* treat in ordinary way */ if (newlist->flags & SERV_LITERAL_ADDRESS) - option = '?'; + ret_err(gen_err); } else { - int source_port = 0, serv_port = NAMESERVER_PORT; - char *portno, *source; - - if ((source = split_chr(arg, '@')) && /* is there a source. */ - (portno = split_chr(source, '#')) && - !atoi_check16(portno, &source_port)) - problem = _("bad port"); - - if ((portno = split_chr(arg, '#')) && /* is there a port no. */ - !atoi_check16(portno, &serv_port)) - problem = _("bad port"); - - if ((newlist->addr.in.sin_addr.s_addr = inet_addr(arg)) != (in_addr_t) -1) - { - newlist->addr.in.sin_port = htons(serv_port); - newlist->source_addr.in.sin_port = htons(source_port); - newlist->addr.sa.sa_family = newlist->source_addr.sa.sa_family = AF_INET; -#ifdef HAVE_SOCKADDR_SA_LEN - newlist->source_addr.in.sin_len = newlist->addr.in.sin_len = sizeof(struct sockaddr_in); -#endif - if (source) - { - newlist->flags |= SERV_HAS_SOURCE; - if ((newlist->source_addr.in.sin_addr.s_addr = inet_addr(source)) == (in_addr_t) -1) - { -#if defined(SO_BINDTODEVICE) - newlist->source_addr.in.sin_addr.s_addr = INADDR_ANY; - strncpy(newlist->interface, source, IF_NAMESIZE - 1); -#else - problem = _("interface binding not supported"); -#endif - } - } - else - newlist->source_addr.in.sin_addr.s_addr = INADDR_ANY; - } -#ifdef HAVE_IPV6 - else if (inet_pton(AF_INET6, arg, &newlist->addr.in6.sin6_addr) > 0) - { - newlist->addr.in6.sin6_port = htons(serv_port); - newlist->source_addr.in6.sin6_port = htons(source_port); - newlist->addr.sa.sa_family = newlist->source_addr.sa.sa_family = AF_INET6; -#ifdef HAVE_SOCKADDR_SA_LEN - newlist->addr.in6.sin6_len = newlist->source_addr.in6.sin6_len = sizeof(newlist->addr.in6); -#endif - if (source) - { - newlist->flags |= SERV_HAS_SOURCE; - if (inet_pton(AF_INET6, source, &newlist->source_addr.in6.sin6_addr) == 0) - { -#if defined(SO_BINDTODEVICE) - newlist->source_addr.in6.sin6_addr = in6addr_any; - strncpy(newlist->interface, source, IF_NAMESIZE - 1); -#else - problem = _("interface binding not supported"); -#endif - } - } - else - newlist->source_addr.in6.sin6_addr = in6addr_any; - } -#endif - else - option = '?'; /* error */ - + char *err = parse_server(arg, &newlist->addr, &newlist->source_addr, newlist->interface, &newlist->flags); + if (err) + ret_err(err); } serv = newlist; @@ -1661,19 +2308,122 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) serv->next->flags = serv->flags; serv->next->addr = serv->addr; serv->next->source_addr = serv->source_addr; + strcpy(serv->next->interface, serv->interface); serv = serv->next; } serv->next = daemon->servers; daemon->servers = newlist; break; } + + case LOPT_REV_SERV: /* --rev-server */ + { + char *string; + int size; + struct server *serv; + struct in_addr addr4; +#ifdef HAVE_IPV6 + struct in6_addr addr6; +#endif + + unhide_metas(arg); + if (!arg || !(comma=split(arg)) || !(string = split_chr(arg, '/')) || !atoi_check(string, &size)) + ret_err(gen_err); + + if (inet_pton(AF_INET, arg, &addr4)) + serv = add_rev4(addr4, size); +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, arg, &addr6)) + serv = add_rev6(&addr6, size); +#endif + else + ret_err(gen_err); + + string = parse_server(comma, &serv->addr, &serv->source_addr, serv->interface, &serv->flags); + + if (string) + ret_err(string); + + if (servers_only) + serv->flags |= SERV_FROM_FILE; + + break; + } + + case LOPT_IPSET: /* --ipset */ +#ifndef HAVE_IPSET + ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives")); + break; +#else + { + struct ipsets ipsets_head; + struct ipsets *ipsets = &ipsets_head; + int size; + char *end; + char **sets, **sets_pos; + memset(ipsets, 0, sizeof(struct ipsets)); + unhide_metas(arg); + if (arg && *arg == '/') + { + arg++; + while ((end = split_chr(arg, '/'))) + { + char *domain = NULL; + /* elide leading dots - they are implied in the search algorithm */ + while (*arg == '.') + arg++; + /* # matches everything and becomes a zero length domain string */ + if (strcmp(arg, "#") == 0 || !*arg) + domain = ""; + else if (strlen(arg) != 0 && !(domain = canonicalise_opt(arg))) + option = '?'; + ipsets->next = opt_malloc(sizeof(struct ipsets)); + ipsets = ipsets->next; + memset(ipsets, 0, sizeof(struct ipsets)); + ipsets->domain = domain; + arg = end; + } + } + else + { + ipsets->next = opt_malloc(sizeof(struct ipsets)); + ipsets = ipsets->next; + memset(ipsets, 0, sizeof(struct ipsets)); + ipsets->domain = ""; + } + if (!arg || !*arg) + { + option = '?'; + break; + } + size = 2; + for (end = arg; *end; ++end) + if (*end == ',') + ++size; + + sets = sets_pos = opt_malloc(sizeof(char *) * size); + + do { + end = split(arg); + *sets_pos++ = opt_string_alloc(arg); + arg = end; + } while (end); + *sets_pos = 0; + for (ipsets = &ipsets_head; ipsets->next; ipsets = ipsets->next) + ipsets->next->sets = sets; + ipsets->next = daemon->ipsets; + daemon->ipsets = ipsets_head.next; + + break; + } +#endif case 'c': /* --cache-size */ { int size; if (!atoi_check(arg, &size)) - option = '?'; + ret_err(gen_err); else { /* zero is OK, and means no caching. */ @@ -1690,23 +2440,29 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) case 'p': /* --port */ if (!atoi_check16(arg, &daemon->port)) - option = '?'; + ret_err(gen_err); break; case LOPT_MINPORT: /* --min-port */ if (!atoi_check16(arg, &daemon->min_port)) - option = '?'; + ret_err(gen_err); break; case '0': /* --dns-forward-max */ if (!atoi_check(arg, &daemon->ftabsize)) - option = '?'; + ret_err(gen_err); break; + case 'q': /* --log-queries */ + set_option_bool(OPT_LOG); + if (arg && strcmp(arg, "extra") == 0) + set_option_bool(OPT_EXTRALOG); + break; + case LOPT_MAX_LOGS: /* --log-async */ daemon->max_logs = LOG_MAX; /* default */ if (arg && !atoi_check(arg, &daemon->max_logs)) - option = '?'; + ret_err(gen_err); else if (daemon->max_logs > 100) daemon->max_logs = 100; break; @@ -1715,14 +2471,14 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) { int i; if (!atoi_check(arg, &i)) - option = '?'; + ret_err(gen_err); daemon->edns_pktsz = (unsigned short)i; break; } case 'Q': /* --query-port */ if (!atoi_check16(arg, &daemon->query_port)) - option = '?'; + ret_err(gen_err); /* if explicitly set to zero, use single OS ephemeral port and disable random ports */ if (daemon->query_port == 0) @@ -1732,14 +2488,27 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) case 'T': /* --local-ttl */ case LOPT_NEGTTL: /* --neg-ttl */ case LOPT_MAXTTL: /* --max-ttl */ + case LOPT_MINCTTL: /* --min-cache-ttl */ + case LOPT_MAXCTTL: /* --max-cache-ttl */ + case LOPT_AUTHTTL: /* --auth-ttl */ { int ttl; if (!atoi_check(arg, &ttl)) - option = '?'; + ret_err(gen_err); else if (option == LOPT_NEGTTL) daemon->neg_ttl = (unsigned long)ttl; else if (option == LOPT_MAXTTL) daemon->max_ttl = (unsigned long)ttl; + else if (option == LOPT_MINCTTL) + { + if (ttl > TTL_FLOOR_LIMIT) + ttl = TTL_FLOOR_LIMIT; + daemon->min_cache_ttl = (unsigned long)ttl; + } + else if (option == LOPT_MAXCTTL) + daemon->max_cache_ttl = (unsigned long)ttl; + else if (option == LOPT_AUTHTTL) + daemon->auth_ttl = (unsigned long)ttl; else daemon->local_ttl = (unsigned long)ttl; break; @@ -1748,26 +2517,14 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) #ifdef HAVE_DHCP case 'X': /* --dhcp-lease-max */ if (!atoi_check(arg, &daemon->dhcp_max)) - option = '?'; + ret_err(gen_err); break; #endif #ifdef HAVE_TFTP - case LOPT_TFTP: /* --enable-tftp */ - if (arg) - { - struct interface_list *new = opt_malloc(sizeof(struct interface_list)); - new->interface = opt_string_alloc(arg); - new->next = daemon->tftp_interfaces; - daemon->tftp_interfaces = new; - } - else - daemon->tftp_unlimited = 1; - break; - case LOPT_TFTP_MAX: /* --tftp-max */ if (!atoi_check(arg, &daemon->tftp_max)) - option = '?'; + ret_err(gen_err); break; case LOPT_PREFIX: /* --tftp-prefix */ @@ -1788,7 +2545,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) if (!(comma = split(arg)) || !atoi_check16(arg, &daemon->start_tftp_port) || !atoi_check16(comma, &daemon->end_tftp_port)) - problem = _("bad port range"); + ret_err(_("bad port range")); if (daemon->start_tftp_port > daemon->end_tftp_port) { @@ -1804,10 +2561,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) { struct dhcp_bridge *new = opt_malloc(sizeof(struct dhcp_bridge)); if (!(comma = split(arg)) || strlen(arg) > IF_NAMESIZE - 1 ) - { - problem = _("bad bridge-interface"); - break; - } + ret_err(_("bad bridge-interface")); strcpy(new->iface, arg); new->alias = NULL; @@ -1833,21 +2587,11 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) case 'F': /* --dhcp-range */ { int k, leasepos = 2; - char *cp, *a[5] = { NULL, NULL, NULL, NULL, NULL }; + char *cp, *a[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; struct dhcp_context *new = opt_malloc(sizeof(struct dhcp_context)); - new->next = daemon->dhcp; + memset (new, 0, sizeof(*new)); new->lease_time = DEFLEASE; - new->addr_epoch = 0; - new->netmask.s_addr = 0; - new->broadcast.s_addr = 0; - new->router.s_addr = 0; - new->netid.net = NULL; - new->filter = NULL; - new->flags = 0; - new->interface = NULL; - - gen_prob = _("bad dhcp-range"); if (!arg) { @@ -1858,24 +2602,26 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) while(1) { for (cp = arg; *cp; cp++) - if (!(*cp == ' ' || *cp == '.' || (*cp >='0' && *cp <= '9'))) + if (!(*cp == ' ' || *cp == '.' || *cp == ':' || + (*cp >= 'a' && *cp <= 'f') || (*cp >= 'A' && *cp <= 'F') || + (*cp >='0' && *cp <= '9'))) break; if (*cp != ',' && (comma = split(arg))) { - if (strstr(arg, "interface:") == arg) - new->interface = opt_string_alloc(arg+10); - else if (is_tag_prefix(arg)) + if (is_tag_prefix(arg)) { struct dhcp_netid *tt = opt_malloc(sizeof (struct dhcp_netid)); tt->net = opt_string_alloc(arg+4); tt->next = new->filter; - new->filter = tt; + /* ignore empty tag */ + if (tt->net) + new->filter = tt; } else { if (new->netid.net) - problem = _("only one tag allowed"); + ret_err(_("only one tag allowed")); else if (strstr(arg, "set:") == arg) new->netid.net = opt_string_alloc(arg+4); else @@ -1890,53 +2636,139 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) } } - for (k = 1; k < 5; k++) + for (k = 1; k < 8; k++) if (!(a[k] = split(a[k-1]))) break; - if ((k < 2) || ((new->start.s_addr = inet_addr(a[0])) == (in_addr_t)-1)) - option = '?'; - else if (strcmp(a[1], "static") == 0) - { - new->end = new->start; - new->flags |= CONTEXT_STATIC; - } - else if (strcmp(a[1], "proxy") == 0) - { - new->end = new->start; - new->flags |= CONTEXT_PROXY; - } - else if ((new->end.s_addr = inet_addr(a[1])) == (in_addr_t)-1) - option = '?'; - - if (ntohl(new->start.s_addr) > ntohl(new->end.s_addr)) - { - struct in_addr tmp = new->start; - new->start = new->end; - new->end = tmp; - } + if (k < 2) + ret_err(_("bad dhcp-range")); - if (option != '?' && k >= 3 && strchr(a[2], '.') && - ((new->netmask.s_addr = inet_addr(a[2])) != (in_addr_t)-1)) + if (inet_pton(AF_INET, a[0], &new->start)) { - new->flags |= CONTEXT_NETMASK; - leasepos = 3; - if (!is_same_net(new->start, new->end, new->netmask)) - problem = _("inconsistent DHCP range"); + new->next = daemon->dhcp; + daemon->dhcp = new; + new->end = new->start; + if (strcmp(a[1], "static") == 0) + new->flags |= CONTEXT_STATIC; + else if (strcmp(a[1], "proxy") == 0) + new->flags |= CONTEXT_PROXY; + else if (!inet_pton(AF_INET, a[1], &new->end)) + ret_err(_("bad dhcp-range")); + + if (ntohl(new->start.s_addr) > ntohl(new->end.s_addr)) + { + struct in_addr tmp = new->start; + new->start = new->end; + new->end = tmp; + } + + if (k >= 3 && strchr(a[2], '.') && + (inet_pton(AF_INET, a[2], &new->netmask) > 0)) + { + new->flags |= CONTEXT_NETMASK; + leasepos = 3; + if (!is_same_net(new->start, new->end, new->netmask)) + ret_err(_("inconsistent DHCP range")); + } + + if (k >= 4 && strchr(a[3], '.') && + (inet_pton(AF_INET, a[3], &new->broadcast) > 0)) + { + new->flags |= CONTEXT_BRDCAST; + leasepos = 4; + } } - daemon->dhcp = new; - - if (k >= 4 && strchr(a[3], '.') && - ((new->broadcast.s_addr = inet_addr(a[3])) != (in_addr_t)-1)) +#ifdef HAVE_DHCP6 + else if (inet_pton(AF_INET6, a[0], &new->start6)) { - new->flags |= CONTEXT_BRDCAST; - leasepos = 4; + new->flags |= CONTEXT_V6; + new->prefix = 64; /* default */ + new->end6 = new->start6; + new->next = daemon->dhcp6; + daemon->dhcp6 = new; + + for (leasepos = 1; leasepos < k; leasepos++) + { + if (strcmp(a[leasepos], "static") == 0) + new->flags |= CONTEXT_STATIC | CONTEXT_DHCP; + else if (strcmp(a[leasepos], "ra-only") == 0 || strcmp(a[leasepos], "slaac") == 0 ) + new->flags |= CONTEXT_RA; + else if (strcmp(a[leasepos], "ra-names") == 0) + new->flags |= CONTEXT_RA_NAME | CONTEXT_RA; + else if (strcmp(a[leasepos], "ra-advrouter") == 0) + new->flags |= CONTEXT_RA_ROUTER | CONTEXT_RA; + else if (strcmp(a[leasepos], "ra-stateless") == 0) + new->flags |= CONTEXT_RA_STATELESS | CONTEXT_DHCP | CONTEXT_RA; + else if (strcmp(a[leasepos], "off-link") == 0) + new->flags |= CONTEXT_RA_OFF_LINK; + else if (leasepos == 1 && inet_pton(AF_INET6, a[leasepos], &new->end6)) + new->flags |= CONTEXT_DHCP; + else if (strstr(a[leasepos], "constructor:") == a[leasepos]) + { + new->template_interface = opt_string_alloc(a[leasepos] + 12); + new->flags |= CONTEXT_TEMPLATE; + } + else + break; + } + + /* bare integer < 128 is prefix value */ + if (leasepos < k) + { + int pref; + for (cp = a[leasepos]; *cp; cp++) + if (!(*cp >= '0' && *cp <= '9')) + break; + if (!*cp && (pref = atoi(a[leasepos])) <= 128) + { + new->prefix = pref; + leasepos++; + } + } + + if (new->prefix != 64) + { + if (new->flags & CONTEXT_RA) + ret_err(_("prefix length must be exactly 64 for RA subnets")); + else if (new->flags & CONTEXT_TEMPLATE) + ret_err(_("prefix length must be exactly 64 for subnet constructors")); + } + + if (new->prefix < 64) + ret_err(_("prefix length must be at least 64")); + + if (!is_same_net6(&new->start6, &new->end6, new->prefix)) + ret_err(_("inconsistent DHCPv6 range")); + + /* dhcp-range=:: enables DHCP stateless on any interface */ + if (IN6_IS_ADDR_UNSPECIFIED(&new->start6) && !(new->flags & CONTEXT_TEMPLATE)) + new->prefix = 0; + + if (new->flags & CONTEXT_TEMPLATE) + { + struct in6_addr zero; + memset(&zero, 0, sizeof(zero)); + if (!is_same_net6(&zero, &new->start6, new->prefix)) + ret_err(_("prefix must be zero with \"constructor:\" argument")); + } + + if (addr6part(&new->start6) > addr6part(&new->end6)) + { + struct in6_addr tmp = new->start6; + new->start6 = new->end6; + new->end6 = tmp; + } } +#endif + else + ret_err(_("bad dhcp-range")); - if (k >= leasepos+1) + if (leasepos < k) { if (strcmp(a[leasepos], "infinite") == 0) new->lease_time = 0xffffffff; + else if (strcmp(a[leasepos], "deprecated") == 0) + new->flags |= CONTEXT_DEPRECATE; else { int fac = 1; @@ -1944,6 +2776,10 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) { switch (a[leasepos][strlen(a[leasepos]) - 1]) { + case 'w': + case 'W': + fac *= 7; + /* fall through */ case 'd': case 'D': fac *= 24; @@ -1961,6 +2797,13 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) a[leasepos][strlen(a[leasepos]) - 1] = 0; } + for (cp = a[leasepos]; *cp; cp++) + if (!(*cp >= '0' && *cp <= '9')) + break; + + if (*cp || (leasepos+1 < k)) + ret_err(_("bad dhcp-range")); + new->lease_time = atoi(a[leasepos]) * fac; /* Leases of a minute or less confuse some clients, notably Apple's */ @@ -1976,7 +2819,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) case 'G': /* --dhcp-host */ { int j, k = 0; - char *a[6] = { NULL, NULL, NULL, NULL, NULL, NULL }; + char *a[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; struct dhcp_config *new; struct in_addr in; @@ -1988,7 +2831,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) new->netid = NULL; if ((a[0] = arg)) - for (k = 1; k < 6; k++) + for (k = 1; k < 7; k++) if (!(a[k] = split(a[k-1]))) break; @@ -2016,7 +2859,8 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) } if (len == -1) - problem = _("bad hex constant"); + + ret_err(_("bad hex constant")); else if ((new->clid = opt_malloc(len))) { new->flags |= CONFIG_CLID; @@ -2037,12 +2881,34 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) strcpy(newtag->net, arg+4); unhide_metas(newtag->net); } - else + else if (strstr(arg, "tag:") == arg) + ret_err(_("cannot match tags in --dhcp-host")); +#ifdef HAVE_DHCP6 + else if (arg[0] == '[' && arg[strlen(arg)-1] == ']') + { + arg[strlen(arg)-1] = 0; + arg++; + + if (!inet_pton(AF_INET6, arg, &new->addr6)) + ret_err(_("bad IPv6 address")); + + for (i= 0; i < 8; i++) + if (new->addr6.s6_addr[i] != 0) + break; + + /* set WILDCARD if network part all zeros */ + if (i == 8) + new->flags |= CONFIG_WILDCARD; + + new->flags |= CONFIG_ADDR6; + } +#endif + else { struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config)); if ((newhw->hwaddr_len = parse_hex(a[j], newhw->hwaddr, DHCP_CHADDR_MAX, &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1) - problem = _("bad hex constant"); + ret_err(_("bad hex constant")); else { @@ -2051,15 +2917,27 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) } } } - else if (strchr(a[j], '.') && (in.s_addr = inet_addr(a[j])) != (in_addr_t)-1) + else if (strchr(a[j], '.') && (inet_pton(AF_INET, a[j], &in) > 0)) { + struct dhcp_config *configs; + new->addr = in; new->flags |= CONFIG_ADDR; + + /* If the same IP appears in more than one host config, then DISCOVER + for one of the hosts will get the address, but REQUEST will be NAKed, + since the address is reserved by the other one -> protocol loop. */ + for (configs = daemon->dhcp_conf; configs; configs = configs->next) + if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr) + { + sprintf(errstr, _("duplicate dhcp-host IP address %s"), inet_ntoa(in)); + return 0; + } } else { char *cp, *lastp = NULL, last = 0; - int fac = 1; + int fac = 1, isdig = 0; if (strlen(a[j]) > 1) { @@ -2067,6 +2945,10 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) last = *lastp; switch (last) { + case 'w': + case 'W': + fac *= 7; + /* fall through */ case 'd': case 'D': fac *= 24; @@ -2086,9 +2968,11 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) } for (cp = a[j]; *cp; cp++) - if (!isdigit((unsigned char)*cp) && *cp != ' ') + if (isdigit((unsigned char)*cp)) + isdig = 1; + else if (*cp != ' ') break; - + if (*cp) { if (lastp) @@ -2104,13 +2988,13 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) { if (!(new->hostname = canonicalise_opt(a[j])) || !legal_hostname(new->hostname)) - problem = _("bad DHCP host name"); - else - new->flags |= CONFIG_NAME; - new->domain = NULL; + ret_err(_("bad DHCP host name")); + + new->flags |= CONFIG_NAME; + new->domain = strip_hostname(new->hostname); } } - else + else if (isdig) { new->lease_time = atoi(a[j]) * fac; /* Leases of a minute or less confuse @@ -2177,6 +3061,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) else { new->set = NULL; + free(newtag); break; } } @@ -2185,7 +3070,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) } if (!new->set) - problem = _("bad tag-if"); + ret_err(_("bad tag-if")); break; } @@ -2195,12 +3080,11 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) case LOPT_FORCE: /* --dhcp-option-force */ case LOPT_OPTS: case LOPT_MATCH: /* --dhcp-match */ - problem = parse_dhcp_opt(arg, - option == LOPT_FORCE ? DHOPT_FORCE : - (option == LOPT_MATCH ? DHOPT_MATCH : - (option == LOPT_OPTS ? DHOPT_BANK : 0))); - break; - + return parse_dhcp_opt(errstr, arg, + option == LOPT_FORCE ? DHOPT_FORCE : + (option == LOPT_MATCH ? DHOPT_MATCH : + (option == LOPT_OPTS ? DHOPT_BANK : 0))); + case 'M': /* --dhcp-boot */ { struct dhcp_netid *id = NULL; @@ -2215,11 +3099,12 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) }; if (!arg) - option = '?'; + ret_err(gen_err); else { - char *dhcp_file, *dhcp_sname = NULL; + char *dhcp_file, *dhcp_sname = NULL, *tftp_sname = NULL; struct in_addr dhcp_next_server; + struct dhcp_boot *new; comma = split(arg); dhcp_file = opt_string_alloc(arg); dhcp_next_server.s_addr = 0; @@ -2231,22 +3116,30 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) if (comma) { unhide_metas(comma); - if ((dhcp_next_server.s_addr = inet_addr(comma)) == (in_addr_t)-1) - option = '?'; + if (!(inet_pton(AF_INET, comma, &dhcp_next_server) > 0)) + { + /* + * The user may have specified the tftp hostname here. + * save it so that it can be resolved/looked up during + * actual dhcp_reply(). + */ + + tftp_sname = opt_string_alloc(comma); + dhcp_next_server.s_addr = 0; + } } } - if (option != '?') - { - struct dhcp_boot *new = opt_malloc(sizeof(struct dhcp_boot)); - new->file = dhcp_file; - new->sname = dhcp_sname; - new->next_server = dhcp_next_server; - new->netid = id; - new->next = daemon->boot_config; - daemon->boot_config = new; - } + + new = opt_malloc(sizeof(struct dhcp_boot)); + new->file = dhcp_file; + new->sname = dhcp_sname; + new->tftp_sname = tftp_sname; + new->next_server = dhcp_next_server; + new->netid = id; + new->next = daemon->boot_config; + daemon->boot_config = new; } - + break; } @@ -2269,7 +3162,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) } if (!arg) - option = '?'; + ret_err(gen_err); else { comma = split(arg); @@ -2302,6 +3195,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) static int boottype = 32768; new->netid = NULL; + new->sname = NULL; new->server.s_addr = 0; while (is_tag_prefix(arg)) @@ -2348,10 +3242,17 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) new->basename = opt_string_alloc(arg); } - if (comma && (new->server.s_addr = inet_addr(comma)) == (in_addr_t)-1) - option = '?'; + if (comma) + { + if (!inet_pton(AF_INET, comma, &new->server)) + { + new->server.s_addr = 0; + new->sname = opt_string_alloc(comma); + } + + } } - + /* Order matters */ new->next = NULL; if (!daemon->pxe_services) @@ -2369,14 +3270,13 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) } } - option = '?'; - break; + ret_err(gen_err); } case '4': /* --dhcp-mac */ { if (!(comma = split(arg))) - option = '?'; + ret_err(gen_err); else { struct dhcp_mac *new = opt_malloc(sizeof(struct dhcp_mac)); @@ -2384,7 +3284,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) unhide_metas(comma); new->hwaddr_len = parse_hex(comma, new->hwaddr, DHCP_CHADDR_MAX, &new->mask, &new->hwaddr_type); if (new->hwaddr_len == -1) - option = '?'; + ret_err(gen_err); else { new->next = daemon->dhcp_macs; @@ -2393,64 +3293,94 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) } } break; - + +#ifdef OPTION6_PREFIX_CLASS + case LOPT_PREF_CLSS: /* --dhcp-prefix-class */ + { + struct prefix_class *new = opt_malloc(sizeof(struct prefix_class)); + + if (!(comma = split(arg)) || + !atoi_check16(comma, &new->class)) + ret_err(gen_err); + + new->tag.net = opt_string_alloc(set_prefix(arg)); + new->next = daemon->prefix_classes; + daemon->prefix_classes = new; + + break; + } +#endif + + case 'U': /* --dhcp-vendorclass */ case 'j': /* --dhcp-userclass */ case LOPT_CIRCUIT: /* --dhcp-circuitid */ case LOPT_REMOTE: /* --dhcp-remoteid */ case LOPT_SUBSCR: /* --dhcp-subscrid */ { - if (!(comma = split(arg))) - option = '?'; - else - { - unsigned char *p; - int dig = 0; - struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor)); - new->netid.net = opt_string_alloc(set_prefix(arg)); - /* check for hex string - must digits may include : must not have nothing else, - only allowed for agent-options. */ - for (p = (unsigned char *)comma; *p; p++) - if (isxdigit(*p)) - dig = 1; - else if (*p != ':') - break; - unhide_metas(comma); - if (option == 'U' || option == 'j' || *p || !dig) - { - new->len = strlen(comma); - new->data = opt_malloc(new->len); - memcpy(new->data, comma, new->len); - } - else - { - new->len = parse_hex(comma, (unsigned char *)comma, strlen(comma), NULL, NULL); - new->data = opt_malloc(new->len); - memcpy(new->data, comma, new->len); - } + unsigned char *p; + int dig = 0; + struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor)); + + if (!(comma = split(arg))) + ret_err(gen_err); + + new->netid.net = opt_string_alloc(set_prefix(arg)); + /* check for hex string - must digits may include : must not have nothing else, + only allowed for agent-options. */ + + arg = comma; + if ((comma = split(arg))) + { + if (option != 'U' || strstr(arg, "enterprise:") != arg) + ret_err(gen_err); + else + new->enterprise = atoi(arg+11); + } + else + comma = arg; + + for (p = (unsigned char *)comma; *p; p++) + if (isxdigit(*p)) + dig = 1; + else if (*p != ':') + break; + unhide_metas(comma); + if (option == 'U' || option == 'j' || *p || !dig) + { + new->len = strlen(comma); + new->data = opt_malloc(new->len); + memcpy(new->data, comma, new->len); + } + else + { + new->len = parse_hex(comma, (unsigned char *)comma, strlen(comma), NULL, NULL); + new->data = opt_malloc(new->len); + memcpy(new->data, comma, new->len); + } + + switch (option) + { + case 'j': + new->match_type = MATCH_USER; + break; + case 'U': + new->match_type = MATCH_VENDOR; + break; + case LOPT_CIRCUIT: + new->match_type = MATCH_CIRCUIT; + break; + case LOPT_REMOTE: + new->match_type = MATCH_REMOTE; + break; + case LOPT_SUBSCR: + new->match_type = MATCH_SUBSCRIBER; + break; + } + new->next = daemon->dhcp_vendors; + daemon->dhcp_vendors = new; - switch (option) - { - case 'j': - new->match_type = MATCH_USER; - break; - case 'U': - new->match_type = MATCH_VENDOR; - break; - case LOPT_CIRCUIT: - new->match_type = MATCH_CIRCUIT; - break; - case LOPT_REMOTE: - new->match_type = MATCH_REMOTE; - break; - case LOPT_SUBSCR: - new->match_type = MATCH_SUBSCRIBER; - break; - } - new->next = daemon->dhcp_vendors; - daemon->dhcp_vendors = new; - } - break; + break; } case LOPT_ALTPORT: /* --dhcp-alternate-port */ @@ -2464,7 +3394,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) comma = split(arg); if (!atoi_check16(arg, &daemon->dhcp_server_port) || (comma && !atoi_check16(comma, &daemon->dhcp_client_port))) - problem = _("invalid port number"); + ret_err(_("invalid port number")); if (!comma) daemon->dhcp_client_port = daemon->dhcp_server_port+1; } @@ -2525,15 +3455,78 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) while (arg) { struct addr_list *new = opt_malloc(sizeof(struct addr_list)); comma = split(arg); - if ((new->addr.s_addr = inet_addr(arg)) == (in_addr_t)-1) - problem = _("bad dhcp-proxy address"); + if (!(inet_pton(AF_INET, arg, &new->addr) > 0)) + ret_err(_("bad dhcp-proxy address")); new->next = daemon->override_relays; daemon->override_relays = new; arg = comma; } break; + + case LOPT_RELAY: /* --dhcp-relay */ + { + struct dhcp_relay *new = opt_malloc(sizeof(struct dhcp_relay)); + comma = split(arg); + new->interface = opt_string_alloc(split(comma)); + new->iface_index = 0; + if (inet_pton(AF_INET, arg, &new->local) && inet_pton(AF_INET, comma, &new->server)) + { + new->next = daemon->relay4; + daemon->relay4 = new; + } +#ifdef HAVE_DHCP6 + else if (inet_pton(AF_INET6, arg, &new->local) && inet_pton(AF_INET6, comma, &new->server)) + { + new->next = daemon->relay6; + daemon->relay6 = new; + } +#endif + else + ret_err(_("Bad dhcp-relay")); + + break; + } + #endif +#ifdef HAVE_DHCP6 + case LOPT_RA_PARAM: /* --ra-param */ + if ((comma = split(arg))) + { + struct ra_interface *new = opt_malloc(sizeof(struct ra_interface)); + new->lifetime = -1; + new->prio = 0; + new->name = opt_string_alloc(arg); + if (strcasestr(comma, "high") == comma || strcasestr(comma, "low") == comma) + { + if (*comma == 'l' || *comma == 'L') + new->prio = 0x18; + else + new->prio = 0x08; + comma = split(comma); + } + arg = split(comma); + if (!atoi_check(comma, &new->interval) || + (arg && !atoi_check(arg, &new->lifetime))) + ret_err(_("bad RA-params")); + + new->next = daemon->ra_interfaces; + daemon->ra_interfaces = new; + } + break; + + case LOPT_DUID: /* --dhcp-duid */ + if (!(comma = split(arg)) || !atoi_check(arg, (int *)&daemon->duid_enterprise)) + ret_err(_("bad DUID")); + else + { + daemon->duid_config_len = parse_hex(comma,(unsigned char *)comma, strlen(comma), NULL, NULL); + daemon->duid_config = opt_malloc(daemon->duid_config_len); + memcpy(daemon->duid_config, comma, daemon->duid_config_len); + } + break; +#endif + case 'V': /* --alias */ { char *dash, *a[3] = { NULL, NULL, NULL }; @@ -2555,18 +3548,18 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) dash = split_chr(a[0], '-'); if ((k < 2) || - ((new->in.s_addr = inet_addr(a[0])) == (in_addr_t)-1) || - ((new->out.s_addr = inet_addr(a[1])) == (in_addr_t)-1)) + (!(inet_pton(AF_INET, a[0], &new->in) > 0)) || + (!(inet_pton(AF_INET, a[1], &new->out) > 0))) option = '?'; if (k == 3) - new->mask.s_addr = inet_addr(a[2]); + inet_pton(AF_INET, a[2], &new->mask); if (dash && - ((new->end.s_addr = inet_addr(dash)) == (in_addr_t)-1 || + (!(inet_pton(AF_INET, dash, &new->end) > 0) || !is_same_net(new->in, new->end, new->mask) || ntohl(new->in.s_addr) > ntohl(new->end.s_addr))) - problem = _("invalid alias range"); + ret_err(_("invalid alias range")); break; } @@ -2579,15 +3572,30 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) comma = split(arg); if (!comma || !(domain = canonicalise_opt(arg))) - problem = _("bad interface name"); + ret_err(_("bad interface name")); new = opt_malloc(sizeof(struct interface_name)); new->next = NULL; + new->addr = NULL; + /* Add to the end of the list, so that first name of an interface is used for PTR lookups. */ for (up = &daemon->int_names; *up; up = &((*up)->next)); *up = new; new->name = domain; + new->family = 0; + arg = split_chr(comma, '/'); + if (arg) + { + if (strcmp(arg, "4") == 0) + new->family = AF_INET; +#ifdef HAVE_IPV6 + else if (strcmp(arg, "6") == 0) + new->family = AF_INET6; +#endif + else + ret_err(gen_err); + } new->intr = opt_string_alloc(comma); break; } @@ -2595,28 +3603,29 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) case LOPT_CNAME: /* --cname */ { struct cname *new; - + char *alias; + char *target; + if (!(comma = split(arg))) - option = '?'; + ret_err(gen_err); + + alias = canonicalise_opt(arg); + target = canonicalise_opt(comma); + + if (!alias || !target) + ret_err(_("bad CNAME")); else { - char *alias = canonicalise_opt(arg); - char *target = canonicalise_opt(comma); - - if (!alias || !target) - problem = _("bad CNAME"); - else - { - for (new = daemon->cnames; new; new = new->next) - if (hostname_isequal(new->alias, arg)) - problem = _("duplicate CNAME"); - new = opt_malloc(sizeof(struct cname)); - new->next = daemon->cnames; - daemon->cnames = new; - new->alias = alias; - new->target = target; - } + for (new = daemon->cnames; new; new = new->next) + if (hostname_isequal(new->alias, arg)) + ret_err(_("duplicate CNAME")); + new = opt_malloc(sizeof(struct cname)); + new->next = daemon->cnames; + daemon->cnames = new; + new->alias = alias; + new->target = target; } + break; } @@ -2629,7 +3638,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) if (!(dom = canonicalise_opt(arg)) || (comma && !(target = canonicalise_opt(comma)))) - problem = _("bad PTR record"); + ret_err(_("bad PTR record")); else { new = opt_malloc(sizeof(struct ptr_record)); @@ -2660,7 +3669,7 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) !atoi_check16(a[1], &order) || !atoi_check16(a[2], &pref) || (k == 7 && !(replace = canonicalise_opt(a[6])))) - problem = _("bad NAPTR record"); + ret_err(_("bad NAPTR record")); else { new = opt_malloc(sizeof(struct naptr)); @@ -2676,7 +3685,39 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) } break; } - + + case LOPT_RR: /* dns-rr */ + { + struct txt_record *new; + size_t len = len; + char *data; + int val; + + comma = split(arg); + data = split(comma); + + new = opt_malloc(sizeof(struct txt_record)); + new->next = daemon->rr; + daemon->rr = new; + + if (!atoi_check(comma, &val) || + !(new->name = canonicalise_opt(arg)) || + (data && (len = parse_hex(data, (unsigned char *)data, -1, NULL, NULL)) == -1U)) + ret_err(_("bad RR record")); + + new->class = val; + new->len = 0; + + if (data) + { + new->txt=opt_malloc(len); + new->len = len; + memcpy(new->txt, data, len); + } + + break; + } + case 'Y': /* --txt-record */ { struct txt_record *new; @@ -2689,13 +3730,11 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) new->next = daemon->txt; daemon->txt = new; new->class = C_IN; - - if (!(new->name = canonicalise_opt(arg))) - { - problem = _("bad TXT record"); - break; - } + new->stat = 0; + if (!(new->name = canonicalise_opt(arg))) + ret_err(_("bad TXT record")); + len = comma ? strlen(comma) : 0; len += (len/255) + 1; /* room for extra counts */ new->txt = p = opt_malloc(len); @@ -2735,35 +3774,35 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) comma = split(arg); if (!(name = canonicalise_opt(arg))) - problem = _("bad SRV record"); - + ret_err(_("bad SRV record")); + if (comma) { arg = comma; comma = split(arg); - if (!(target = canonicalise_opt(arg)) -) problem = _("bad SRV target"); + if (!(target = canonicalise_opt(arg))) + ret_err(_("bad SRV target")); if (comma) { arg = comma; comma = split(arg); if (!atoi_check16(arg, &port)) - problem = _("invalid port number"); + ret_err(_("invalid port number")); if (comma) { arg = comma; comma = split(arg); if (!atoi_check16(arg, &priority)) - problem = _("invalid priority"); + ret_err(_("invalid priority")); if (comma) { arg = comma; comma = split(arg); if (!atoi_check16(arg, &weight)) - problem = _("invalid weight"); + ret_err(_("invalid weight")); } } } @@ -2781,18 +3820,123 @@ static char *one_opt(int option, char *arg, char *gen_prob, int command_line) break; } - default: - return _("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DBus support)"); + case LOPT_HOST_REC: /* --host-record */ + { + struct host_record *new = opt_malloc(sizeof(struct host_record)); + memset(new, 0, sizeof(struct host_record)); + + if (!arg || !(comma = split(arg))) + ret_err(_("Bad host-record")); + + while (arg) + { + struct all_addr addr; + if (inet_pton(AF_INET, arg, &addr)) + new->addr = addr.addr.addr4; +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, arg, &addr)) + new->addr6 = addr.addr.addr6; +#endif + else + { + int nomem; + char *canon = canonicalise(arg, &nomem); + struct name_list *nl = opt_malloc(sizeof(struct name_list)); + if (!canon) + ret_err(_("Bad name in host-record")); + + nl->name = canon; + /* keep order, so that PTR record goes to first name */ + nl->next = NULL; + if (!new->names) + new->names = nl; + else + { + struct name_list *tmp; + for (tmp = new->names; tmp->next; tmp = tmp->next); + tmp->next = nl; + } + } + + arg = comma; + comma = split(arg); + } - } + /* Keep list order */ + if (!daemon->host_records_tail) + daemon->host_records = new; + else + daemon->host_records_tail->next = new; + new->next = NULL; + daemon->host_records_tail = new; + break; + } - if (problem) - return problem; - - if (option == '?') - return gen_prob; +#ifdef HAVE_DNSSEC + case LOPT_DNSSEC_STAMP: + daemon->timestamp_file = opt_string_alloc(arg); + break; - return NULL; + case LOPT_TRUST_ANCHOR: + { + struct ds_config *new = opt_malloc(sizeof(struct ds_config)); + char *cp, *cp1, *keyhex, *digest, *algo = NULL; + int len; + + new->class = C_IN; + + if ((comma = split(arg)) && (algo = split(comma))) + { + int class = 0; + if (strcmp(comma, "IN") == 0) + class = C_IN; + else if (strcmp(comma, "CH") == 0) + class = C_CHAOS; + else if (strcmp(comma, "HS") == 0) + class = C_HESIOD; + + if (class != 0) + { + new->class = class; + comma = algo; + algo = split(comma); + } + } + + if (!comma || !algo || !(digest = split(algo)) || !(keyhex = split(digest)) || + !atoi_check16(comma, &new->keytag) || + !atoi_check8(algo, &new->algo) || + !atoi_check8(digest, &new->digest_type) || + !(new->name = canonicalise_opt(arg))) + ret_err(_("bad trust anchor")); + + /* Upper bound on length */ + len = (2*strlen(keyhex))+1; + new->digest = opt_malloc(len); + unhide_metas(keyhex); + /* 4034: "Whitespace is allowed within digits" */ + for (cp = keyhex; *cp; ) + if (isspace(*cp)) + for (cp1 = cp; *cp1; cp1++) + *cp1 = *(cp1+1); + else + cp++; + if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1) + ret_err(_("bad HEX in trust anchor")); + + new->next = daemon->ds; + daemon->ds = new; + + break; + } +#endif + + default: + ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)")); + + } + + return 1; } static void read_file(char *file, FILE *f, int hard_opt) @@ -2802,12 +3946,13 @@ static void read_file(char *file, FILE *f, int hard_opt) while (fgets(buff, MAXDNAME, f)) { - int white, i, option; ; - char *errmess, *p, *arg, *start; + int white, i; + volatile int option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt; + char *errmess, *p, *arg = NULL, *start; size_t len; /* Memory allocation failure longjmps here if mem_recover == 1 */ - if (hard_opt) + if (option != 0 || hard_opt == LOPT_REV_SERV) { if (setjmp(mem_jmp)) continue; @@ -2881,7 +4026,7 @@ static void read_file(char *file, FILE *f, int hard_opt) else start[len] = 0; - if (hard_opt != 0) + if (option != 0) arg = start; else if ((p=strchr(start, '='))) { @@ -2893,9 +4038,7 @@ static void read_file(char *file, FILE *f, int hard_opt) else arg = NULL; - if (hard_opt != 0) - option = hard_opt; - else + if (option == 0) { for (option = 0, i = 0; opts[i].name; i++) if (strcmp(opts[i].name, start) == 0) @@ -2910,19 +4053,21 @@ static void read_file(char *file, FILE *f, int hard_opt) errmess = _("extraneous parameter"); else if (opts[i].has_arg == 1 && !arg) errmess = _("missing parameter"); + else if (hard_opt == LOPT_REV_SERV && option != 'S' && option != LOPT_REV_SERV) + errmess = _("illegal option"); } - - if (!errmess) - errmess = one_opt(option, arg, _("error"), 0); - + + oops: if (errmess) + strcpy(daemon->namebuff, errmess); + + if (errmess || !one_opt(option, arg, buff, _("error"), 0, hard_opt == LOPT_REV_SERV)) { - oops: - sprintf(buff, _("%s at line %d of %%s"), errmess, lineno); + sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file); if (hard_opt != 0) - my_syslog(LOG_ERR, buff, file); + my_syslog(LOG_ERR, "%s", daemon->namebuff); else - die(buff, file, EC_BADCONF); + die("%s", daemon->namebuff, EC_BADCONF); } } @@ -2930,7 +4075,21 @@ static void read_file(char *file, FILE *f, int hard_opt) fclose(f); } -static void one_file(char *file, int hard_opt) +#ifdef HAVE_DHCP +int option_read_dynfile(char *file, int flags) +{ + my_syslog(MS_DHCP | LOG_INFO, _("read %s"), file); + + if (flags & AH_DHCP_HST) + return one_file(file, LOPT_BANK); + else if (flags & AH_DHCP_OPT) + return one_file(file, LOPT_OPTS); + + return 0; +} +#endif + +static int one_file(char *file, int hard_opt) { FILE *f; int nofile_ok = 0; @@ -2951,7 +4110,7 @@ static void one_file(char *file, int hard_opt) if (hard_opt == 0 && strcmp(file, "-") == 0) { if (read_stdin == 1) - return; + return 1; read_stdin = 1; file = "stdin"; f = stdin; @@ -2967,7 +4126,7 @@ static void one_file(char *file, int hard_opt) for (r = filesread; r; r = r->next) if (r->dev == statbuf.st_dev && r->ino == statbuf.st_ino) - return; + return 1; r = safe_malloc(sizeof(struct fileread)); r->next = filesread; @@ -2979,14 +4138,14 @@ static void one_file(char *file, int hard_opt) if (!(f = fopen(file, "r"))) { if (errno == ENOENT && nofile_ok) - return; /* No conffile, all done. */ + return 1; /* No conffile, all done. */ else { char *str = _("cannot read %s: %s"); if (hard_opt != 0) { my_syslog(LOG_ERR, str, file, strerror(errno)); - return; + return 0; } else die(str, file, EC_FILE); @@ -2995,15 +4154,17 @@ static void one_file(char *file, int hard_opt) } read_file(file, f, hard_opt); + return 1; } /* expand any name which is a directory */ struct hostsfile *expand_filelist(struct hostsfile *list) { - int i; + unsigned int i; struct hostsfile *ah; - for (i = 0, ah = list; ah; ah = ah->next) + /* find largest used index */ + for (i = SRC_AH, ah = list; ah; ah = ah->next) { if (i <= ah->index) i = ah->index + 1; @@ -3025,7 +4186,7 @@ struct hostsfile *expand_filelist(struct hostsfile *list) /* don't read this as a file */ ah->flags |= AH_INACTIVE; - + if (!(dir_stream = opendir(ah->fname))) my_syslog(LOG_ERR, _("cannot access directory %s: %s"), ah->fname, strerror(errno)); @@ -3097,6 +4258,22 @@ struct hostsfile *expand_filelist(struct hostsfile *list) return list; } +void read_servers_file(void) +{ + FILE *f; + + if (!(f = fopen(daemon->servers_file, "r"))) + { + my_syslog(LOG_ERR, _("cannot read %s: %s"), daemon->servers_file, strerror(errno)); + return; + } + + mark_servers(SERV_FROM_FILE); + cleanup_servers(); + + read_file(daemon->servers_file, f, LOPT_REV_SERV); +} + #ifdef HAVE_DHCP void reread_dhcp(void) @@ -3147,8 +4324,8 @@ void reread_dhcp(void) for (hf = daemon->dhcp_hosts_file; hf; hf = hf->next) if (!(hf->flags & AH_INACTIVE)) { - one_file(hf->fname, LOPT_BANK); - my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname); + if (one_file(hf->fname, LOPT_BANK)) + my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname); } } @@ -3183,8 +4360,8 @@ void reread_dhcp(void) for (hf = daemon->dhcp_opts_file; hf; hf = hf->next) if (!(hf->flags & AH_INACTIVE)) { - one_file(hf->fname, LOPT_OPTS); - my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname); + if (one_file(hf->fname, LOPT_OPTS)) + my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname); } } } @@ -3194,7 +4371,7 @@ void read_opts(int argc, char **argv, char *compile_opts) { char *buff = opt_malloc(MAXDNAME); int option, conffile_opt = '7', testmode = 0; - char *errmess, *arg, *conffile = CONFFILE; + char *arg, *conffile = CONFFILE; opterr = 0; @@ -3217,9 +4394,23 @@ void read_opts(int argc, char **argv, char *compile_opts) daemon->tftp_max = TFTP_MAX_CONNECTIONS; daemon->edns_pktsz = EDNS_PKTSZ; daemon->log_fac = -1; - add_txt("version.bind", "dnsmasq-" VERSION ); - add_txt("authors.bind", "Simon Kelley"); - add_txt("copyright.bind", COPYRIGHT); + daemon->auth_ttl = AUTH_TTL; + daemon->soa_refresh = SOA_REFRESH; + daemon->soa_retry = SOA_RETRY; + daemon->soa_expiry = SOA_EXPIRY; + + add_txt("version.bind", "dnsmasq-" VERSION, 0 ); + add_txt("authors.bind", "Simon Kelley", 0); + add_txt("copyright.bind", COPYRIGHT, 0); + add_txt("cachesize.bind", NULL, TXT_STAT_CACHESIZE); + add_txt("insertions.bind", NULL, TXT_STAT_INSERTS); + add_txt("evictions.bind", NULL, TXT_STAT_EVICTIONS); + add_txt("misses.bind", NULL, TXT_STAT_MISSES); + add_txt("hits.bind", NULL, TXT_STAT_HITS); +#ifdef HAVE_AUTH + add_txt("auth.bind", NULL, TXT_STAT_AUTH); +#endif + add_txt("servers.bind", NULL, TXT_STAT_SERVERS); while (1) { @@ -3256,18 +4447,23 @@ void read_opts(int argc, char **argv, char *compile_opts) testmode = 1; else if (option == 'w') { - if (argc != 3 || strcmp(argv[2], "dhcp") != 0) - do_usage(); #ifdef HAVE_DHCP - else + if (argc == 3 && strcmp(argv[2], "dhcp") == 0) display_opts(); +#ifdef HAVE_DHCP6 + else if (argc == 3 && strcmp(argv[2], "dhcp6") == 0) + display_opts6(); +#endif + else #endif + do_usage(); + exit(0); } else if (option == 'v') { printf(_("Dnsmasq version %s %s\n"), VERSION, COPYRIGHT); - printf(_("Compile time options %s\n\n"), compile_opts); + printf(_("Compile time options: %s\n\n"), compile_opts); printf(_("This software comes with ABSOLUTELY NO WARRANTY.\n")); printf(_("Dnsmasq is free software, and you are welcome to redistribute it\n")); printf(_("under the terms of the GNU General Public License, version 2 or 3.\n")); @@ -3281,32 +4477,39 @@ void read_opts(int argc, char **argv, char *compile_opts) else { #ifdef HAVE_GETOPT_LONG - errmess = one_opt(option, arg, _("try --help"), 1); + if (!one_opt(option, arg, daemon->namebuff, _("try --help"), 1, 0)) #else - errmess = one_opt(option, arg, _("try -w"), 1); + if (!one_opt(option, arg, daemon->namebuff, _("try -w"), 1, 0)) #endif - if (errmess) - die(_("bad command line options: %s"), errmess, EC_BADCONF); + die(_("bad command line options: %s"), daemon->namebuff, EC_BADCONF); } } if (conffile) - one_file(conffile, conffile_opt); + { + one_file(conffile, conffile_opt); + if (conffile_opt == 0) + free(conffile); + } /* port might not be known when the address is parsed - fill in here */ if (daemon->servers) { struct server *tmp; for (tmp = daemon->servers; tmp; tmp = tmp->next) - if (!(tmp->flags & SERV_HAS_SOURCE)) - { - if (tmp->source_addr.sa.sa_family == AF_INET) - tmp->source_addr.in.sin_port = htons(daemon->query_port); + { + tmp->edns_pktsz = daemon->edns_pktsz; + + if (!(tmp->flags & SERV_HAS_SOURCE)) + { + if (tmp->source_addr.sa.sa_family == AF_INET) + tmp->source_addr.in.sin_port = htons(daemon->query_port); #ifdef HAVE_IPV6 - else if (tmp->source_addr.sa.sa_family == AF_INET6) - tmp->source_addr.in6.sin6_port = htons(daemon->query_port); + else if (tmp->source_addr.sa.sa_family == AF_INET6) + tmp->source_addr.in6.sin6_port = htons(daemon->query_port); #endif - } + } + } } if (daemon->if_addrs) @@ -3320,7 +4523,15 @@ void read_opts(int argc, char **argv, char *compile_opts) tmp->addr.in6.sin6_port = htons(daemon->port); #endif /* IPv6 */ } - + + /* create default, if not specified */ + if (daemon->authserver && !daemon->hostmaster) + { + strcpy(buff, "hostmaster."); + strcat(buff, daemon->authserver); + daemon->hostmaster = opt_string_alloc(buff); + } + /* only one of these need be specified: the other defaults to the host-name */ if (option_bool(OPT_LOCALMX) || daemon->mxnames || daemon->mxtarget) { @@ -3408,6 +4619,11 @@ void read_opts(int argc, char **argv, char *compile_opts) else if (option_bool(OPT_DHCP_FQDN)) die(_("there must be a default domain when --dhcp-fqdn is set"), NULL, EC_BADCONF); + /* If there's access-control config, then ignore --local-service, it's intended + as a system default to keep otherwise unconfigured installations safe. */ + if (daemon->if_names || daemon->if_except || daemon->if_addrs || daemon->authserver) + reset_option_bool(OPT_LOCAL_SERVICE); + if (testmode) { fprintf(stderr, "dnsmasq: %s.\n", _("syntax check OK")); diff --git a/src/outpacket.c b/src/outpacket.c new file mode 100644 index 0000000..5b1ff93 --- /dev/null +++ b/src/outpacket.c @@ -0,0 +1,108 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "dnsmasq.h" + +#ifdef HAVE_DHCP6 + +static size_t outpacket_counter; + +void end_opt6(int container) +{ + void *p = daemon->outpacket.iov_base + container + 2; + u16 len = outpacket_counter - container - 4 ; + + PUTSHORT(len, p); +} + +int save_counter(int newval) +{ + int ret = outpacket_counter; + if (newval != -1) + outpacket_counter = newval; + + return ret; +} + +void *expand(size_t headroom) +{ + void *ret; + + if (expand_buf(&daemon->outpacket, outpacket_counter + headroom)) + { + ret = daemon->outpacket.iov_base + outpacket_counter; + outpacket_counter += headroom; + return ret; + } + + return NULL; +} + +int new_opt6(int opt) +{ + int ret = outpacket_counter; + void *p; + + if ((p = expand(4))) + { + PUTSHORT(opt, p); + PUTSHORT(0, p); + } + + return ret; +} + +void *put_opt6(void *data, size_t len) +{ + void *p; + + if ((p = expand(len)) && data) + memcpy(p, data, len); + + return p; +} + +void put_opt6_long(unsigned int val) +{ + void *p; + + if ((p = expand(4))) + PUTLONG(val, p); +} + +void put_opt6_short(unsigned int val) +{ + void *p; + + if ((p = expand(2))) + PUTSHORT(val, p); +} + +void put_opt6_char(unsigned int val) +{ + unsigned char *p; + + if ((p = expand(1))) + *p = val; +} + +void put_opt6_string(char *s) +{ + put_opt6(s, strlen(s)); +} + +#endif diff --git a/src/poll.c b/src/poll.c new file mode 100644 index 0000000..d71b1b9 --- /dev/null +++ b/src/poll.c @@ -0,0 +1,125 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +/* Wrapper for poll(). Allocates and extends array of struct pollfds, + keeps them in fd order so that we can set and test conditions on + fd using a simple but efficient binary chop. */ + +/* poll_reset() + poll_listen(fd, event) + . + . + poll_listen(fd, event); + + hits = do_poll(timeout); + + if (poll_check(fd, event) + . + . + + if (poll_check(fd, event) + . + . + + event is OR of POLLIN, POLLOUT, POLLERR, etc +*/ + +static struct pollfd *pollfds = NULL; +static nfds_t nfds, arrsize = 0; + +/* Binary search. Returns either the pollfd with fd, or + if the fd doesn't match, or return equals nfds, the entry + to the left of which a new record should be inserted. */ +static nfds_t fd_search(int fd) +{ + nfds_t left, right, mid; + + if ((right = nfds) == 0) + return 0; + + left = 0; + + while (1) + { + if (right == left + 1) + return (pollfds[left].fd >= fd) ? left : right; + + mid = (left + right)/2; + + if (pollfds[mid].fd > fd) + right = mid; + else + left = mid; + } +} + +void poll_reset(void) +{ + nfds = 0; +} + +int do_poll(int timeout) +{ + return poll(pollfds, nfds, timeout); +} + +int poll_check(int fd, short event) +{ + nfds_t i = fd_search(fd); + + if (i < nfds && pollfds[i].fd == fd) + return pollfds[i].revents & event; + + return 0; +} + +void poll_listen(int fd, short event) +{ + nfds_t i = fd_search(fd); + + if (i < nfds && pollfds[i].fd == fd) + pollfds[i].events |= event; + else + { + if (arrsize != nfds) + memmove(&pollfds[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd)); + else + { + /* Array too small, extend. */ + struct pollfd *new; + + arrsize = (arrsize == 0) ? 64 : arrsize * 2; + + if (!(new = whine_malloc(arrsize * sizeof(struct pollfd)))) + return; + + if (pollfds) + { + memcpy(new, pollfds, i * sizeof(struct pollfd)); + memcpy(&new[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd)); + free(pollfds); + } + + pollfds = new; + } + + pollfds[i].fd = fd; + pollfds[i].events = event; + nfds++; + } +} diff --git a/src/radv-protocol.h b/src/radv-protocol.h new file mode 100644 index 0000000..4cc1ea4 --- /dev/null +++ b/src/radv-protocol.h @@ -0,0 +1,58 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define ALL_NODES "FF02::1" +#define ALL_ROUTERS "FF02::2" + +struct ping_packet { + u8 type, code; + u16 checksum; + u16 identifier; + u16 sequence_no; +}; + +struct ra_packet { + u8 type, code; + u16 checksum; + u8 hop_limit, flags; + u16 lifetime; + u32 reachable_time; + u32 retrans_time; +}; + +struct neigh_packet { + u8 type, code; + u16 checksum; + u16 reserved; + struct in6_addr target; +}; + +struct prefix_opt { + u8 type, len, prefix_len, flags; + u32 valid_lifetime, preferred_lifetime, reserved; + struct in6_addr prefix; +}; + +#define ICMP6_OPT_SOURCE_MAC 1 +#define ICMP6_OPT_PREFIX 3 +#define ICMP6_OPT_MTU 5 +#define ICMP6_OPT_ADV_INTERVAL 7 +#define ICMP6_OPT_RT_INFO 24 +#define ICMP6_OPT_RDNSS 25 +#define ICMP6_OPT_DNSSL 31 + + + diff --git a/src/radv.c b/src/radv.c new file mode 100644 index 0000000..39f1e92 --- /dev/null +++ b/src/radv.c @@ -0,0 +1,976 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +/* NB. This code may be called during a DHCPv4 or transaction which is in ping-wait + It therefore cannot use any DHCP buffer resources except outpacket, which is + not used by DHCPv4 code. This code may also be called when DHCP 4 or 6 isn't + active, so we ensure that outpacket is allocated here too */ + +#include "dnsmasq.h" + +#ifdef HAVE_DHCP6 + +#include <netinet/icmp6.h> + +struct ra_param { + time_t now; + int ind, managed, other, found_context, first, adv_router; + char *if_name; + struct dhcp_netid *tags; + struct in6_addr link_local, link_global, ula; + unsigned int glob_pref_time, link_pref_time, ula_pref_time, adv_interval, prio; +}; + +struct search_param { + time_t now; int iface; + char name[IF_NAMESIZE+1]; +}; + +struct alias_param { + int iface; + struct dhcp_bridge *bridge; + int num_alias_ifs; + int max_alias_ifs; + int *alias_ifs; +}; + +static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *dest); +static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_addr *dest, + int send_iface); +static int send_ra_to_aliases(int index, unsigned int type, char *mac, size_t maclen, void *parm); +static int add_prefixes(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, + unsigned int preferred, unsigned int valid, void *vparam); +static int iface_search(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, + int prefered, int valid, void *vparam); +static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm); +static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now); +static unsigned int calc_lifetime(struct ra_interface *ra); +static unsigned int calc_interval(struct ra_interface *ra); +static unsigned int calc_prio(struct ra_interface *ra); +static struct ra_interface *find_iface_param(char *iface); + +static int hop_limit; + +void ra_init(time_t now) +{ + struct icmp6_filter filter; + int fd; +#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6) + int class = IPTOS_CLASS_CS6; +#endif + int val = 255; /* radvd uses this value */ + socklen_t len = sizeof(int); + struct dhcp_context *context; + + /* ensure this is around even if we're not doing DHCPv6 */ + expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet)); + + /* See if we're guessing SLAAC addresses, if so we need to recieve ping replies */ + for (context = daemon->dhcp6; context; context = context->next) + if ((context->flags & CONTEXT_RA_NAME)) + break; + + /* Need ICMP6 socket for transmission for DHCPv6 even when not doing RA. */ + + ICMP6_FILTER_SETBLOCKALL(&filter); + if (daemon->doing_ra) + { + ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); + if (context) + ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter); + } + + if ((fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1 || + getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hop_limit, &len) || +#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6) + setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 || +#endif + !fix_fd(fd) || + !set_ipv6pktinfo(fd) || + setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)) || + setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)) || + setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) == -1) + die (_("cannot create ICMPv6 socket: %s"), NULL, EC_BADNET); + + daemon->icmp6fd = fd; + + if (daemon->doing_ra) + ra_start_unsolicted(now, NULL); +} + +void ra_start_unsolicted(time_t now, struct dhcp_context *context) +{ + /* init timers so that we do ra's for some/all soon. some ra_times will end up zeroed + if it's not appropriate to advertise those contexts. + This gets re-called on a netlink route-change to re-do the advertisement + and pick up new interfaces */ + + if (context) + context->ra_short_period_start = context->ra_time = now; + else + for (context = daemon->dhcp6; context; context = context->next) + if (!(context->flags & CONTEXT_TEMPLATE)) + { + context->ra_time = now + (rand16()/13000); /* range 0 - 5 */ + /* re-do frequently for a minute or so, in case the first gets lost. */ + context->ra_short_period_start = now; + } +} + +void icmp6_packet(time_t now) +{ + char interface[IF_NAMESIZE+1]; + ssize_t sz; + int if_index = 0; + struct cmsghdr *cmptr; + struct msghdr msg; + union { + struct cmsghdr align; /* this ensures alignment */ + char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } control_u; + struct sockaddr_in6 from; + unsigned char *packet; + struct iname *tmp; + + /* Note: use outpacket for input buffer */ + msg.msg_control = control_u.control6; + msg.msg_controllen = sizeof(control_u); + msg.msg_flags = 0; + msg.msg_name = &from; + msg.msg_namelen = sizeof(from); + msg.msg_iov = &daemon->outpacket; + msg.msg_iovlen = 1; + + if ((sz = recv_dhcp_packet(daemon->icmp6fd, &msg)) == -1 || sz < 8) + return; + + packet = (unsigned char *)daemon->outpacket.iov_base; + + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) + if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) + { + union { + unsigned char *c; + struct in6_pktinfo *p; + } p; + p.c = CMSG_DATA(cmptr); + + if_index = p.p->ipi6_ifindex; + } + + if (!indextoname(daemon->icmp6fd, if_index, interface)) + return; + + if (!iface_check(AF_LOCAL, NULL, interface, NULL)) + return; + + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, interface)) + return; + + if (packet[1] != 0) + return; + + if (packet[0] == ICMP6_ECHO_REPLY) + lease_ping_reply(&from.sin6_addr, packet, interface); + else if (packet[0] == ND_ROUTER_SOLICIT) + { + char *mac = ""; + struct dhcp_bridge *bridge, *alias; + + /* look for link-layer address option for logging */ + if (sz >= 16 && packet[8] == ICMP6_OPT_SOURCE_MAC && (packet[9] * 8) + 8 <= sz) + { + print_mac(daemon->namebuff, &packet[10], (packet[9] * 8) - 2); + mac = daemon->namebuff; + } + + if (!option_bool(OPT_QUIET_RA)) + my_syslog(MS_DHCP | LOG_INFO, "RTR-SOLICIT(%s) %s", interface, mac); + + /* If the incoming interface is an alias of some other one (as + specified by the --bridge-interface option), send an RA using + the context of the aliased interface. */ + for (bridge = daemon->bridges; bridge; bridge = bridge->next) + { + int bridge_index = if_nametoindex(bridge->iface); + if (bridge_index) + { + for (alias = bridge->alias; alias; alias = alias->next) + if (wildcard_matchn(alias->iface, interface, IF_NAMESIZE)) + { + /* Send an RA on if_index with information from + bridge_index. */ + send_ra_alias(now, bridge_index, bridge->iface, NULL, if_index); + break; + } + if (alias) + break; + } + } + + /* If the incoming interface wasn't an alias, send an RA using + the context of the incoming interface. */ + if (!bridge) + /* source address may not be valid in solicit request. */ + send_ra(now, if_index, interface, !IN6_IS_ADDR_UNSPECIFIED(&from.sin6_addr) ? &from.sin6_addr : NULL); + } +} + +static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_addr *dest, int send_iface) +{ + struct ra_packet *ra; + struct ra_param parm; + struct sockaddr_in6 addr; + struct dhcp_context *context, *tmp, **up; + struct dhcp_netid iface_id; + struct dhcp_opt *opt_cfg; + struct ra_interface *ra_param = find_iface_param(iface_name); + int done_dns = 0, old_prefix = 0; + unsigned int min_pref_time; +#ifdef HAVE_LINUX_NETWORK + FILE *f; +#endif + + parm.ind = iface; + parm.managed = 0; + parm.other = 0; + parm.found_context = 0; + parm.adv_router = 0; + parm.if_name = iface_name; + parm.first = 1; + parm.now = now; + parm.glob_pref_time = parm.link_pref_time = parm.ula_pref_time = 0; + parm.adv_interval = calc_interval(ra_param); + parm.prio = calc_prio(ra_param); + + save_counter(0); + ra = expand(sizeof(struct ra_packet)); + + ra->type = ND_ROUTER_ADVERT; + ra->code = 0; + ra->hop_limit = hop_limit; + ra->flags = parm.prio; + ra->lifetime = htons(calc_lifetime(ra_param)); + ra->reachable_time = 0; + ra->retrans_time = 0; + + /* set tag with name == interface */ + iface_id.net = iface_name; + iface_id.next = NULL; + parm.tags = &iface_id; + + for (context = daemon->dhcp6; context; context = context->next) + { + context->flags &= ~CONTEXT_RA_DONE; + context->netid.next = &context->netid; + } + + if (!iface_enumerate(AF_INET6, &parm, add_prefixes)) + return; + + /* Find smallest preferred time within address classes, + to use as lifetime for options. This is a rather arbitrary choice. */ + min_pref_time = 0xffffffff; + if (parm.glob_pref_time != 0 && parm.glob_pref_time < min_pref_time) + min_pref_time = parm.glob_pref_time; + + if (parm.ula_pref_time != 0 && parm.ula_pref_time < min_pref_time) + min_pref_time = parm.ula_pref_time; + + if (parm.link_pref_time != 0 && parm.link_pref_time < min_pref_time) + min_pref_time = parm.link_pref_time; + + /* Look for constructed contexts associated with addresses which have gone, + and advertise them with preferred_time == 0 RFC 6204 4.3 L-13 */ + for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp) + { + tmp = context->next; + + if (context->if_index == iface && (context->flags & CONTEXT_OLD)) + { + unsigned int old = difftime(now, context->address_lost_time); + + if (old > context->saved_valid) + { + /* We've advertised this enough, time to go */ + *up = context->next; + free(context); + } + else + { + struct prefix_opt *opt; + struct in6_addr local = context->start6; + int do_slaac = 0; + + old_prefix = 1; + + /* zero net part of address */ + setaddr6part(&local, addr6part(&local) & ~((context->prefix == 64) ? (u64)-1LL : (1LLU << (128 - context->prefix)) - 1LLU)); + + + if (context->flags & CONTEXT_RA) + { + do_slaac = 1; + if (context->flags & CONTEXT_DHCP) + { + parm.other = 1; + if (!(context->flags & CONTEXT_RA_STATELESS)) + parm.managed = 1; + } + } + else + { + /* don't do RA for non-ra-only unless --enable-ra is set */ + if (option_bool(OPT_RA)) + { + parm.managed = 1; + parm.other = 1; + } + } + + if ((opt = expand(sizeof(struct prefix_opt)))) + { + opt->type = ICMP6_OPT_PREFIX; + opt->len = 4; + opt->prefix_len = context->prefix; + /* autonomous only if we're not doing dhcp, set + "on-link" unless "off-link" was specified */ + opt->flags = (do_slaac ? 0x40 : 0) | + ((context->flags & CONTEXT_RA_OFF_LINK) ? 0 : 0x80); + opt->valid_lifetime = htonl(context->saved_valid - old); + opt->preferred_lifetime = htonl(0); + opt->reserved = 0; + opt->prefix = local; + + inet_ntop(AF_INET6, &local, daemon->addrbuff, ADDRSTRLEN); + if (!option_bool(OPT_QUIET_RA)) + my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s old prefix", iface_name, daemon->addrbuff); + } + + up = &context->next; + } + } + else + up = &context->next; + } + + /* If we're advertising only old prefixes, set router lifetime to zero. */ + if (old_prefix && !parm.found_context) + ra->lifetime = htons(0); + + /* No prefixes to advertise. */ + if (!old_prefix && !parm.found_context) + return; + + /* If we're sending router address instead of prefix in at least on prefix, + include the advertisement interval option. */ + if (parm.adv_router) + { + put_opt6_char(ICMP6_OPT_ADV_INTERVAL); + put_opt6_char(1); + put_opt6_short(0); + /* interval value is in milliseconds */ + put_opt6_long(1000 * calc_interval(find_iface_param(iface_name))); + } + +#ifdef HAVE_LINUX_NETWORK + /* Note that IPv6 MTU is not necessarilly the same as the IPv4 MTU + available from SIOCGIFMTU */ + sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", iface_name); + if ((f = fopen(daemon->namebuff, "r"))) + { + if (fgets(daemon->namebuff, MAXDNAME, f)) + { + put_opt6_char(ICMP6_OPT_MTU); + put_opt6_char(1); + put_opt6_short(0); + put_opt6_long(atoi(daemon->namebuff)); + } + fclose(f); + } +#endif + + iface_enumerate(AF_LOCAL, &send_iface, add_lla); + + /* RDNSS, RFC 6106, use relevant DHCP6 options */ + (void)option_filter(parm.tags, NULL, daemon->dhcp_opts6); + + for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next) + { + int i; + + /* netids match and not encapsulated? */ + if (!(opt_cfg->flags & DHOPT_TAGOK)) + continue; + + if (opt_cfg->opt == OPTION6_DNS_SERVER) + { + struct in6_addr *a; + int len; + + done_dns = 1; + + if (opt_cfg->len == 0) + continue; + + /* reduce len for any addresses we can't substitute */ + for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, i = 0; + i < opt_cfg->len; i += IN6ADDRSZ, a++) + if ((IN6_IS_ADDR_UNSPECIFIED(a) && parm.glob_pref_time == 0) || + (IN6_IS_ADDR_ULA_ZERO(a) && parm.ula_pref_time == 0) || + (IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && parm.link_pref_time == 0)) + len -= IN6ADDRSZ; + + if (len != 0) + { + put_opt6_char(ICMP6_OPT_RDNSS); + put_opt6_char((len/8) + 1); + put_opt6_short(0); + put_opt6_long(min_pref_time); + + for (a = (struct in6_addr *)opt_cfg->val, i = 0; i < opt_cfg->len; i += IN6ADDRSZ, a++) + if (IN6_IS_ADDR_UNSPECIFIED(a)) + { + if (parm.glob_pref_time != 0) + put_opt6(&parm.link_global, IN6ADDRSZ); + } + else if (IN6_IS_ADDR_ULA_ZERO(a)) + { + if (parm.ula_pref_time != 0) + put_opt6(&parm.ula, IN6ADDRSZ); + } + else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a)) + { + if (parm.link_pref_time != 0) + put_opt6(&parm.link_local, IN6ADDRSZ); + } + else + put_opt6(a, IN6ADDRSZ); + } + } + + if (opt_cfg->opt == OPTION6_DOMAIN_SEARCH && opt_cfg->len != 0) + { + int len = ((opt_cfg->len+7)/8); + + put_opt6_char(ICMP6_OPT_DNSSL); + put_opt6_char(len + 1); + put_opt6_short(0); + put_opt6_long(min_pref_time); + put_opt6(opt_cfg->val, opt_cfg->len); + + /* pad */ + for (i = opt_cfg->len; i < len * 8; i++) + put_opt6_char(0); + } + } + + if (daemon->port == NAMESERVER_PORT && !done_dns && parm.link_pref_time != 0) + { + /* default == us, as long as we are supplying DNS service. */ + put_opt6_char(ICMP6_OPT_RDNSS); + put_opt6_char(3); + put_opt6_short(0); + put_opt6_long(min_pref_time); + put_opt6(&parm.link_local, IN6ADDRSZ); + } + + /* set managed bits unless we're providing only RA on this link */ + if (parm.managed) + ra->flags |= 0x80; /* M flag, managed, */ + if (parm.other) + ra->flags |= 0x40; /* O flag, other */ + + /* decide where we're sending */ + memset(&addr, 0, sizeof(addr)); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.sin6_len = sizeof(struct sockaddr_in6); +#endif + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(IPPROTO_ICMPV6); + if (dest) + { + addr.sin6_addr = *dest; + if (IN6_IS_ADDR_LINKLOCAL(dest) || + IN6_IS_ADDR_MC_LINKLOCAL(dest)) + addr.sin6_scope_id = iface; + } + else + { + inet_pton(AF_INET6, ALL_NODES, &addr.sin6_addr); + setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &send_iface, sizeof(send_iface)); + } + + while (retry_send(sendto(daemon->icmp6fd, daemon->outpacket.iov_base, + save_counter(0), 0, (struct sockaddr *)&addr, + sizeof(addr)))); + +} + +static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *dest) +{ + /* Send an RA on the same interface that the RA content is based + on. */ + send_ra_alias(now, iface, iface_name, dest, iface); +} + +static int add_prefixes(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, + unsigned int preferred, unsigned int valid, void *vparam) +{ + struct ra_param *param = vparam; + + (void)scope; /* warning */ + + if (if_index == param->ind) + { + if (IN6_IS_ADDR_LINKLOCAL(local)) + { + /* Can there be more than one LL address? + Select the one with the longest preferred time + if there is. */ + if (preferred > param->link_pref_time) + { + param->link_pref_time = preferred; + param->link_local = *local; + } + } + else if (!IN6_IS_ADDR_LOOPBACK(local) && + !IN6_IS_ADDR_MULTICAST(local)) + { + int real_prefix = 0; + int do_slaac = 0; + int deprecate = 0; + int constructed = 0; + int adv_router = 0; + int off_link = 0; + unsigned int time = 0xffffffff; + struct dhcp_context *context; + + for (context = daemon->dhcp6; context; context = context->next) + if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && + prefix <= context->prefix && + is_same_net6(local, &context->start6, context->prefix) && + is_same_net6(local, &context->end6, context->prefix)) + { + context->saved_valid = valid; + + if (context->flags & CONTEXT_RA) + { + do_slaac = 1; + if (context->flags & CONTEXT_DHCP) + { + param->other = 1; + if (!(context->flags & CONTEXT_RA_STATELESS)) + param->managed = 1; + } + } + else + { + /* don't do RA for non-ra-only unless --enable-ra is set */ + if (!option_bool(OPT_RA)) + continue; + param->managed = 1; + param->other = 1; + } + + /* Configured to advertise router address, not prefix. See RFC 3775 7.2 + In this case we do all addresses associated with a context, + hence the real_prefix setting here. */ + if (context->flags & CONTEXT_RA_ROUTER) + { + adv_router = 1; + param->adv_router = 1; + real_prefix = context->prefix; + } + + /* find floor time, don't reduce below 3 * RA interval. */ + if (time > context->lease_time) + { + time = context->lease_time; + if (time < ((unsigned int)(3 * param->adv_interval))) + time = 3 * param->adv_interval; + } + + if (context->flags & CONTEXT_DEPRECATE) + deprecate = 1; + + if (context->flags & CONTEXT_CONSTRUCTED) + constructed = 1; + + + /* collect dhcp-range tags */ + if (context->netid.next == &context->netid && context->netid.net) + { + context->netid.next = param->tags; + param->tags = &context->netid; + } + + /* subsequent prefixes on the same interface + and subsequent instances of this prefix don't need timers. + Be careful not to find the same prefix twice with different + addresses unless we're advertising the actual addresses. */ + if (!(context->flags & CONTEXT_RA_DONE)) + { + if (!param->first) + context->ra_time = 0; + context->flags |= CONTEXT_RA_DONE; + real_prefix = context->prefix; + off_link = (context->flags & CONTEXT_RA_OFF_LINK); + } + + param->first = 0; + param->found_context = 1; + } + + /* configured time is ceiling */ + if (!constructed || valid > time) + valid = time; + + if (flags & IFACE_DEPRECATED) + preferred = 0; + + if (deprecate) + time = 0; + + /* configured time is ceiling */ + if (!constructed || preferred > time) + preferred = time; + + if (IN6_IS_ADDR_ULA(local)) + { + if (preferred > param->ula_pref_time) + { + param->ula_pref_time = preferred; + param->ula = *local; + } + } + else + { + if (preferred > param->glob_pref_time) + { + param->glob_pref_time = preferred; + param->link_global = *local; + } + } + + if (real_prefix != 0) + { + struct prefix_opt *opt; + + if ((opt = expand(sizeof(struct prefix_opt)))) + { + /* zero net part of address */ + if (!adv_router) + setaddr6part(local, addr6part(local) & ~((real_prefix == 64) ? (u64)-1LL : (1LLU << (128 - real_prefix)) - 1LLU)); + + opt->type = ICMP6_OPT_PREFIX; + opt->len = 4; + opt->prefix_len = real_prefix; + /* autonomous only if we're not doing dhcp, set + "on-link" unless "off-link" was specified */ + opt->flags = (off_link ? 0 : 0x80); + if (do_slaac) + opt->flags |= 0x40; + if (adv_router) + opt->flags |= 0x20; + opt->valid_lifetime = htonl(valid); + opt->preferred_lifetime = htonl(preferred); + opt->reserved = 0; + opt->prefix = *local; + + inet_ntop(AF_INET6, local, daemon->addrbuff, ADDRSTRLEN); + if (!option_bool(OPT_QUIET_RA)) + my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff); + } + } + } + } + return 1; +} + +static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm) +{ + (void)type; + + if (index == *((int *)parm)) + { + /* size is in units of 8 octets and includes type and length (2 bytes) + add 7 to round up */ + int len = (maclen + 9) >> 3; + unsigned char *p = expand(len << 3); + memset(p, 0, len << 3); + *p++ = ICMP6_OPT_SOURCE_MAC; + *p++ = len; + memcpy(p, mac, maclen); + + return 0; + } + + return 1; +} + +time_t periodic_ra(time_t now) +{ + struct search_param param; + struct dhcp_context *context; + time_t next_event; + struct alias_param aparam; + + param.now = now; + param.iface = 0; + + while (1) + { + /* find overdue events, and time of first future event */ + for (next_event = 0, context = daemon->dhcp6; context; context = context->next) + if (context->ra_time != 0) + { + if (difftime(context->ra_time, now) <= 0.0) + break; /* overdue */ + + if (next_event == 0 || difftime(next_event, context->ra_time) > 0.0) + next_event = context->ra_time; + } + + /* none overdue */ + if (!context) + break; + + if ((context->flags & CONTEXT_OLD) && + context->if_index != 0 && + indextoname(daemon->icmp6fd, context->if_index, param.name)) + { + /* A context for an old address. We'll not find the interface by + looking for addresses, but we know it anyway, since the context is + constructed */ + param.iface = context->if_index; + new_timeout(context, param.name, now); + } + else if (iface_enumerate(AF_INET6, ¶m, iface_search)) + /* There's a context overdue, but we can't find an interface + associated with it, because it's for a subnet we dont + have an interface on. Probably we're doing DHCP on + a remote subnet via a relay. Zero the timer, since we won't + ever be able to send ra's and satistfy it. */ + context->ra_time = 0; + + if (param.iface != 0 && + iface_check(AF_LOCAL, NULL, param.name, NULL)) + { + struct iname *tmp; + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, param.name)) + break; + if (!tmp) + { + send_ra(now, param.iface, param.name, NULL); + + /* Also send on all interfaces that are aliases of this + one. */ + for (aparam.bridge = daemon->bridges; + aparam.bridge; + aparam.bridge = aparam.bridge->next) + if ((int)if_nametoindex(aparam.bridge->iface) == param.iface) + { + /* Count the number of alias interfaces for this + 'bridge', by calling iface_enumerate with + send_ra_to_aliases and NULL alias_ifs. */ + aparam.iface = param.iface; + aparam.alias_ifs = NULL; + aparam.num_alias_ifs = 0; + iface_enumerate(AF_LOCAL, &aparam, send_ra_to_aliases); + my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s => %d alias(es)", + param.name, daemon->addrbuff, aparam.num_alias_ifs); + + /* Allocate memory to store the alias interface + indices. */ + aparam.alias_ifs = (int *)whine_malloc(aparam.num_alias_ifs * + sizeof(int)); + if (aparam.alias_ifs) + { + /* Use iface_enumerate again to get the alias + interface indices, then send on each of + those. */ + aparam.max_alias_ifs = aparam.num_alias_ifs; + aparam.num_alias_ifs = 0; + iface_enumerate(AF_LOCAL, &aparam, send_ra_to_aliases); + for (; aparam.num_alias_ifs; aparam.num_alias_ifs--) + { + my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s => i/f %d", + param.name, daemon->addrbuff, + aparam.alias_ifs[aparam.num_alias_ifs - 1]); + send_ra_alias(now, + param.iface, + param.name, + NULL, + aparam.alias_ifs[aparam.num_alias_ifs - 1]); + } + free(aparam.alias_ifs); + } + + /* The source interface can only appear in at most + one --bridge-interface. */ + break; + } + } + } + } + return next_event; +} + +static int send_ra_to_aliases(int index, unsigned int type, char *mac, size_t maclen, void *parm) +{ + struct alias_param *aparam = (struct alias_param *)parm; + char ifrn_name[IFNAMSIZ]; + struct dhcp_bridge *alias; + + (void)type; + (void)mac; + (void)maclen; + + if (if_indextoname(index, ifrn_name)) + for (alias = aparam->bridge->alias; alias; alias = alias->next) + if (wildcard_matchn(alias->iface, ifrn_name, IFNAMSIZ)) + { + if (aparam->alias_ifs && (aparam->num_alias_ifs < aparam->max_alias_ifs)) + aparam->alias_ifs[aparam->num_alias_ifs] = index; + aparam->num_alias_ifs++; + } + + return 1; +} + +static int iface_search(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, + int preferred, int valid, void *vparam) +{ + struct search_param *param = vparam; + struct dhcp_context *context; + + (void)scope; + (void)preferred; + (void)valid; + + for (context = daemon->dhcp6; context; context = context->next) + if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && + prefix <= context->prefix && + is_same_net6(local, &context->start6, context->prefix) && + is_same_net6(local, &context->end6, context->prefix) && + context->ra_time != 0 && + difftime(context->ra_time, param->now) <= 0.0) + { + /* found an interface that's overdue for RA determine new + timeout value and arrange for RA to be sent unless interface is + still doing DAD.*/ + + if (!(flags & IFACE_TENTATIVE)) + param->iface = if_index; + + /* should never fail */ + if (!indextoname(daemon->icmp6fd, if_index, param->name)) + { + param->iface = 0; + return 0; + } + + new_timeout(context, param->name, param->now); + + /* zero timers for other contexts on the same subnet, so they don't timeout + independently */ + for (context = context->next; context; context = context->next) + if (prefix <= context->prefix && + is_same_net6(local, &context->start6, context->prefix) && + is_same_net6(local, &context->end6, context->prefix)) + context->ra_time = 0; + + return 0; /* found, abort */ + } + + return 1; /* keep searching */ +} + +static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now) +{ + if (difftime(now, context->ra_short_period_start) < 60.0) + /* range 5 - 20 */ + context->ra_time = now + 5 + (rand16()/4400); + else + { + /* range 3/4 - 1 times MaxRtrAdvInterval */ + unsigned int adv_interval = calc_interval(find_iface_param(iface_name)); + context->ra_time = now + (3 * adv_interval)/4 + ((adv_interval * (unsigned int)rand16()) >> 18); + } +} + +static struct ra_interface *find_iface_param(char *iface) +{ + struct ra_interface *ra; + + for (ra = daemon->ra_interfaces; ra; ra = ra->next) + if (wildcard_match(ra->name, iface)) + return ra; + + return NULL; +} + +static unsigned int calc_interval(struct ra_interface *ra) +{ + int interval = 600; + + if (ra && ra->interval != 0) + { + interval = ra->interval; + if (interval > 1800) + interval = 1800; + else if (interval < 4) + interval = 4; + } + + return (unsigned int)interval; +} + +static unsigned int calc_lifetime(struct ra_interface *ra) +{ + int lifetime, interval = (int)calc_interval(ra); + + if (!ra || ra->lifetime == -1) /* not specified */ + lifetime = 3 * interval; + else + { + lifetime = ra->lifetime; + if (lifetime < interval && lifetime != 0) + lifetime = interval; + else if (lifetime > 9000) + lifetime = 9000; + } + + return (unsigned int)lifetime; +} + +static unsigned int calc_prio(struct ra_interface *ra) +{ + if (ra) + return ra->prio; + + return 0; +} + +#endif diff --git a/src/rfc1035.c b/src/rfc1035.c index 889c1f0..56647b0 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.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 @@ -16,22 +16,11 @@ #include "dnsmasq.h" -static int add_resource_record(struct dns_header *header, char *limit, int *truncp, - unsigned int nameoffset, unsigned char **pp, - unsigned long ttl, unsigned int *offset, unsigned short type, - unsigned short class, char *format, ...); - -#define CHECK_LEN(header, pp, plen, len) \ - ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) - -#define ADD_RDLEN(header, pp, plen, len) \ - (!CHECK_LEN(header, pp, plen, len) ? 0 : (long)((pp) += (len)), 1) - -static int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, - char *name, int isExtract, int extrabytes) +int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, + char *name, int isExtract, int extrabytes) { unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL; - unsigned int j, l, hops = 0; + unsigned int j, l, namelen = 0, hops = 0; int retvalue = 1; if (isExtract) @@ -88,49 +77,10 @@ static int extract_name(struct dns_header *header, size_t plen, unsigned char ** p = l + (unsigned char *)header; } - else if (label_type == 0x80) - return 0; /* reserved */ - else if (label_type == 0x40) - { /* ELT */ - unsigned int count, digs; - - if ((l & 0x3f) != 1) - return 0; /* we only understand bitstrings */ - - if (!isExtract) - return 0; /* Cannot compare bitsrings */ - - count = *p++; - if (count == 0) - count = 256; - digs = ((count-1)>>2)+1; - - /* output is \[x<hex>/siz]. which is digs+9 chars */ - if (cp - (unsigned char *)name + digs + 9 >= MAXDNAME) - return 0; - if (!CHECK_LEN(header, p, plen, (count-1)>>3)) - return 0; - - *cp++ = '\\'; - *cp++ = '['; - *cp++ = 'x'; - for (j=0; j<digs; j++) - { - unsigned int dig; - if (j%2 == 0) - dig = *p >> 4; - else - dig = *p++ & 0x0f; - - *cp++ = dig < 10 ? dig + '0' : dig + 'A' - 10; - } - cp += sprintf((char *)cp, "/%d]", count); - /* do this here to overwrite the zero char from sprintf */ - *cp++ = '.'; - } - else + else if (label_type == 0x00) { /* label_type = 0 -> label. */ - if (cp - (unsigned char *)name + l + 1 >= MAXDNAME) + namelen += l + 1; /* include period */ + if (namelen >= MAXDNAME) return 0; if (!CHECK_LEN(header, p, plen, l)) return 0; @@ -139,8 +89,21 @@ static int extract_name(struct dns_header *header, size_t plen, unsigned char ** if (isExtract) { unsigned char c = *p; - if (isascii(c) && !iscntrl(c) && c != '.') - *cp++ = *p; +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + if (c == 0 || c == '.' || c == NAME_ESCAPE) + { + *cp++ = NAME_ESCAPE; + *cp++ = c+1; + } + else + *cp++ = c; + } + else +#endif + if (c != 0 && c != '.') + *cp++ = c; else return 0; } @@ -155,25 +118,32 @@ static int extract_name(struct dns_header *header, size_t plen, unsigned char ** cp++; if (c1 >= 'A' && c1 <= 'Z') c1 += 'a' - 'A'; +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && c1 == NAME_ESCAPE) + c1 = (*cp++)-1; +#endif + if (c2 >= 'A' && c2 <= 'Z') c2 += 'a' - 'A'; - + if (c1 != c2) retvalue = 2; } } - + if (isExtract) *cp++ = '.'; else if (*cp != 0 && *cp++ != '.') retvalue = 2; } + else + return 0; /* label types 0x40 and 0x80 not supported */ } } /* Max size of input string (for IPv6) is 75 chars.) */ #define MAXARPANAME 75 -static int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) +int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) { int j; char name[MAXARPANAME+1], *cp1; @@ -278,7 +248,7 @@ static int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) return 0; } -static unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes) +unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes) { while(1) { @@ -333,7 +303,7 @@ static unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, return ansp; } -static unsigned char *skip_questions(struct dns_header *header, size_t plen) +unsigned char *skip_questions(struct dns_header *header, size_t plen) { int q; unsigned char *ansp = (unsigned char *)(header+1); @@ -348,7 +318,7 @@ static unsigned char *skip_questions(struct dns_header *header, size_t plen) return ansp; } -static unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen) +unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen) { int i, rdlen; @@ -371,6 +341,7 @@ static unsigned char *skip_section(unsigned char *ansp, int count, struct dns_he than CRC the raw bytes, since replies might be compressed differently. We ignore case in the names for the same reason. Return all-ones if there is not question section. */ +#ifndef HAVE_DNSSEC unsigned int questions_crc(struct dns_header *header, size_t plen, char *name) { int q; @@ -411,7 +382,7 @@ unsigned int questions_crc(struct dns_header *header, size_t plen, char *name) return crc; } - +#endif size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen) { @@ -504,7 +475,7 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t else if (is_sign && i == arcount - 1 && class == C_ANY && - (type == T_SIG || type == T_TSIG)) + type == T_TSIG) *is_sign = 1; } @@ -517,94 +488,117 @@ struct macparm { size_t plen; union mysockaddr *l3; }; - -static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) -{ - struct macparm *parm = parmv; - int match = 0; - unsigned short rdlen; - struct dns_header *header = parm->header; - unsigned char *lenp, *datap, *p; - - if (family == parm->l3->sa.sa_family) - { - if (family == AF_INET && memcmp (&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0) - match = 1; -#ifdef HAVE_IPV6 - else - if (family == AF_INET6 && memcmp (&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0) - match = 1; -#endif - } - if (!match) - return 1; /* continue */ +static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit, + int optno, unsigned char *opt, size_t optlen, int set_do) +{ + unsigned char *lenp, *datap, *p; + int rdlen, is_sign; - if (ntohs(header->arcount) == 0) + if (!(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign))) { + if (is_sign) + return plen; + /* We are adding the pseudoheader */ - if (!(p = skip_questions(header, parm->plen)) || + if (!(p = skip_questions(header, plen)) || !(p = skip_section(p, - ntohs(header->ancount) + ntohs(header->nscount), - header, parm->plen))) - return 0; + ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount), + header, plen))) + return plen; *p++ = 0; /* empty name */ PUTSHORT(T_OPT, p); - PUTSHORT(PACKETSZ, p); /* max packet length - is 512 suitable default for non-EDNS0 resolvers? */ - PUTLONG(0, p); /* extended RCODE */ + PUTSHORT(SAFE_PKTSZ, p); /* max packet length, this will be overwritten */ + PUTSHORT(0, p); /* extended RCODE and version */ + PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */ lenp = p; PUTSHORT(0, p); /* RDLEN */ rdlen = 0; - if (((ssize_t)maclen) > (parm->limit - (p + 4))) - return 0; /* Too big */ - header->arcount = htons(1); + if (((ssize_t)optlen) > (limit - (p + 4))) + return plen; /* Too big */ + header->arcount = htons(ntohs(header->arcount) + 1); datap = p; } else { - int i, is_sign; - unsigned short code, len; + int i; + unsigned short code, len, flags; + /* Must be at the end, if exists */ if (ntohs(header->arcount) != 1 || - !(p = find_pseudoheader(header, parm->plen, NULL, NULL, &is_sign)) || is_sign || - (!(p = skip_name(p, header, parm->plen, 10)))) - return 0; - - p += 8; /* skip UDP length and RCODE */ + (!(p = skip_name(p, header, plen, 10)))) + return plen; + p += 6; /* skip UDP length and RCODE */ + GETSHORT(flags, p); + if (set_do) + { + p -=2; + PUTSHORT(flags | 0x8000, p); + } + lenp = p; GETSHORT(rdlen, p); - if (!CHECK_LEN(header, p, parm->plen, rdlen)) - return 0; /* bad packet */ + if (!CHECK_LEN(header, p, plen, rdlen)) + return plen; /* bad packet */ datap = p; + /* no option to add */ + if (optno == 0) + return plen; + /* check if option already there */ for (i = 0; i + 4 < rdlen; i += len + 4) { GETSHORT(code, p); GETSHORT(len, p); - if (code == EDNS0_OPTION_MAC) - return 0; + if (code == optno) + return plen; p += len; } - if (((ssize_t)maclen) > (parm->limit - (p + 4))) - return 0; /* Too big */ + if (((ssize_t)optlen) > (limit - (p + 4))) + return plen; /* Too big */ } - PUTSHORT(EDNS0_OPTION_MAC, p); - PUTSHORT(maclen, p); - memcpy(p, mac, maclen); - p += maclen; + if (optno != 0) + { + PUTSHORT(optno, p); + PUTSHORT(optlen, p); + memcpy(p, opt, optlen); + p += optlen; + } PUTSHORT(p - datap, lenp); - parm->plen = p - (unsigned char *)header; + return p - (unsigned char *)header; + +} + +static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) +{ + struct macparm *parm = parmv; + int match = 0; + + if (family == parm->l3->sa.sa_family) + { + if (family == AF_INET && memcmp(&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0) + match = 1; +#ifdef HAVE_IPV6 + else + if (family == AF_INET6 && memcmp(&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0) + match = 1; +#endif + } + + if (!match) + return 1; /* continue */ + + parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen, 0); return 0; /* done */ } - size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3) { struct macparm parm; @@ -625,9 +619,111 @@ size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysock return parm.plen; } - +struct subnet_opt { + u16 family; + u8 source_netmask, scope_netmask; +#ifdef HAVE_IPV6 + u8 addr[IN6ADDRSZ]; +#else + u8 addr[INADDRSZ]; +#endif +}; + +static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) +{ + /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ + + int len; + void *addrp; + +#ifdef HAVE_IPV6 + if (source->sa.sa_family == AF_INET6) + { + opt->family = htons(2); + opt->source_netmask = daemon->addr6_netmask; + addrp = &source->in6.sin6_addr; + } + else +#endif + { + opt->family = htons(1); + opt->source_netmask = daemon->addr4_netmask; + addrp = &source->in.sin_addr; + } + + opt->scope_netmask = 0; + len = 0; + + if (opt->source_netmask != 0) + { + len = ((opt->source_netmask - 1) >> 3) + 1; + memcpy(opt->addr, addrp, len); + if (opt->source_netmask & 7) + opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7)); + } + + return len + 4; +} + +size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source) +{ + /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ + + int len; + struct subnet_opt opt; + + len = calc_subnet_opt(&opt, source); + return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0); +} + +#ifdef HAVE_DNSSEC +size_t add_do_bit(struct dns_header *header, size_t plen, char *limit) +{ + return add_pseudoheader(header, plen, (unsigned char *)limit, 0, NULL, 0, 1); +} +#endif + +int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer) +{ + /* Section 9.2, Check that subnet option in reply matches. */ + + + int len, calc_len; + struct subnet_opt opt; + unsigned char *p; + int code, i, rdlen; + + calc_len = calc_subnet_opt(&opt, peer); + + if (!(p = skip_name(pseudoheader, header, plen, 10))) + return 1; + + p += 8; /* skip UDP length and RCODE */ + + GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, plen, rdlen)) + return 1; /* bad packet */ + + /* check if option there */ + for (i = 0; i + 4 < rdlen; i += len + 4) + { + GETSHORT(code, p); + GETSHORT(len, p); + if (code == EDNS0_OPTION_CLIENT_SUBNET) + { + /* make sure this doesn't mismatch. */ + opt.scope_netmask = p[3]; + if (len != calc_len || memcmp(p, &opt, len) != 0) + return 0; + } + p += len; + } + + return 1; +} + /* is addr in the non-globally-routed IP space? */ -static int private_net(struct in_addr addr, int ban_localhost) +int private_net(struct in_addr addr, int ban_localhost) { in_addr_t ip_addr = ntohl(addr.s_addr); @@ -639,10 +735,9 @@ static int private_net(struct in_addr addr, int ban_localhost) ((ip_addr & 0xFFFF0000) == 0xA9FE0000) /* 169.254.0.0/16 (zeroconf) */ ; } -static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, char *name) +static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, char *name, int *doctored) { int i, qtype, qclass, rdlen; - unsigned long ttl; for (i = count; i != 0; i--) { @@ -656,7 +751,7 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * GETSHORT(qtype, p); GETSHORT(qclass, p); - GETLONG(ttl, p); + p += 4; /* ttl */ GETSHORT(rdlen, p); if (qclass == C_IN && qtype == T_A) @@ -685,6 +780,7 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * addr.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr); /* Since we munged the data, the server it came from is no longer authoritative */ header->hb3 &= ~HB3_AA; + *doctored = 1; memcpy(p, &addr, INADDRSZ); break; } @@ -700,15 +796,17 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * unsigned char *p2 = p1; /* make counted string zero-term and sanitise */ for (i = 0; i < len; i++) - if (isprint(*(p2+1))) - { - *p2 = *(p2+1); - p2++; - } + { + if (!isprint((int)*(p2+1))) + break; + + *p2 = *(p2+1); + p2++; + } *p2 = 0; my_syslog(LOG_INFO, "reply %s is %s", name, p1); /* restore */ - memmove(p1 + 1, p1, len); + memmove(p1 + 1, p1, i); *p1 = len; p1 += len+1; } @@ -721,7 +819,7 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * return p; } -static int find_soa(struct dns_header *header, size_t qlen, char *name) +static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doctored) { unsigned char *p; int qtype, qclass, rdlen; @@ -730,7 +828,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) /* first move to NS section and find TTL from any SOA section */ if (!(p = skip_questions(header, qlen)) || - !(p = do_doctor(p, ntohs(header->ancount), header, qlen, name))) + !(p = do_doctor(p, ntohs(header->ancount), header, qlen, name, doctored))) return 0; /* bad packet */ for (i = ntohs(header->nscount); i != 0; i--) @@ -765,8 +863,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) return 0; /* bad packet */ } - /* rewrite addresses in additioal section too */ - if (!do_doctor(p, ntohs(header->arcount), header, qlen, NULL)) + /* rewrite addresses in additional section too */ + if (!do_doctor(p, ntohs(header->arcount), header, qlen, NULL, doctored)) return 0; if (!found_soa) @@ -780,20 +878,29 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) expired and cleaned out that way. Return 1 if we reject an address because it look like part of dns-rebinding attack. */ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, - int is_sign, int check_rebind, int checking_disabled) + char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure, int *doctored) { unsigned char *p, *p1, *endrr, *namep; int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; unsigned long ttl = 0; struct all_addr addr; - +#ifdef HAVE_IPSET + char **ipsets_cur; +#else + (void)ipsets; /* unused */ +#endif + cache_start_insert(); /* find_soa is needed for dns_doctor and logging side-effects, so don't call it lazily if there are any. */ - if (daemon->doctors || option_bool(OPT_LOG)) + if (daemon->doctors || option_bool(OPT_LOG) || option_bool(OPT_DNSSEC_VALID)) { searched_soa = 1; - ttl = find_soa(header, qlen, name); + ttl = find_soa(header, qlen, name, doctored); +#ifdef HAVE_DNSSEC + if (*doctored && secure) + return 0; +#endif } /* go through the questions. */ @@ -801,11 +908,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t for (i = ntohs(header->qdcount); i != 0; i--) { - int found = 0, cname_count = 5; + int found = 0, cname_count = CNAME_CHAIN; struct crec *cpp = NULL; int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; + int secflag = secure ? F_DNSSECOK : 0; unsigned long cttl = ULONG_MAX, attl; - + namep = p; if (!extract_name(header, qlen, &p, name, 1, 4)) return 0; /* bad packet */ @@ -861,12 +969,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (aqtype == T_CNAME) { - if (!cname_count--) - return 0; /* looped CNAMES */ + if (!cname_count-- || secure) + return 0; /* looped CNAMES, or DNSSEC, which we can't cache. */ goto cname_loop; } - cache_insert(name, &addr, now, cttl, name_encoding | F_REVERSE); + cache_insert(name, &addr, now, cttl, name_encoding | secflag | F_REVERSE); found = 1; } @@ -881,10 +989,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (!searched_soa) { searched_soa = 1; - ttl = find_soa(header, qlen, NULL); + ttl = find_soa(header, qlen, NULL, doctored); } if (ttl) - cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags); + cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | secflag); } } else @@ -908,78 +1016,106 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t else continue; - if (!(flags & F_NXDOMAIN)) + cname_loop1: + if (!(p1 = skip_questions(header, qlen))) + return 0; + + for (j = ntohs(header->ancount); j != 0; j--) { - cname_loop1: - if (!(p1 = skip_questions(header, qlen))) - return 0; + if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) + return 0; /* bad packet */ - for (j = ntohs(header->ancount); j != 0; j--) + GETSHORT(aqtype, p1); + GETSHORT(aqclass, p1); + GETLONG(attl, p1); + if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) { - if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) - return 0; /* bad packet */ - - GETSHORT(aqtype, p1); - GETSHORT(aqclass, p1); - GETLONG(attl, p1); - if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) - { - (p1) -= 4; - PUTLONG(daemon->max_ttl, p1); - } - GETSHORT(ardlen, p1); - endrr = p1+ardlen; - - if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype)) + (p1) -= 4; + PUTLONG(daemon->max_ttl, p1); + } + GETSHORT(ardlen, p1); + endrr = p1+ardlen; + + if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype)) + { + if (aqtype == T_CNAME) { - if (aqtype == T_CNAME) + if (!cname_count--) + return 0; /* looped CNAMES */ + newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD | secflag); + if (newc) { - if (!cname_count--) - return 0; /* looped CNAMES */ - newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD); - if (newc && cpp) + newc->addr.cname.target.cache = NULL; + /* anything other than zero, to avoid being mistaken for CNAME to interface-name */ + newc->addr.cname.uid = 1; + if (cpp) { - cpp->addr.cname.cache = newc; + cpp->addr.cname.target.cache = newc; cpp->addr.cname.uid = newc->uid; } - - cpp = newc; - if (attl < cttl) - cttl = attl; - - if (!extract_name(header, qlen, &p1, name, 1, 0)) - return 0; - goto cname_loop1; } - else + + cpp = newc; + if (attl < cttl) + cttl = attl; + + if (!extract_name(header, qlen, &p1, name, 1, 0)) + return 0; + goto cname_loop1; + } + else if (!(flags & F_NXDOMAIN)) + { + found = 1; + + /* copy address into aligned storage */ + if (!CHECK_LEN(header, p1, qlen, addrlen)) + return 0; /* bad packet */ + memcpy(&addr, p1, addrlen); + + /* check for returned address in private space */ + if (check_rebind) { - found = 1; - - /* copy address into aligned storage */ - if (!CHECK_LEN(header, p1, qlen, addrlen)) - return 0; /* bad packet */ - memcpy(&addr, p1, addrlen); - - /* check for returned address in private space */ - if (check_rebind && - (flags & F_IPV4) && + if ((flags & F_IPV4) && private_net(addr.addr.addr4, !option_bool(OPT_LOCAL_REBIND))) return 1; - newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD); - if (newc && cpp) +#ifdef HAVE_IPV6 + if ((flags & F_IPV6) && + IN6_IS_ADDR_V4MAPPED(&addr.addr.addr6)) { - cpp->addr.cname.cache = newc; - cpp->addr.cname.uid = newc->uid; + struct in_addr v4; + v4.s_addr = ((const uint32_t *) (&addr.addr.addr6))[3]; + if (private_net(v4, !option_bool(OPT_LOCAL_REBIND))) + return 1; + } +#endif + } + +#ifdef HAVE_IPSET + if (ipsets && (flags & (F_IPV4 | F_IPV6))) + { + ipsets_cur = ipsets; + while (*ipsets_cur) + { + log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, name, &addr, *ipsets_cur); + add_to_ipset(*ipsets_cur++, &addr, flags, 0); } - cpp = NULL; } +#endif + + newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD | secflag); + if (newc && cpp) + { + cpp->addr.cname.target.cache = newc; + cpp->addr.cname.uid = newc->uid; + } + cpp = NULL; } - - p1 = endrr; - if (!CHECK_LEN(header, p1, qlen, 0)) - return 0; /* bad packet */ } + + p1 = endrr; + if (!CHECK_LEN(header, p1, qlen, 0)) + return 0; /* bad packet */ } if (!found && !option_bool(OPT_NO_NEG)) @@ -987,16 +1123,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (!searched_soa) { searched_soa = 1; - ttl = find_soa(header, qlen, NULL); + ttl = find_soa(header, qlen, NULL, doctored); } /* If there's no SOA to get the TTL from, but there is a CNAME pointing at this, inherit its TTL */ if (ttl || cpp) { - newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags); + newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | secflag); if (newc && cpp) { - cpp->addr.cname.cache = newc; + cpp->addr.cname.target.cache = newc; cpp->addr.cname.uid = newc->uid; } } @@ -1004,10 +1140,14 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t } } - /* Don't put stuff from a truncated packet into the cache, - also don't cache replies where DNSSEC validation was turned off, either - the upstream server told us so, or the original query specified it. */ - if (!(header->hb3 & HB3_TC) && !(header->hb4 & HB4_CD) && !checking_disabled) + /* Don't put stuff from a truncated packet into the cache. + Don't cache replies from non-recursive nameservers, since we may get a + reply containing a CNAME but not its target, even though the target + does exist. */ + if (!(header->hb3 & HB3_TC) && + !(header->hb4 & HB4_CD) && + (header->hb4 & HB4_RA) && + !no_cache_dnssec) cache_end_insert(); return 0; @@ -1015,7 +1155,6 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t /* If the packet holds exactly one query return F_IPV4 or F_IPV6 and leave the name from the query in name */ - unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep) { unsigned char *p = (unsigned char *)(header+1); @@ -1044,8 +1183,6 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, return F_IPV6; if (qtype == T_ANY) return F_IPV4 | F_IPV6; - if (qtype == T_NS || qtype == T_SOA) - return F_QUERY | F_NSRR; } return F_QUERY; @@ -1055,7 +1192,10 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, size_t setup_reply(struct dns_header *header, size_t qlen, struct all_addr *addrp, unsigned int flags, unsigned long ttl) { - unsigned char *p = skip_questions(header, qlen); + unsigned char *p; + + if (!(p = skip_questions(header, qlen))) + return 0; /* clear authoritative and truncated flags, set QR flag */ header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR; @@ -1071,7 +1211,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen, SET_RCODE(header, NOERROR); /* empty domain */ else if (flags == F_NXDOMAIN) SET_RCODE(header, NXDOMAIN); - else if (p && flags == F_IPV4) + else if (flags == F_IPV4) { /* we know the address */ SET_RCODE(header, NOERROR); header->ancount = htons(1); @@ -1079,7 +1219,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen, add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_A, C_IN, "4", addrp); } #ifdef HAVE_IPV6 - else if (p && flags == F_IPV6) + else if (flags == F_IPV6) { SET_RCODE(header, NOERROR); header->ancount = htons(1); @@ -1101,12 +1241,22 @@ int check_for_local_domain(char *name, time_t now) struct txt_record *txt; struct interface_name *intr; struct ptr_record *ptr; - - if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6)) && - (crecp->flags & (F_HOSTS | F_DHCP))) + struct naptr *naptr; + + /* Note: the call to cache_find_by_name is intended to find any record which matches + ie A, AAAA, CNAME, DS. Because RRSIG records are marked by setting both F_DS and F_DNSKEY, + cache_find_by name ordinarily only returns records with an exact match on those bits (ie + for the call below, only DS records). The F_NSIGMATCH bit changes this behaviour */ + + if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_DS | F_NO_RR | F_NSIGMATCH)) && + (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) return 1; - for (mx = daemon->mxnames; mx; mx = mx->next) + for (naptr = daemon->naptr; naptr; naptr = naptr->next) + if (hostname_isequal(name, naptr->name)) + return 1; + + for (mx = daemon->mxnames; mx; mx = mx->next) if (hostname_isequal(name, mx->name)) return 1; @@ -1161,7 +1311,7 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, /* Found a bogus address. Insert that info here, since there no SOA record to get the ttl from in the normal processing */ cache_start_insert(); - cache_insert(name, NULL, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN | F_CONFIG); + cache_insert(name, NULL, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN); cache_end_insert(); return 1; @@ -1175,8 +1325,45 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, return 0; } -static int add_resource_record(struct dns_header *header, char *limit, int *truncp, unsigned int nameoffset, unsigned char **pp, - unsigned long ttl, unsigned int *offset, unsigned short type, unsigned short class, char *format, ...) +int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr) +{ + unsigned char *p; + int i, qtype, qclass, rdlen; + struct bogus_addr *baddrp; + + /* skip over questions */ + if (!(p = skip_questions(header, qlen))) + return 0; /* bad packet */ + + for (i = ntohs(header->ancount); i != 0; i--) + { + if (!(p = skip_name(p, header, qlen, 10))) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + p += 4; /* TTL */ + GETSHORT(rdlen, p); + + if (qclass == C_IN && qtype == T_A) + { + if (!CHECK_LEN(header, p, qlen, INADDRSZ)) + return 0; + + for (baddrp = baddr; baddrp; baddrp = baddrp->next) + if (memcmp(&baddrp->addr, p, INADDRSZ) == 0) + return 1; + } + + if (!ADD_RDLEN(header, p, qlen, rdlen)) + return 0; + } + + return 0; +} + +int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, + unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...) { va_list ap; unsigned char *sav, *p = *pp; @@ -1187,8 +1374,26 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun if (truncp && *truncp) return 0; + + va_start(ap, format); /* make ap point to 1st unamed argument */ + + if (nameoffset > 0) + { + PUTSHORT(nameoffset | 0xc000, p); + } + else + { + char *name = va_arg(ap, char *); + if (name) + p = do_rfc1035_name(p, name); + if (nameoffset < 0) + { + PUTSHORT(-nameoffset | 0xc000, p); + } + else + *p++ = 0; + } - PUTSHORT(nameoffset | 0xc000, p); PUTSHORT(type, p); PUTSHORT(class, p); PUTLONG(ttl, p); /* TTL */ @@ -1196,8 +1401,6 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun sav = p; /* Save pointer to RDLength field */ PUTSHORT(0, p); /* Placeholder RDLength */ - va_start(ap, format); /* make ap point to 1st unamed argument */ - for (; *format; format++) switch (*format) { @@ -1215,6 +1418,11 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun p += INADDRSZ; break; + case 'b': + usval = va_arg(ap, int); + *p++ = usval; + break; + case 's': usval = va_arg(ap, int); PUTSHORT(usval, p); @@ -1236,7 +1444,8 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun case 't': usval = va_arg(ap, int); sval = va_arg(ap, char *); - memcpy(p, sval, usval); + if (usval != 0) + memcpy(p, sval, usval); p += usval; break; @@ -1286,45 +1495,49 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now) /* return zero if we can't answer from cache, or packet size if we can */ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, - struct in_addr local_addr, struct in_addr local_netmask, time_t now) + struct in_addr local_addr, struct in_addr local_netmask, + time_t now, int *ad_reqd, int *do_bit) { char *name = daemon->namebuff; unsigned char *p, *ansp, *pheader; - int qtype, qclass; + unsigned int qtype, qclass; struct all_addr addr; - unsigned int nameoffset; + int nameoffset; unsigned short flag; int q, ans, anscount = 0, addncount = 0; - int dryrun = 0, sec_reqd = 0; - int is_sign; + int dryrun = 0, sec_reqd = 0, have_pseudoheader = 0; struct crec *crecp; - int nxdomain = 0, auth = 1, trunc = 0; + int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1; struct mx_srv_record *rec; + size_t len; + /* Don't return AD set if checking disabled. */ + if (header->hb4 & HB4_CD) + sec_data = 0; + + /* RFC 6840 5.7 */ + *ad_reqd = header->hb4 & HB4_AD; + *do_bit = 0; + /* If there is an RFC2671 pseudoheader then it will be overwritten by partial replies, so we have to do a dry run to see if we can answer the query. We check to see if the do bit is set, if so we always forward rather than answering from the cache, which doesn't include - security information. */ + security information, unless we're in DNSSEC validation mode. */ - if (find_pseudoheader(header, qlen, NULL, &pheader, &is_sign)) + if (find_pseudoheader(header, qlen, NULL, &pheader, NULL)) { - unsigned short udpsz, ext_rcode, flags; - unsigned char *psave = pheader; + unsigned short flags; + + have_pseudoheader = 1; - GETSHORT(udpsz, pheader); - GETSHORT(ext_rcode, pheader); + pheader += 4; /* udp size, ext_rcode */ GETSHORT(flags, pheader); - sec_reqd = flags & 0x8000; /* do bit */ - - /* If our client is advertising a larger UDP packet size - than we allow, trim it so that we don't get an overlarge - response from upstream */ - - if (!is_sign && (udpsz > daemon->edns_pktsz)) - PUTSHORT(daemon->edns_pktsz, psave); + if ((sec_reqd = flags & 0x8000)) + *do_bit = 1;/* do bit */ + *ad_reqd = 1; dryrun = 1; } @@ -1354,6 +1567,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, GETSHORT(qtype, p); GETSHORT(qclass, p); + /* Don't filter RRSIGS from answers to ANY queries, even if do-bit + not set. */ + if (qtype == T_ANY) + *do_bit = 1; + ans = 0; /* have we answered this question */ if (qtype == T_TXT || qtype == T_ANY) @@ -1366,10 +1584,20 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ans = 1; if (!dryrun) { + unsigned long ttl = daemon->local_ttl; + int ok = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>"); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, - T_TXT, t->class, "t", t->len, t->txt)) + /* Dynamically generate stat record */ + if (t->stat != 0) + { + ttl = 0; + if (!cache_make_stat(t)) + ok = 0; + } + + if (ok && add_resource_record(header, limit, &trunc, nameoffset, &ansp, + ttl, NULL, + T_TXT, t->class, "t", t->len, t->txt)) anscount++; } @@ -1377,8 +1605,116 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DNSKEY || qtype == T_DS)) + { + int gotone = 0; + struct blockdata *keydata; + + /* Do we have RRSIG? Can't do DS or DNSKEY otherwise. */ + if (sec_reqd) + { + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS))) + if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype) + break; + } + + if (!sec_reqd || crecp) + { + if (qtype == T_DS) + { + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, name, now, F_DS))) + if (crecp->uid == qclass) + { + gotone = 1; + if (!dryrun) + { + if (crecp->flags & F_NEG) + { + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + log_query(F_UPSTREAM, name, NULL, "no DS"); + } + else if ((keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL))) + { + struct all_addr a; + a.addr.keytag = crecp->addr.ds.keytag; + log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DS keytag %u"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_DS, qclass, "sbbt", + crecp->addr.ds.keytag, crecp->addr.ds.algo, + crecp->addr.ds.digest, crecp->addr.ds.keylen, keydata)) + anscount++; + + } + } + } + } + else /* DNSKEY */ + { + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY))) + if (crecp->uid == qclass) + { + gotone = 1; + if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL))) + { + struct all_addr a; + a.addr.keytag = crecp->addr.key.keytag; + log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DNSKEY keytag %u"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_DNSKEY, qclass, "sbbt", + crecp->addr.key.flags, 3, crecp->addr.key.algo, crecp->addr.key.keylen, keydata)) + anscount++; + } + } + } + } + + /* Now do RRSIGs */ + if (gotone) + { + ans = 1; + auth = 0; + if (!dryrun && sec_reqd) + { + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS))) + if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype && + (keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL))) + { + add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_RRSIG, qclass, "t", crecp->addr.sig.keylen, keydata); + anscount++; + } + } + } + } +#endif + if (qclass == C_IN) { + struct txt_record *t; + + for (t = daemon->rr; t; t = t->next) + if ((t->class == qtype || qtype == T_ANY) && hostname_isequal(name, t->name)) + { + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "<RR>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + t->class, C_IN, "t", t->len, t->txt)) + anscount ++; + } + } + if (qtype == T_PTR || qtype == T_ANY) { /* see if it's w.z.y.z.in-addr.arpa format */ @@ -1393,19 +1729,42 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (is_arpa == F_IPV4) for (intr = daemon->int_names; intr; intr = intr->next) { - if (addr.addr.addr4.s_addr == get_ifaddr(intr->intr).s_addr) + struct addrlist *addrlist; + + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr.addr4.s_addr == addrlist->addr.addr.addr4.s_addr) + break; + + if (addrlist) + break; + else + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; + } +#ifdef HAVE_IPV6 + else if (is_arpa == F_IPV6) + for (intr = daemon->int_names; intr; intr = intr->next) + { + struct addrlist *addrlist; + + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr.addr6, &addrlist->addr.addr.addr6)) + break; + + if (addrlist) break; else while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) intr = intr->next; } +#endif if (intr) { ans = 1; if (!dryrun) { - log_query(F_IPV4 | F_REVERSE | F_CONFIG, intr->name, &addr, NULL); + log_query(is_arpa | F_REVERSE | F_CONFIG, intr->name, &addr, NULL); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, NULL, T_PTR, C_IN, "d", intr->name)) @@ -1428,38 +1787,90 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } else if ((crecp = cache_find_by_addr(NULL, &addr, now, is_arpa))) - do - { - /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ - if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) - continue; - - if (crecp->flags & F_NEG) - { - ans = 1; - auth = 0; - if (crecp->flags & F_NXDOMAIN) - nxdomain = 1; - if (!dryrun) - log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL); - } - else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd) - { - ans = 1; - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; - if (!dryrun) - { - log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr, - record_source(crecp->uid)); - - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), NULL, - T_PTR, C_IN, "d", cache_get_name(crecp))) + { + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd) + { + if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))) + crecp = NULL; +#ifdef HAVE_DNSSEC + else if (crecp->flags & F_DNSSECOK) + { + int gotsig = 0; + struct crec *rr_crec = NULL; + + while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY))) + { + if (rr_crec->addr.sig.type_covered == T_PTR && rr_crec->uid == C_IN) + { + char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL); + gotsig = 1; + + if (!dryrun && + add_resource_record(header, limit, &trunc, nameoffset, &ansp, + rr_crec->ttd - now, &nameoffset, + T_RRSIG, C_IN, "t", crecp->addr.sig.keylen, sigdata)) + anscount++; + } + } + + if (!gotsig) + crecp = NULL; + } +#endif + } + + if (crecp) + { + do + { + /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ + if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) + continue; + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + if (crecp->flags & F_NEG) + { + ans = 1; + auth = 0; + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + if (!dryrun) + log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL); + } + else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd || option_bool(OPT_DNSSEC_VALID)) + { + ans = 1; + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + if (!dryrun) + { + log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr, + record_source(crecp->uid)); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, + T_PTR, C_IN, "d", cache_get_name(crecp))) + anscount++; + } + } + } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))); + } + } + else if (is_rev_synth(is_arpa, &addr, name)) + { + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + T_PTR, C_IN, "d", name)) anscount++; - } - } - } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))); + } + } else if (is_arpa == F_IPV4 && option_bool(OPT_BOGUSPRIV) && private_net(addr.addr.addr4, 1)) @@ -1476,7 +1887,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0) { unsigned short type = T_A; - + struct interface_name *intr; + if (flag == F_IPV6) #ifdef HAVE_IPV6 type = T_AAAA; @@ -1525,35 +1937,51 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } /* interface name stuff */ - if (qtype == T_A) + intname_restart: + for (intr = daemon->int_names; intr; intr = intr->next) + if (hostname_isequal(name, intr->name)) + break; + + if (intr) { - struct interface_name *intr; + struct addrlist *addrlist; + int gotit = 0; + enumerate_interfaces(0); + for (intr = daemon->int_names; intr; intr = intr->next) if (hostname_isequal(name, intr->name)) - break; - - if (intr) - { - ans = 1; - if (!dryrun) - { - if ((addr.addr.addr4 = get_ifaddr(intr->intr)).s_addr == (in_addr_t) -1) - log_query(F_FORWARD | F_CONFIG | F_IPV4 | F_NEG, name, NULL, NULL); - else + { + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) +#ifdef HAVE_IPV6 + if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == type) +#endif { - log_query(F_FORWARD | F_CONFIG | F_IPV4, name, &addr, NULL); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, type, C_IN, "4", &addr)) - anscount++; +#ifdef HAVE_IPV6 + if (addrlist->flags & ADDRLIST_REVONLY) + continue; +#endif + ans = 1; + if (!dryrun) + { + gotit = 1; + log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, type, C_IN, + type == T_A ? "4" : "6", &addrlist->addr)) + anscount++; + } } - } - continue; - } + } + + if (!dryrun && !gotit) + log_query(F_FORWARD | F_CONFIG | flag | F_NEG, name, NULL, NULL); + + continue; } cname_restart: - if ((crecp = cache_find_by_name(NULL, name, now, flag | F_CNAME))) + if ((crecp = cache_find_by_name(NULL, name, now, flag | F_CNAME | (dryrun ? F_NO_RR : 0)))) { int localise = 0; @@ -1572,66 +2000,153 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); crecp = save; } - - do - { - /* don't answer wildcard queries with data not from /etc/hosts - or DHCP leases */ - if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) - break; - - if (crecp->flags & F_CNAME) + + /* If the client asked for DNSSEC and we can't provide RRSIGs, either + because we've not doing DNSSEC or the cached answer is signed by negative, + don't answer from the cache, forward instead. */ + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd) + { + if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))) + crecp = NULL; +#ifdef HAVE_DNSSEC + else if (crecp->flags & F_DNSSECOK) { - if (!dryrun) - { - log_query(crecp->flags, name, NULL, record_source(crecp->uid)); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), &nameoffset, - T_CNAME, C_IN, "d", cache_get_name(crecp->addr.cname.cache))) - anscount++; - } + /* We're returning validated data, need to return the RRSIG too. */ + struct crec *rr_crec = NULL; + int sigtype = type; + /* The signature may have expired even though the data is still in cache, + forward instead of answering from cache if so. */ + int gotsig = 0; - strcpy(name, cache_get_name(crecp->addr.cname.cache)); - goto cname_restart; - } - - if (crecp->flags & F_NEG) - { - ans = 1; - auth = 0; - if (crecp->flags & F_NXDOMAIN) - nxdomain = 1; - if (!dryrun) - log_query(crecp->flags, name, NULL, NULL); - } - else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd) - { - /* If we are returning local answers depending on network, - filter here. */ - if (localise && - (crecp->flags & F_HOSTS) && - !is_same_net(*((struct in_addr *)&crecp->addr), local_addr, local_netmask)) - continue; - - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; + if (crecp->flags & F_CNAME) + sigtype = T_CNAME; - ans = 1; - if (!dryrun) + while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY))) { - log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr.addr, - record_source(crecp->uid)); - - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), NULL, type, C_IN, - type == T_A ? "4" : "6", &crecp->addr)) - anscount++; + if (rr_crec->addr.sig.type_covered == sigtype && rr_crec->uid == C_IN) + { + char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL); + gotsig = 1; + + if (!dryrun && + add_resource_record(header, limit, &trunc, nameoffset, &ansp, + rr_crec->ttd - now, &nameoffset, + T_RRSIG, C_IN, "t", rr_crec->addr.sig.keylen, sigdata)) + anscount++; + } } + + if (!gotsig) + crecp = NULL; } - } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); +#endif + } + + if (crecp) + do + { + /* don't answer wildcard queries with data not from /etc/hosts + or DHCP leases */ + if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) + break; + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + if (crecp->flags & F_CNAME) + { + char *cname_target = cache_get_cname_target(crecp); + + if (!dryrun) + { + log_query(crecp->flags, name, NULL, record_source(crecp->uid)); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_CNAME, C_IN, "d", cname_target)) + anscount++; + } + + strcpy(name, cname_target); + /* check if target interface_name */ + if (crecp->addr.cname.uid == SRC_INTERFACE) + goto intname_restart; + else + goto cname_restart; + } + + if (crecp->flags & F_NEG) + { + /* We don't cache NSEC records, so if a DNSSEC-validated negative answer + is cached and the client wants DNSSEC, forward rather than answering from the cache */ + if (!sec_reqd || !(crecp->flags & F_DNSSECOK)) + { + ans = 1; + auth = 0; + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + if (!dryrun) + log_query(crecp->flags, name, NULL, NULL); + } + } + else + { + /* If we are returning local answers depending on network, + filter here. */ + if (localise && + (crecp->flags & F_HOSTS) && + !is_same_net(*((struct in_addr *)&crecp->addr), local_addr, local_netmask)) + continue; + + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + + ans = 1; + if (!dryrun) + { + log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr.addr, + record_source(crecp->uid)); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, type, C_IN, + type == T_A ? "4" : "6", &crecp->addr)) + anscount++; + } + } + } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); + } + else if (is_name_synthetic(flag, name, &addr)) + { + ans = 1; + if (!dryrun) + { + log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, type, C_IN, type == T_A ? "4" : "6", &addr)) + anscount++; + } } } - + + if (qtype == T_CNAME || qtype == T_ANY) + { + if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) && + (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | (dryrun ? F_NO_RR : 0))))) + { + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + ans = 1; + if (!dryrun) + { + log_query(crecp->flags, name, NULL, record_source(crecp->uid)); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_CNAME, C_IN, "d", cache_get_cname_target(crecp))) + anscount++; + } + } + } + if (qtype == T_MX || qtype == T_ANY) { int found = 0; @@ -1641,7 +2156,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ans = found = 1; if (!dryrun) { - unsigned int offset; + int offset; log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, &offset, T_MX, C_IN, "sd", rec->weight, rec->target)) @@ -1654,7 +2169,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) && - cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP)) + cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP | F_NO_RR)) { ans = 1; if (!dryrun) @@ -1679,7 +2194,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, found = ans = 1; if (!dryrun) { - unsigned int offset; + int offset; log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>"); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, &offset, T_SRV, C_IN, "sssd", @@ -1798,18 +2313,25 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, /* truncation */ if (trunc) header->hb3 |= HB3_TC; - - if (anscount == 0 && nxdomain) + + if (nxdomain) SET_RCODE(header, NXDOMAIN); else SET_RCODE(header, NOERROR); /* no error */ header->ancount = htons(anscount); header->nscount = htons(0); header->arcount = htons(addncount); - return ansp - (unsigned char *)header; -} - - - + len = ansp - (unsigned char *)header; + + if (have_pseudoheader) + len = add_pseudoheader(header, len, (unsigned char *)limit, 0, NULL, 0, sec_reqd); + + if (*ad_reqd && sec_data) + header->hb4 |= HB4_AD; + else + header->hb4 &= ~HB4_AD; + + return len; +} 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)) && diff --git a/src/rfc3315.c b/src/rfc3315.c new file mode 100644 index 0000000..2665d0d --- /dev/null +++ b/src/rfc3315.c @@ -0,0 +1,2183 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "dnsmasq.h" + +#ifdef HAVE_DHCP6 + +struct state { + unsigned char *clid; + int clid_len, iaid, ia_type, interface, hostname_auth, lease_allocate; + char *client_hostname, *hostname, *domain, *send_domain; + struct dhcp_context *context; + struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr; + unsigned int xid, fqdn_flags; + char *iface_name; + void *packet_options, *end; + struct dhcp_netid *tags, *context_tags; + unsigned char mac[DHCP_CHADDR_MAX]; + unsigned int mac_len, mac_type; +#ifdef OPTION6_PREFIX_CLASS + struct prefix_class *send_prefix_class; +#endif +}; + +static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, + struct in6_addr *client_addr, int is_unicast, time_t now); +static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_t sz, int is_unicast, time_t now); +static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts); +static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string); +static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string); +static void *opt6_find (void *opts, void *end, unsigned int search, unsigned int minsize); +static void *opt6_next(void *opts, void *end); +static unsigned int opt6_uint(unsigned char *opt, int offset, int size); +static void get_context_tag(struct state *state, struct dhcp_context *context); +static int check_ia(struct state *state, void *opt, void **endp, void **ia_option); +static int build_ia(struct state *state, int *t1cntr); +static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz); +#ifdef OPTION6_PREFIX_CLASS +static struct prefix_class *prefix_class_from_context(struct dhcp_context *context); +#endif +static void mark_context_used(struct state *state, struct in6_addr *addr); +static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr); +static int check_address(struct state *state, struct in6_addr *addr); +static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option, + unsigned int *min_time, struct in6_addr *addr, time_t now); +static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now); +static int add_local_addrs(struct dhcp_context *context); +static struct dhcp_netid *add_options(struct state *state, int do_refresh); +static void calculate_times(struct dhcp_context *context, unsigned int *min_time, unsigned int *valid_timep, + unsigned int *preferred_timep, unsigned int lease_time); + +#define opt6_len(opt) ((int)(opt6_uint(opt, -2, 2))) +#define opt6_type(opt) (opt6_uint(opt, -4, 2)) +#define opt6_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[4+(i)])) + +#define opt6_user_vendor_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2+(i)])) +#define opt6_user_vendor_len(opt) ((int)(opt6_uint(opt, -4, 2))) +#define opt6_user_vendor_next(opt, end) (opt6_next(((void *) opt) - 2, end)) + + +unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name, + struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr, + size_t sz, struct in6_addr *client_addr, time_t now) +{ + struct dhcp_vendor *vendor; + int msg_type; + struct state state; + + if (sz <= 4) + return 0; + + msg_type = *((unsigned char *)daemon->dhcp_packet.iov_base); + + /* Mark these so we only match each at most once, to avoid tangled linked lists */ + for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next) + vendor->netid.next = &vendor->netid; + + save_counter(0); + state.context = context; + state.interface = interface; + state.iface_name = iface_name; + state.fallback = fallback; + state.ll_addr = ll_addr; + state.ula_addr = ula_addr; + state.mac_len = 0; + state.tags = NULL; + state.link_address = NULL; + + if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr, + IN6_IS_ADDR_MULTICAST(client_addr), now)) + return msg_type == DHCP6RELAYFORW ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT; + + return 0; +} + +/* This cost me blood to write, it will probably cost you blood to understand - srk. */ +static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, + struct in6_addr *client_addr, int is_unicast, time_t now) +{ + void *end = inbuff + sz; + void *opts = inbuff + 34; + int msg_type = *((unsigned char *)inbuff); + unsigned char *outmsgtypep; + void *opt; + struct dhcp_vendor *vendor; + + /* if not an encaplsulated relayed message, just do the stuff */ + if (msg_type != DHCP6RELAYFORW) + { + /* if link_address != NULL if points to the link address field of the + innermost nested RELAYFORW message, which is where we find the + address of the network on which we can allocate an address. + Recalculate the available contexts using that information. + + link_address == NULL means there's no relay in use, so we try and find the client's + MAC address from the local ND cache. */ + + if (!state->link_address) + get_client_mac(client_addr, state->interface, state->mac, &state->mac_len, &state->mac_type); + else + { + struct dhcp_context *c; + state->context = NULL; + + if (!IN6_IS_ADDR_LOOPBACK(state->link_address) && + !IN6_IS_ADDR_LINKLOCAL(state->link_address) && + !IN6_IS_ADDR_MULTICAST(state->link_address)) + for (c = daemon->dhcp6; c; c = c->next) + if ((c->flags & CONTEXT_DHCP) && + !(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && + is_same_net6(state->link_address, &c->start6, c->prefix) && + is_same_net6(state->link_address, &c->end6, c->prefix)) + { + c->preferred = c->valid = 0xffffffff; + c->current = state->context; + state->context = c; + } + + if (!state->context) + { + inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_WARNING, + _("no address range available for DHCPv6 request from relay at %s"), + daemon->addrbuff); + return 0; + } + } + + if (!state->context) + { + my_syslog(MS_DHCP | LOG_WARNING, + _("no address range available for DHCPv6 request via %s"), state->iface_name); + return 0; + } + + return dhcp6_no_relay(state, msg_type, inbuff, sz, is_unicast, now); + } + + /* must have at least msg_type+hopcount+link_address+peer_address+minimal size option + which is 1 + 1 + 16 + 16 + 2 + 2 = 38 */ + if (sz < 38) + return 0; + + /* copy header stuff into reply message and set type to reply */ + if (!(outmsgtypep = put_opt6(inbuff, 34))) + return 0; + *outmsgtypep = DHCP6RELAYREPL; + + /* look for relay options and set tags if found. */ + for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next) + { + int mopt; + + if (vendor->match_type == MATCH_SUBSCRIBER) + mopt = OPTION6_SUBSCRIBER_ID; + else if (vendor->match_type == MATCH_REMOTE) + mopt = OPTION6_REMOTE_ID; + else + continue; + + if ((opt = opt6_find(opts, end, mopt, 1)) && + vendor->len == opt6_len(opt) && + memcmp(vendor->data, opt6_ptr(opt, 0), vendor->len) == 0 && + vendor->netid.next != &vendor->netid) + { + vendor->netid.next = state->tags; + state->tags = &vendor->netid; + break; + } + } + + /* RFC-6939 */ + if ((opt = opt6_find(opts, end, OPTION6_CLIENT_MAC, 3))) + { + state->mac_type = opt6_uint(opt, 0, 2); + state->mac_len = opt6_len(opt) - 2; + memcpy(&state->mac[0], opt6_ptr(opt, 2), state->mac_len); + } + + for (opt = opts; opt; opt = opt6_next(opt, end)) + { + int o = new_opt6(opt6_type(opt)); + if (opt6_type(opt) == OPTION6_RELAY_MSG) + { + struct in6_addr align; + /* the packet data is unaligned, copy to aligned storage */ + memcpy(&align, inbuff + 2, IN6ADDRSZ); + state->link_address = &align; + /* zero is_unicast since that is now known to refer to the + relayed packet, not the original sent by the client */ + if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, 0, now)) + return 0; + } + else if (opt6_type(opt) != OPTION6_CLIENT_MAC) + put_opt6(opt6_ptr(opt, 0), opt6_len(opt)); + end_opt6(o); + } + + return 1; +} + +static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_t sz, int is_unicast, time_t now) +{ + void *opt; + int i, o, o1, start_opts; + struct dhcp_opt *opt_cfg; + struct dhcp_netid *tagif; + struct dhcp_config *config = NULL; + struct dhcp_netid known_id, iface_id, v6_id; + unsigned char *outmsgtypep; + struct dhcp_vendor *vendor; + struct dhcp_context *context_tmp; + struct dhcp_mac *mac_opt; + unsigned int ignore = 0; +#ifdef OPTION6_PREFIX_CLASS + struct prefix_class *p; + int dump_all_prefix_classes = 0; +#endif + + state->packet_options = inbuff + 4; + state->end = inbuff + sz; + state->clid = NULL; + state->clid_len = 0; + state->lease_allocate = 0; + state->context_tags = NULL; + state->domain = NULL; + state->send_domain = NULL; + state->hostname_auth = 0; + state->hostname = NULL; + state->client_hostname = NULL; + state->fqdn_flags = 0x01; /* default to send if we recieve no FQDN option */ +#ifdef OPTION6_PREFIX_CLASS + state->send_prefix_class = NULL; +#endif + + /* set tag with name == interface */ + iface_id.net = state->iface_name; + iface_id.next = state->tags; + state->tags = &iface_id; + + /* set tag "dhcpv6" */ + v6_id.net = "dhcpv6"; + v6_id.next = state->tags; + state->tags = &v6_id; + + /* copy over transaction-id, and save pointer to message type */ + if (!(outmsgtypep = put_opt6(inbuff, 4))) + return 0; + start_opts = save_counter(-1); + state->xid = outmsgtypep[3] | outmsgtypep[2] << 8 | outmsgtypep[1] << 16; + + /* We're going to be linking tags from all context we use. + mark them as unused so we don't link one twice and break the list */ + for (context_tmp = state->context; context_tmp; context_tmp = context_tmp->current) + { + context_tmp->netid.next = &context_tmp->netid; + + if (option_bool(OPT_LOG_OPTS)) + { + inet_ntop(AF_INET6, &context_tmp->start6, daemon->dhcp_buff, ADDRSTRLEN); + inet_ntop(AF_INET6, &context_tmp->end6, daemon->dhcp_buff2, ADDRSTRLEN); + if (context_tmp->flags & (CONTEXT_STATIC)) + my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCPv6 subnet: %s/%d"), + state->xid, daemon->dhcp_buff, context_tmp->prefix); + else + my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"), + state->xid, daemon->dhcp_buff, daemon->dhcp_buff2); + } + } + + if ((opt = opt6_find(state->packet_options, state->end, OPTION6_CLIENT_ID, 1))) + { + state->clid = opt6_ptr(opt, 0); + state->clid_len = opt6_len(opt); + o = new_opt6(OPTION6_CLIENT_ID); + put_opt6(state->clid, state->clid_len); + end_opt6(o); + } + else if (msg_type != DHCP6IREQ) + return 0; + + /* server-id must match except for SOLICIT, CONFIRM and REBIND messages */ + if (msg_type != DHCP6SOLICIT && msg_type != DHCP6CONFIRM && msg_type != DHCP6IREQ && msg_type != DHCP6REBIND && + (!(opt = opt6_find(state->packet_options, state->end, OPTION6_SERVER_ID, 1)) || + opt6_len(opt) != daemon->duid_len || + memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0)) + return 0; + + o = new_opt6(OPTION6_SERVER_ID); + put_opt6(daemon->duid, daemon->duid_len); + end_opt6(o); + + if (is_unicast && + (msg_type == DHCP6REQUEST || msg_type == DHCP6RENEW || msg_type == DHCP6RELEASE || msg_type == DHCP6DECLINE)) + + { + *outmsgtypep = DHCP6REPLY; + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6USEMULTI); + put_opt6_string("Use multicast"); + end_opt6(o1); + return 1; + } + + /* match vendor and user class options */ + for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next) + { + int mopt; + + if (vendor->match_type == MATCH_VENDOR) + mopt = OPTION6_VENDOR_CLASS; + else if (vendor->match_type == MATCH_USER) + mopt = OPTION6_USER_CLASS; + else + continue; + + if ((opt = opt6_find(state->packet_options, state->end, mopt, 2))) + { + void *enc_opt, *enc_end = opt6_ptr(opt, opt6_len(opt)); + int offset = 0; + + if (mopt == OPTION6_VENDOR_CLASS) + { + if (opt6_len(opt) < 4) + continue; + + if (vendor->enterprise != opt6_uint(opt, 0, 4)) + continue; + + offset = 4; + } + + /* Note that format if user/vendor classes is different to DHCP options - no option types. */ + for (enc_opt = opt6_ptr(opt, offset); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end)) + for (i = 0; i <= (opt6_user_vendor_len(enc_opt) - vendor->len); i++) + if (memcmp(vendor->data, opt6_user_vendor_ptr(enc_opt, i), vendor->len) == 0) + { + vendor->netid.next = state->tags; + state->tags = &vendor->netid; + break; + } + } + } + + if (option_bool(OPT_LOG_OPTS) && (opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_CLASS, 4))) + my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %u"), state->xid, opt6_uint(opt, 0, 4)); + + /* 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 + V-I opts too. */ + for (opt_cfg = daemon->dhcp_match6; opt_cfg; opt_cfg = opt_cfg->next) + { + int match = 0; + + if (opt_cfg->flags & DHOPT_RFC3925) + { + for (opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_OPTS, 4); + opt; + opt = opt6_find(opt6_next(opt, state->end), state->end, OPTION6_VENDOR_OPTS, 4)) + { + void *vopt; + void *vend = opt6_ptr(opt, opt6_len(opt)); + + for (vopt = opt6_find(opt6_ptr(opt, 4), vend, opt_cfg->opt, 0); + vopt; + vopt = opt6_find(opt6_next(vopt, vend), vend, opt_cfg->opt, 0)) + if ((match = match_bytes(opt_cfg, opt6_ptr(vopt, 0), opt6_len(vopt)))) + break; + } + if (match) + break; + } + else + { + if (!(opt = opt6_find(state->packet_options, state->end, opt_cfg->opt, 1))) + continue; + + match = match_bytes(opt_cfg, opt6_ptr(opt, 0), opt6_len(opt)); + } + + if (match) + { + opt_cfg->netid->next = state->tags; + state->tags = opt_cfg->netid; + } + } + + if (state->mac_len != 0) + { + if (option_bool(OPT_LOG_OPTS)) + { + print_mac(daemon->dhcp_buff, state->mac, state->mac_len); + my_syslog(MS_DHCP | LOG_INFO, _("%u client MAC address: %s"), state->xid, daemon->dhcp_buff); + } + + for (mac_opt = daemon->dhcp_macs; mac_opt; mac_opt = mac_opt->next) + if ((unsigned)mac_opt->hwaddr_len == state->mac_len && + ((unsigned)mac_opt->hwaddr_type == state->mac_type || mac_opt->hwaddr_type == 0) && + memcmp_masked(mac_opt->hwaddr, state->mac, state->mac_len, mac_opt->mask)) + { + mac_opt->netid.next = state->tags; + state->tags = &mac_opt->netid; + } + } + + if ((opt = opt6_find(state->packet_options, state->end, OPTION6_FQDN, 1))) + { + /* RFC4704 refers */ + int len = opt6_len(opt) - 1; + + state->fqdn_flags = opt6_uint(opt, 0, 1); + + /* Always force update, since the client has no way to do it itself. */ + if (!option_bool(OPT_FQDN_UPDATE) && !(state->fqdn_flags & 0x01)) + state->fqdn_flags |= 0x03; + + state->fqdn_flags &= ~0x04; + + if (len != 0 && len < 255) + { + unsigned char *pp, *op = opt6_ptr(opt, 1); + char *pq = daemon->dhcp_buff; + + pp = op; + while (*op != 0 && ((op + (*op)) - pp) < len) + { + memcpy(pq, op+1, *op); + pq += *op; + op += (*op)+1; + *(pq++) = '.'; + } + + if (pq != daemon->dhcp_buff) + pq--; + *pq = 0; + + if (legal_hostname(daemon->dhcp_buff)) + { + state->client_hostname = daemon->dhcp_buff; + if (option_bool(OPT_LOG_OPTS)) + my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), state->xid, state->client_hostname); + } + } + } + + if (state->clid) + { + config = find_config(daemon->dhcp_conf, state->context, state->clid, state->clid_len, state->mac, state->mac_len, state->mac_type, NULL); + + if (have_config(config, CONFIG_NAME)) + { + state->hostname = config->hostname; + state->domain = config->domain; + state->hostname_auth = 1; + } + else if (state->client_hostname) + { + state->domain = strip_hostname(state->client_hostname); + + if (strlen(state->client_hostname) != 0) + { + state->hostname = state->client_hostname; + if (!config) + { + /* Search again now we have a hostname. + Only accept configs without CLID here, (it won't match) + to avoid impersonation by name. */ + struct dhcp_config *new = find_config(daemon->dhcp_conf, state->context, NULL, 0, NULL, 0, 0, state->hostname); + if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) + config = new; + } + } + } + } + + if (config) + { + struct dhcp_netid_list *list; + + for (list = config->netid; list; list = list->next) + { + list->list->next = state->tags; + state->tags = list->list; + } + + /* set "known" tag for known hosts */ + known_id.net = "known"; + known_id.next = state->tags; + state->tags = &known_id; + + if (have_config(config, CONFIG_DISABLE)) + ignore = 1; + } + +#ifdef OPTION6_PREFIX_CLASS + /* OPTION_PREFIX_CLASS in ORO, send addresses in all prefix classes */ + if (daemon->prefix_classes && (msg_type == DHCP6SOLICIT || msg_type == DHCP6REQUEST)) + { + void *oro; + + if ((oro = opt6_find(state->packet_options, state->end, OPTION6_ORO, 0))) + for (i = 0; i < opt6_len(oro) - 1; i += 2) + if (opt6_uint(oro, i, 2) == OPTION6_PREFIX_CLASS) + { + dump_all_prefix_classes = 1; + break; + } + + if (msg_type != DHCP6SOLICIT || dump_all_prefix_classes) + /* Add the tags associated with prefix classes so we can use the DHCP ranges. + Not done for SOLICIT as we add them one-at-time. */ + for (p = daemon->prefix_classes; p ; p = p->next) + { + p->tag.next = state->tags; + state->tags = &p->tag; + } + } +#endif + + tagif = run_tag_if(state->tags); + + /* if all the netids in the ignore list are present, ignore this client */ + if (daemon->dhcp_ignore) + { + struct dhcp_netid_list *id_list; + + for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next) + if (match_netid(id_list->list, tagif, 0)) + ignore = 1; + } + + /* if all the netids in the ignore_name list are present, ignore client-supplied name */ + if (!state->hostname_auth) + { + struct dhcp_netid_list *id_list; + + for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, tagif, 0)) + break; + if (id_list) + state->hostname = NULL; + } + + + switch (msg_type) + { + default: + return 0; + + + case DHCP6SOLICIT: + { + int address_assigned = 0; + /* tags without all prefix-class tags */ + struct dhcp_netid *solicit_tags; + struct dhcp_context *c; + + *outmsgtypep = DHCP6ADVERTISE; + + if (opt6_find(state->packet_options, state->end, OPTION6_RAPID_COMMIT, 0)) + { + *outmsgtypep = DHCP6REPLY; + state->lease_allocate = 1; + o = new_opt6(OPTION6_RAPID_COMMIT); + end_opt6(o); + } + + log6_quiet(state, "DHCPSOLICIT", NULL, ignore ? _("ignored") : NULL); + + request_no_address: + solicit_tags = tagif; + + if (ignore) + return 0; + + /* reset USED bits in leases */ + lease6_reset(); + + /* Can use configured address max once per prefix */ + for (c = state->context; c; c = c->current) + c->flags &= ~CONTEXT_CONF_USED; + + for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end)) + { + void *ia_option, *ia_end; + unsigned int min_time = 0xffffffff; + int t1cntr; + int ia_counter; + /* set unless we're sending a particular prefix-class, when we + want only dhcp-ranges with the correct tags set and not those without any tags. */ + int plain_range = 1; + u32 lease_time; + struct dhcp_lease *ltmp; + struct in6_addr *req_addr; + struct in6_addr addr; + + if (!check_ia(state, opt, &ia_end, &ia_option)) + continue; + + /* reset USED bits in contexts - one address per prefix per IAID */ + for (c = state->context; c; c = c->current) + c->flags &= ~CONTEXT_USED; + +#ifdef OPTION6_PREFIX_CLASS + if (daemon->prefix_classes && state->ia_type == OPTION6_IA_NA) + { + void *prefix_opt; + int prefix_class; + + if (dump_all_prefix_classes) + /* OPTION_PREFIX_CLASS in ORO, send addresses in all prefix classes */ + plain_range = 0; + else + { + if ((prefix_opt = opt6_find(opt6_ptr(opt, 12), ia_end, OPTION6_PREFIX_CLASS, 2))) + { + + prefix_class = opt6_uint(prefix_opt, 0, 2); + + for (p = daemon->prefix_classes; p ; p = p->next) + if (p->class == prefix_class) + break; + + if (!p) + my_syslog(MS_DHCP | LOG_WARNING, _("unknown prefix-class %d"), prefix_class); + else + { + /* add tag to list, and exclude undecorated dhcp-ranges */ + p->tag.next = state->tags; + solicit_tags = run_tag_if(&p->tag); + plain_range = 0; + state->send_prefix_class = p; + } + } + else + { + /* client didn't ask for a prefix class, lets see if we can find one. */ + for (p = daemon->prefix_classes; p ; p = p->next) + { + p->tag.next = NULL; + if (match_netid(&p->tag, solicit_tags, 1)) + break; + } + + if (p) + { + plain_range = 0; + state->send_prefix_class = p; + } + } + + if (p && option_bool(OPT_LOG_OPTS)) + my_syslog(MS_DHCP | LOG_INFO, "%u prefix class %d tag:%s", state->xid, p->class, p->tag.net); + } + } +#endif + + o = build_ia(state, &t1cntr); + if (address_assigned) + address_assigned = 2; + + for (ia_counter = 0; ia_option; ia_counter++, ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { + req_addr = opt6_ptr(ia_option, 0); + + if ((c = address6_valid(state->context, req_addr, solicit_tags, plain_range))) + { + lease_time = c->lease_time; + /* If the client asks for an address on the same network as a configured address, + offer the configured address instead, to make moving to newly-configured + addresses automatic. */ + if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr) && check_address(state, &addr)) + { + req_addr = &addr; + mark_config_used(c, &addr); + if (have_config(config, CONFIG_TIME)) + lease_time = config->lease_time; + } + else if (!(c = address6_available(state->context, req_addr, solicit_tags, plain_range))) + continue; /* not an address we're allowed */ + else if (!check_address(state, req_addr)) + continue; /* address leased elsewhere */ + + /* add address to output packet */ +#ifdef OPTION6_PREFIX_CLASS + if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) + state->send_prefix_class = prefix_class_from_context(c); +#endif + add_address(state, c, lease_time, ia_option, &min_time, req_addr, now); + mark_context_used(state, req_addr); + get_context_tag(state, c); + address_assigned = 1; + } + } + + /* Suggest configured address(es) */ + for (c = state->context; c; c = c->current) + if (!(c->flags & CONTEXT_CONF_USED) && + match_netid(c->filter, solicit_tags, plain_range) && + config_valid(config, c, &addr) && + check_address(state, &addr)) + { + mark_config_used(state->context, &addr); + if (have_config(config, CONFIG_TIME)) + lease_time = config->lease_time; + else + lease_time = c->lease_time; + /* add address to output packet */ +#ifdef OPTION6_PREFIX_CLASS + if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) + state->send_prefix_class = prefix_class_from_context(c); +#endif + add_address(state, c, lease_time, NULL, &min_time, &addr, now); + mark_context_used(state, &addr); + get_context_tag(state, c); + address_assigned = 1; + } + + /* return addresses for existing leases */ + ltmp = NULL; + while ((ltmp = lease6_find_by_client(ltmp, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, state->clid, state->clid_len, state->iaid))) + { + req_addr = <mp->addr6; + if ((c = address6_available(state->context, req_addr, solicit_tags, plain_range))) + { +#ifdef OPTION6_PREFIX_CLASS + if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) + state->send_prefix_class = prefix_class_from_context(c); +#endif + add_address(state, c, c->lease_time, NULL, &min_time, req_addr, now); + mark_context_used(state, req_addr); + get_context_tag(state, c); + address_assigned = 1; + } + } + + /* Return addresses for all valid contexts which don't yet have one */ + while ((c = address6_allocate(state->context, state->clid, state->clid_len, state->ia_type == OPTION6_IA_TA, + state->iaid, ia_counter, solicit_tags, plain_range, &addr))) + { +#ifdef OPTION6_PREFIX_CLASS + if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) + state->send_prefix_class = prefix_class_from_context(c); +#endif + add_address(state, c, c->lease_time, NULL, &min_time, &addr, now); + mark_context_used(state, &addr); + get_context_tag(state, c); + address_assigned = 1; + } + + if (address_assigned != 1) + { + /* If the server will not assign any addresses to any IAs in a + subsequent Request from the client, the server MUST send an Advertise + message to the client that doesn't include any IA options. */ + if (!state->lease_allocate) + { + save_counter(o); + continue; + } + + /* If the server cannot assign any addresses to an IA in the message + from the client, the server MUST include the IA in the Reply message + with no addresses in the IA and a Status Code option in the IA + containing status code NoAddrsAvail. */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOADDRS); + put_opt6_string(_("address unavailable")); + end_opt6(o1); + } + + end_ia(t1cntr, min_time, 0); + end_opt6(o); + } + + if (address_assigned) + { + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6SUCCESS); + put_opt6_string(_("success")); + end_opt6(o1); + + /* If --dhcp-authoritative is set, we can tell client not to wait for + other possible servers */ + o = new_opt6(OPTION6_PREFERENCE); + put_opt6_char(option_bool(OPT_AUTHORITATIVE) ? 255 : 0); + end_opt6(o); + tagif = add_options(state, 0); + } + else + { + /* no address, return error */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOADDRS); + put_opt6_string(_("no addresses available")); + end_opt6(o1); + + /* Some clients will ask repeatedly when we're not giving + out addresses because we're in stateless mode. Avoid spamming + the log in that case. */ + for (c = state->context; c; c = c->current) + if (!(c->flags & CONTEXT_RA_STATELESS)) + { + log6_packet(state, state->lease_allocate ? "DHCPREPLY" : "DHCPADVERTISE", NULL, _("no addresses available")); + break; + } + } + + break; + } + + case DHCP6REQUEST: + { + int address_assigned = 0; + int start = save_counter(-1); + + /* set reply message type */ + *outmsgtypep = DHCP6REPLY; + state->lease_allocate = 1; + + log6_quiet(state, "DHCPREQUEST", NULL, ignore ? _("ignored") : NULL); + + if (ignore) + return 0; + + for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end)) + { + void *ia_option, *ia_end; + unsigned int min_time = 0xffffffff; + int t1cntr; + + if (!check_ia(state, opt, &ia_end, &ia_option)) + continue; + + if (!ia_option) + { + /* If we get a request with a IA_*A without addresses, treat it exactly like + a SOLICT with rapid commit set. */ + save_counter(start); + goto request_no_address; + } + + o = build_ia(state, &t1cntr); + + for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { + struct in6_addr *req_addr = opt6_ptr(ia_option, 0); + struct dhcp_context *dynamic, *c; + unsigned int lease_time; + struct in6_addr addr; + int config_ok = 0; + + if ((c = address6_valid(state->context, req_addr, tagif, 1))) + config_ok = config_valid(config, c, &addr) && IN6_ARE_ADDR_EQUAL(&addr, req_addr); + + if ((dynamic = address6_available(state->context, req_addr, tagif, 1)) || c) + { + if (!dynamic && !config_ok) + { + /* Static range, not configured. */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOADDRS); + put_opt6_string(_("address unavailable")); + end_opt6(o1); + } + else if (!check_address(state, req_addr)) + { + /* Address leased to another DUID/IAID */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6UNSPEC); + put_opt6_string(_("address in use")); + end_opt6(o1); + } + else + { + if (!dynamic) + dynamic = c; + + lease_time = dynamic->lease_time; + + if (config_ok && have_config(config, CONFIG_TIME)) + lease_time = config->lease_time; + +#ifdef OPTION6_PREFIX_CLASS + if (dump_all_prefix_classes && state->ia_type == OPTION6_IA_NA) + state->send_prefix_class = prefix_class_from_context(c); +#endif + add_address(state, dynamic, lease_time, ia_option, &min_time, req_addr, now); + get_context_tag(state, dynamic); + address_assigned = 1; + } + } + else + { + /* requested address not on the correct link */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOTONLINK); + put_opt6_string(_("not on link")); + end_opt6(o1); + } + } + + end_ia(t1cntr, min_time, 0); + end_opt6(o); + } + + if (address_assigned) + { + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6SUCCESS); + put_opt6_string(_("success")); + end_opt6(o1); + } + else + { + /* no address, return error */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOADDRS); + put_opt6_string(_("no addresses available")); + end_opt6(o1); + log6_packet(state, "DHCPREPLY", NULL, _("no addresses available")); + } + + tagif = add_options(state, 0); + break; + } + + + case DHCP6RENEW: + { + /* set reply message type */ + *outmsgtypep = DHCP6REPLY; + + log6_quiet(state, "DHCPRENEW", NULL, NULL); + + for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end)) + { + void *ia_option, *ia_end; + unsigned int min_time = 0xffffffff; + int t1cntr, iacntr; + + if (!check_ia(state, opt, &ia_end, &ia_option)) + continue; + + o = build_ia(state, &t1cntr); + iacntr = save_counter(-1); + + for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { + struct dhcp_lease *lease = NULL; + struct in6_addr *req_addr = opt6_ptr(ia_option, 0); + unsigned int preferred_time = opt6_uint(ia_option, 16, 4); + unsigned int valid_time = opt6_uint(ia_option, 20, 4); + char *message = NULL; + struct dhcp_context *this_context; + + if (!(lease = lease6_find(state->clid, state->clid_len, + state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, + state->iaid, req_addr))) + { + /* If the server cannot find a client entry for the IA the server + returns the IA containing no addresses with a Status Code option set + to NoBinding in the Reply message. */ + save_counter(iacntr); + t1cntr = 0; + + log6_packet(state, "DHCPREPLY", req_addr, _("lease not found")); + + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOBINDING); + put_opt6_string(_("no binding found")); + end_opt6(o1); + + preferred_time = valid_time = 0; + break; + } + + + if ((this_context = address6_available(state->context, req_addr, tagif, 1)) || + (this_context = address6_valid(state->context, req_addr, tagif, 1))) + { + struct in6_addr addr; + unsigned int lease_time; + + get_context_tag(state, this_context); + + if (config_valid(config, this_context, &addr) && IN6_ARE_ADDR_EQUAL(&addr, req_addr) && have_config(config, CONFIG_TIME)) + lease_time = config->lease_time; + else + lease_time = this_context->lease_time; + + calculate_times(this_context, &min_time, &valid_time, &preferred_time, lease_time); + + lease_set_expires(lease, valid_time, now); + /* Update MAC record in case it's new information. */ + if (state->mac_len != 0) + lease_set_hwaddr(lease, state->mac, state->clid, state->mac_len, state->mac_type, state->clid_len, now, 0); + if (state->ia_type == OPTION6_IA_NA && state->hostname) + { + char *addr_domain = get_domain6(req_addr); + if (!state->send_domain) + state->send_domain = addr_domain; + lease_set_hostname(lease, state->hostname, state->hostname_auth, addr_domain, state->domain); + message = state->hostname; + } + + + if (preferred_time == 0) + message = _("deprecated"); + } + else + { + preferred_time = valid_time = 0; + message = _("address invalid"); + } + + if (message && (message != state->hostname)) + log6_packet(state, "DHCPREPLY", req_addr, message); + else + log6_quiet(state, "DHCPREPLY", req_addr, message); + + o1 = new_opt6(OPTION6_IAADDR); + put_opt6(req_addr, sizeof(*req_addr)); + put_opt6_long(preferred_time); + put_opt6_long(valid_time); + end_opt6(o1); + } + + end_ia(t1cntr, min_time, 1); + end_opt6(o); + } + + tagif = add_options(state, 0); + break; + + } + + case DHCP6CONFIRM: + { + int good_addr = 0; + + /* set reply message type */ + *outmsgtypep = DHCP6REPLY; + + log6_quiet(state, "DHCPCONFIRM", NULL, NULL); + + for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end)) + { + void *ia_option, *ia_end; + + for (check_ia(state, opt, &ia_end, &ia_option); + ia_option; + ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { + struct in6_addr *req_addr = opt6_ptr(ia_option, 0); + + if (!address6_valid(state->context, req_addr, tagif, 1)) + { + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOTONLINK); + put_opt6_string(_("confirm failed")); + end_opt6(o1); + return 1; + } + + good_addr = 1; + log6_quiet(state, "DHCPREPLY", req_addr, state->hostname); + } + } + + /* No addresses, no reply: RFC 3315 18.2.2 */ + if (!good_addr) + return 0; + + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6SUCCESS ); + put_opt6_string(_("all addresses still on link")); + end_opt6(o1); + break; + } + + case DHCP6IREQ: + { + /* We can't discriminate contexts based on address, as we don't know it. + If there is only one possible context, we can use its tags */ + if (state->context && state->context->netid.net && !state->context->current) + { + state->context->netid.next = NULL; + state->context_tags = &state->context->netid; + } + + /* Similarly, we can't determine domain from address, but if the FQDN is + given in --dhcp-host, we can use that, and failing that we can use the + unqualified configured domain, if any. */ + if (state->hostname_auth) + state->send_domain = state->domain; + else + state->send_domain = get_domain6(NULL); + + log6_quiet(state, "DHCPINFORMATION-REQUEST", NULL, ignore ? _("ignored") : state->hostname); + if (ignore) + return 0; + *outmsgtypep = DHCP6REPLY; + tagif = add_options(state, 1); + break; + } + + + case DHCP6RELEASE: + { + /* set reply message type */ + *outmsgtypep = DHCP6REPLY; + + log6_quiet(state, "DHCPRELEASE", NULL, NULL); + + for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end)) + { + void *ia_option, *ia_end; + int made_ia = 0; + + for (check_ia(state, opt, &ia_end, &ia_option); + ia_option; + ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { + struct dhcp_lease *lease; + + if ((lease = lease6_find(state->clid, state->clid_len, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, + state->iaid, opt6_ptr(ia_option, 0)))) + lease_prune(lease, now); + else + { + if (!made_ia) + { + o = new_opt6(state->ia_type); + put_opt6_long(state->iaid); + if (state->ia_type == OPTION6_IA_NA) + { + put_opt6_long(0); + put_opt6_long(0); + } + made_ia = 1; + } + + o1 = new_opt6(OPTION6_IAADDR); + put_opt6(opt6_ptr(ia_option, 0), IN6ADDRSZ); + put_opt6_long(0); + put_opt6_long(0); + end_opt6(o1); + } + } + + if (made_ia) + { + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOBINDING); + put_opt6_string(_("no binding found")); + end_opt6(o1); + + end_opt6(o); + } + } + + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6SUCCESS); + put_opt6_string(_("release received")); + end_opt6(o1); + + break; + } + + case DHCP6DECLINE: + { + /* set reply message type */ + *outmsgtypep = DHCP6REPLY; + + log6_quiet(state, "DHCPDECLINE", NULL, NULL); + + for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end)) + { + void *ia_option, *ia_end; + int made_ia = 0; + + for (check_ia(state, opt, &ia_end, &ia_option); + ia_option; + ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) + { + struct dhcp_lease *lease; + struct in6_addr *addrp = opt6_ptr(ia_option, 0); + + if (have_config(config, CONFIG_ADDR6) && IN6_ARE_ADDR_EQUAL(&config->addr6, addrp)) + { + prettyprint_time(daemon->dhcp_buff3, DECLINE_BACKOFF); + inet_ntop(AF_INET6, addrp, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_WARNING, _("disabling DHCP static address %s for %s"), + daemon->addrbuff, daemon->dhcp_buff3); + config->flags |= CONFIG_DECLINED; + config->decline_time = now; + } + else + /* make sure this host gets a different address next time. */ + for (context_tmp = state->context; context_tmp; context_tmp = context_tmp->current) + context_tmp->addr_epoch++; + + if ((lease = lease6_find(state->clid, state->clid_len, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, + state->iaid, opt6_ptr(ia_option, 0)))) + lease_prune(lease, now); + else + { + if (!made_ia) + { + o = new_opt6(state->ia_type); + put_opt6_long(state->iaid); + if (state->ia_type == OPTION6_IA_NA) + { + put_opt6_long(0); + put_opt6_long(0); + } + made_ia = 1; + } + + o1 = new_opt6(OPTION6_IAADDR); + put_opt6(opt6_ptr(ia_option, 0), IN6ADDRSZ); + put_opt6_long(0); + put_opt6_long(0); + end_opt6(o1); + } + } + + if (made_ia) + { + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOBINDING); + put_opt6_string(_("no binding found")); + end_opt6(o1); + + end_opt6(o); + } + + } + + /* We must anwser with 'success' in global section anyway */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6SUCCESS); + put_opt6_string(_("success")); + end_opt6(o1); + break; + } + + } + + log_tags(tagif, state->xid); + log6_opts(0, state->xid, daemon->outpacket.iov_base + start_opts, daemon->outpacket.iov_base + save_counter(-1)); + + return 1; + +} + +static struct dhcp_netid *add_options(struct state *state, int do_refresh) +{ + void *oro; + /* filter options based on tags, those we want get DHOPT_TAGOK bit set */ + struct dhcp_netid *tagif = option_filter(state->tags, state->context_tags, daemon->dhcp_opts6); + struct dhcp_opt *opt_cfg; + int done_dns = 0, done_refresh = !do_refresh, do_encap = 0; + int i, o, o1; + + oro = opt6_find(state->packet_options, state->end, OPTION6_ORO, 0); + + for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next) + { + /* netids match and not encapsulated? */ + if (!(opt_cfg->flags & DHOPT_TAGOK)) + continue; + + if (!(opt_cfg->flags & DHOPT_FORCE) && oro) + { + for (i = 0; i < opt6_len(oro) - 1; i += 2) + if (opt6_uint(oro, i, 2) == (unsigned)opt_cfg->opt) + break; + + /* option not requested */ + if (i >= opt6_len(oro) - 1) + continue; + } + + if (opt_cfg->opt == OPTION6_REFRESH_TIME) + done_refresh = 1; + + if (opt_cfg->flags & DHOPT_ADDR6) + { + int len, j; + struct in6_addr *a; + + if (opt_cfg->opt == OPTION6_DNS_SERVER) + done_dns = 1; + + for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, j = 0; + j < opt_cfg->len; j += IN6ADDRSZ, a++) + if ((IN6_IS_ADDR_ULA_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) || + (IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ll_addr))) + len -= IN6ADDRSZ; + + if (len != 0) + { + + o = new_opt6(opt_cfg->opt); + + for (a = (struct in6_addr *)opt_cfg->val, j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++) + { + if (IN6_IS_ADDR_UNSPECIFIED(a)) + { + if (!add_local_addrs(state->context)) + put_opt6(state->fallback, IN6ADDRSZ); + } + else if (IN6_IS_ADDR_ULA_ZERO(a)) + { + if (!IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) + put_opt6(state->ula_addr, IN6ADDRSZ); + } + else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a)) + { + if (!IN6_IS_ADDR_UNSPECIFIED(state->ll_addr)) + put_opt6(state->ll_addr, IN6ADDRSZ); + } + else + put_opt6(a, IN6ADDRSZ); + } + + end_opt6(o); + } + } + else + { + o = new_opt6(opt_cfg->opt); + if (opt_cfg->val) + put_opt6(opt_cfg->val, opt_cfg->len); + end_opt6(o); + } + } + + if (daemon->port == NAMESERVER_PORT && !done_dns) + { + o = new_opt6(OPTION6_DNS_SERVER); + if (!add_local_addrs(state->context)) + put_opt6(state->fallback, IN6ADDRSZ); + end_opt6(o); + } + + if (state->context && !done_refresh) + { + struct dhcp_context *c; + unsigned int lease_time = 0xffffffff; + + /* Find the smallest lease tie of all contexts, + subjext to the RFC-4242 stipulation that this must not + be less than 600. */ + for (c = state->context; c; c = c->next) + if (c->lease_time < lease_time) + { + if (c->lease_time < 600) + lease_time = 600; + else + lease_time = c->lease_time; + } + + o = new_opt6(OPTION6_REFRESH_TIME); + put_opt6_long(lease_time); + end_opt6(o); + } + + /* handle vendor-identifying vendor-encapsulated options, + dhcp-option = vi-encap:13,17,....... */ + for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next) + opt_cfg->flags &= ~DHOPT_ENCAP_DONE; + + if (oro) + for (i = 0; i < opt6_len(oro) - 1; i += 2) + if (opt6_uint(oro, i, 2) == OPTION6_VENDOR_OPTS) + do_encap = 1; + + for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next) + { + if (opt_cfg->flags & DHOPT_RFC3925) + { + int found = 0; + struct dhcp_opt *oc; + + if (opt_cfg->flags & DHOPT_ENCAP_DONE) + continue; + + for (oc = daemon->dhcp_opts6; oc; oc = oc->next) + { + oc->flags &= ~DHOPT_ENCAP_MATCH; + + if (!(oc->flags & DHOPT_RFC3925) || opt_cfg->u.encap != oc->u.encap) + continue; + + oc->flags |= DHOPT_ENCAP_DONE; + if (match_netid(oc->netid, tagif, 1)) + { + /* option requested/forced? */ + if (!oro || do_encap || (oc->flags & DHOPT_FORCE)) + { + oc->flags |= DHOPT_ENCAP_MATCH; + found = 1; + } + } + } + + if (found) + { + o = new_opt6(OPTION6_VENDOR_OPTS); + put_opt6_long(opt_cfg->u.encap); + + for (oc = daemon->dhcp_opts6; oc; oc = oc->next) + if (oc->flags & DHOPT_ENCAP_MATCH) + { + o1 = new_opt6(oc->opt); + put_opt6(oc->val, oc->len); + end_opt6(o1); + } + end_opt6(o); + } + } + } + + + if (state->hostname) + { + unsigned char *p; + size_t len = strlen(state->hostname); + + if (state->send_domain) + len += strlen(state->send_domain) + 2; + + o = new_opt6(OPTION6_FQDN); + if ((p = expand(len + 2))) + { + *(p++) = state->fqdn_flags; + p = do_rfc1035_name(p, state->hostname); + if (state->send_domain) + { + p = do_rfc1035_name(p, state->send_domain); + *p = 0; + } + } + end_opt6(o); + } + + + /* logging */ + if (option_bool(OPT_LOG_OPTS) && oro) + { + char *q = daemon->namebuff; + for (i = 0; i < opt6_len(oro) - 1; i += 2) + { + char *s = option_string(AF_INET6, opt6_uint(oro, i, 2), NULL, 0, NULL, 0); + q += snprintf(q, MAXDNAME - (q - daemon->namebuff), + "%d%s%s%s", + opt6_uint(oro, i, 2), + strlen(s) != 0 ? ":" : "", + s, + (i > opt6_len(oro) - 3) ? "" : ", "); + if ( i > opt6_len(oro) - 3 || (q - daemon->namebuff) > 40) + { + q = daemon->namebuff; + my_syslog(MS_DHCP | LOG_INFO, _("%u requested options: %s"), state->xid, daemon->namebuff); + } + } + } + + return tagif; +} + +static int add_local_addrs(struct dhcp_context *context) +{ + int done = 0; + + for (; context; context = context->current) + if ((context->flags & CONTEXT_USED) && !IN6_IS_ADDR_UNSPECIFIED(&context->local6)) + { + /* squash duplicates */ + struct dhcp_context *c; + for (c = context->current; c; c = c->current) + if ((c->flags & CONTEXT_USED) && + IN6_ARE_ADDR_EQUAL(&context->local6, &c->local6)) + break; + + if (!c) + { + done = 1; + put_opt6(&context->local6, IN6ADDRSZ); + } + } + + return done; +} + + +static void get_context_tag(struct state *state, struct dhcp_context *context) +{ + /* get tags from context if we've not used it before */ + if (context->netid.next == &context->netid && context->netid.net) + { + context->netid.next = state->context_tags; + state->context_tags = &context->netid; + if (!state->hostname_auth) + { + struct dhcp_netid_list *id_list; + + for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, &context->netid, 0)) + break; + if (id_list) + state->hostname = NULL; + } + } +} + +#ifdef OPTION6_PREFIX_CLASS +static struct prefix_class *prefix_class_from_context(struct dhcp_context *context) +{ + struct prefix_class *p; + struct dhcp_netid *t; + + for (p = daemon->prefix_classes; p ; p = p->next) + for (t = context->filter; t; t = t->next) + if (strcmp(p->tag.net, t->net) == 0) + return p; + + return NULL; +} +#endif + +static int check_ia(struct state *state, void *opt, void **endp, void **ia_option) +{ + state->ia_type = opt6_type(opt); + *ia_option = NULL; + + if (state->ia_type != OPTION6_IA_NA && state->ia_type != OPTION6_IA_TA) + return 0; + + if (state->ia_type == OPTION6_IA_NA && opt6_len(opt) < 12) + return 0; + + if (state->ia_type == OPTION6_IA_TA && opt6_len(opt) < 4) + return 0; + + *endp = opt6_ptr(opt, opt6_len(opt)); + state->iaid = opt6_uint(opt, 0, 4); + *ia_option = opt6_find(opt6_ptr(opt, state->ia_type == OPTION6_IA_NA ? 12 : 4), *endp, OPTION6_IAADDR, 24); + + return 1; +} + + +static int build_ia(struct state *state, int *t1cntr) +{ + int o = new_opt6(state->ia_type); + + put_opt6_long(state->iaid); + *t1cntr = 0; + + if (state->ia_type == OPTION6_IA_NA) + { + /* save pointer */ + *t1cntr = save_counter(-1); + /* so we can fill these in later */ + put_opt6_long(0); + put_opt6_long(0); + } + + return o; +} + +static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz) +{ + if (t1cntr != 0) + { + /* go back an fill in fields in IA_NA option */ + int sav = save_counter(t1cntr); + unsigned int t1, t2, fuzz = 0; + + if (do_fuzz) + { + fuzz = rand16(); + + while (fuzz > (min_time/16)) + fuzz = fuzz/2; + } + + t1 = (min_time == 0xffffffff) ? 0xffffffff : min_time/2 - fuzz; + t2 = (min_time == 0xffffffff) ? 0xffffffff : ((min_time/8)*7) - fuzz; + put_opt6_long(t1); + put_opt6_long(t2); + save_counter(sav); + } +} + +static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option, + unsigned int *min_time, struct in6_addr *addr, time_t now) +{ + unsigned int valid_time = 0, preferred_time = 0; + int o = new_opt6(OPTION6_IAADDR); + struct dhcp_lease *lease; + + /* get client requested times */ + if (ia_option) + { + preferred_time = opt6_uint(ia_option, 16, 4); + valid_time = opt6_uint(ia_option, 20, 4); + } + + calculate_times(context, min_time, &valid_time, &preferred_time, lease_time); + + put_opt6(addr, sizeof(*addr)); + put_opt6_long(preferred_time); + put_opt6_long(valid_time); + +#ifdef OPTION6_PREFIX_CLASS + if (state->send_prefix_class) + { + int o1 = new_opt6(OPTION6_PREFIX_CLASS); + put_opt6_short(state->send_prefix_class->class); + end_opt6(o1); + } +#endif + + end_opt6(o); + + if (state->lease_allocate) + update_leases(state, context, addr, valid_time, now); + + if ((lease = lease6_find_by_addr(addr, 128, 0))) + lease->flags |= LEASE_USED; + + /* get tags from context if we've not used it before */ + if (context->netid.next == &context->netid && context->netid.net) + { + context->netid.next = state->context_tags; + state->context_tags = &context->netid; + + if (!state->hostname_auth) + { + struct dhcp_netid_list *id_list; + + for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next) + if ((!id_list->list) || match_netid(id_list->list, &context->netid, 0)) + break; + if (id_list) + state->hostname = NULL; + } + } + + log6_quiet(state, state->lease_allocate ? "DHCPREPLY" : "DHCPADVERTISE", addr, state->hostname); + +} + +static void mark_context_used(struct state *state, struct in6_addr *addr) +{ + struct dhcp_context *context; + + /* Mark that we have an address for this prefix. */ +#ifdef OPTION6_PREFIX_CLASS + for (context = state->context; context; context = context->current) + if (is_same_net6(addr, &context->start6, context->prefix) && + (!state->send_prefix_class || state->send_prefix_class == prefix_class_from_context(context))) + context->flags |= CONTEXT_USED; +#else + for (context = state->context; context; context = context->current) + if (is_same_net6(addr, &context->start6, context->prefix)) + context->flags |= CONTEXT_USED; +#endif +} + +static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr) +{ + for (; context; context = context->current) + if (is_same_net6(addr, &context->start6, context->prefix)) + context->flags |= CONTEXT_CONF_USED; +} + +/* make sure address not leased to another CLID/IAID */ +static int check_address(struct state *state, struct in6_addr *addr) +{ + struct dhcp_lease *lease; + + if (!(lease = lease6_find_by_addr(addr, 128, 0))) + return 1; + + if (lease->clid_len != state->clid_len || + memcmp(lease->clid, state->clid, state->clid_len) != 0 || + lease->iaid != state->iaid) + return 0; + + return 1; +} + + +/* Calculate valid and preferred times to send in leases/renewals. + + Inputs are: + + *valid_timep, *preferred_timep - requested times from IAADDR options. + context->valid, context->preferred - times associated with subnet address on local interface. + context->flags | CONTEXT_DEPRECATE - "deprecated" flag in dhcp-range. + lease_time - configured time for context for individual client. + *min_time - smallest valid time sent so far. + + Outputs are : + + *valid_timep, *preferred_timep - times to be send in IAADDR option. + *min_time - smallest valid time sent so far, to calculate T1 and T2. + + */ +static void calculate_times(struct dhcp_context *context, unsigned int *min_time, unsigned int *valid_timep, + unsigned int *preferred_timep, unsigned int lease_time) +{ + unsigned int req_preferred = *preferred_timep, req_valid = *valid_timep; + unsigned int valid_time = lease_time, preferred_time = lease_time; + + /* RFC 3315: "A server ignores the lifetimes set + by the client if the preferred lifetime is greater than the valid + lifetime. */ + if (req_preferred <= req_valid) + { + if (req_preferred != 0) + { + /* 0 == "no preference from client" */ + if (req_preferred < 120u) + req_preferred = 120u; /* sanity */ + + if (req_preferred < preferred_time) + preferred_time = req_preferred; + } + + if (req_valid != 0) + /* 0 == "no preference from client" */ + { + if (req_valid < 120u) + req_valid = 120u; /* sanity */ + + if (req_valid < valid_time) + valid_time = req_valid; + } + } + + /* deprecate (preferred == 0) which configured, or when local address + is deprecated */ + if ((context->flags & CONTEXT_DEPRECATE) || context->preferred == 0) + preferred_time = 0; + + if (preferred_time != 0 && preferred_time < *min_time) + *min_time = preferred_time; + + if (valid_time != 0 && valid_time < *min_time) + *min_time = valid_time; + + *valid_timep = valid_time; + *preferred_timep = preferred_time; +} + +static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now) +{ + struct dhcp_lease *lease = lease6_find_by_addr(addr, 128, 0); +#ifdef HAVE_SCRIPT + struct dhcp_netid *tagif = run_tag_if(state->tags); +#endif + + (void)context; + + if (!lease) + lease = lease6_allocate(addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA); + + if (lease) + { + lease_set_expires(lease, lease_time, now); + lease_set_iaid(lease, state->iaid); + lease_set_hwaddr(lease, state->mac, state->clid, state->mac_len, state->mac_type, state->clid_len, now, 0); + lease_set_interface(lease, state->interface, now); + if (state->hostname && state->ia_type == OPTION6_IA_NA) + { + char *addr_domain = get_domain6(addr); + if (!state->send_domain) + state->send_domain = addr_domain; + lease_set_hostname(lease, state->hostname, state->hostname_auth, addr_domain, state->domain); + } + +#ifdef HAVE_SCRIPT + if (daemon->lease_change_command) + { + void *class_opt; + lease->flags |= LEASE_CHANGED; + free(lease->extradata); + lease->extradata = NULL; + lease->extradata_size = lease->extradata_len = 0; + lease->vendorclass_count = 0; + + if ((class_opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_CLASS, 4))) + { + void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt)); + lease->vendorclass_count++; + /* send enterprise number first */ + sprintf(daemon->dhcp_buff2, "%u", opt6_uint(class_opt, 0, 4)); + lease_add_extradata(lease, (unsigned char *)daemon->dhcp_buff2, strlen(daemon->dhcp_buff2), 0); + + if (opt6_len(class_opt) >= 6) + for (enc_opt = opt6_ptr(class_opt, 4); enc_opt; enc_opt = opt6_next(enc_opt, enc_end)) + { + lease->vendorclass_count++; + lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0); + } + } + + lease_add_extradata(lease, (unsigned char *)state->client_hostname, + state->client_hostname ? strlen(state->client_hostname) : 0, 0); + + /* space-concat tag set */ + if (!tagif && !context->netid.net) + lease_add_extradata(lease, NULL, 0, 0); + else + { + if (context->netid.net) + lease_add_extradata(lease, (unsigned char *)context->netid.net, strlen(context->netid.net), tagif ? ' ' : 0); + + if (tagif) + { + struct dhcp_netid *n; + for (n = tagif; 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 (state->link_address) + inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN); + + lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->link_address ? strlen(daemon->addrbuff) : 0, 0); + + if ((class_opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2))) + { + void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt)); + for (enc_opt = opt6_ptr(class_opt, 0); enc_opt; enc_opt = opt6_next(enc_opt, enc_end)) + lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0); + } + } +#endif + + } +} + + + +static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts) +{ + void *opt; + char *desc = nest ? "nest" : "sent"; + + if (!option_bool(OPT_LOG_OPTS) || start_opts == end_opts) + return; + + for (opt = start_opts; opt; opt = opt6_next(opt, end_opts)) + { + int type = opt6_type(opt); + void *ia_options = NULL; + char *optname; + + if (type == OPTION6_IA_NA) + { + sprintf(daemon->namebuff, "IAID=%u T1=%u T2=%u", + opt6_uint(opt, 0, 4), opt6_uint(opt, 4, 4), opt6_uint(opt, 8, 4)); + optname = "ia-na"; + ia_options = opt6_ptr(opt, 12); + } + else if (type == OPTION6_IA_TA) + { + sprintf(daemon->namebuff, "IAID=%u", opt6_uint(opt, 0, 4)); + optname = "ia-ta"; + ia_options = opt6_ptr(opt, 4); + } + else if (type == OPTION6_IAADDR) + { + inet_ntop(AF_INET6, opt6_ptr(opt, 0), daemon->addrbuff, ADDRSTRLEN); + sprintf(daemon->namebuff, "%s PL=%u VL=%u", + daemon->addrbuff, opt6_uint(opt, 16, 4), opt6_uint(opt, 20, 4)); + optname = "iaaddr"; + ia_options = opt6_ptr(opt, 24); + } +#ifdef OPTION6_PREFIX_CLASS + else if (type == OPTION6_PREFIX_CLASS) + { + optname = "prefix-class"; + sprintf(daemon->namebuff, "class=%u", opt6_uint(opt, 0, 2)); + } +#endif + else if (type == OPTION6_STATUS_CODE) + { + int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2)); + memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), opt6_len(opt)-2); + daemon->namebuff[len + opt6_len(opt) - 2] = 0; + optname = "status"; + } + else + { + /* account for flag byte on FQDN */ + int offset = type == OPTION6_FQDN ? 1 : 0; + optname = option_string(AF_INET6, type, opt6_ptr(opt, offset), opt6_len(opt) - offset, daemon->namebuff, MAXDNAME); + } + + my_syslog(MS_DHCP | LOG_INFO, "%u %s size:%3d option:%3d %s %s", + xid, desc, opt6_len(opt), type, optname, daemon->namebuff); + + if (ia_options) + log6_opts(1, xid, ia_options, opt6_ptr(opt, opt6_len(opt))); + } +} + +static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string) +{ + if (option_bool(OPT_LOG_OPTS) || !option_bool(OPT_QUIET_DHCP6)) + log6_packet(state, type, addr, string); +} + +static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string) +{ + int clid_len = state->clid_len; + + /* avoid buffer overflow */ + if (clid_len > 100) + clid_len = 100; + + print_mac(daemon->namebuff, state->clid, clid_len); + + if (addr) + { + inet_ntop(AF_INET6, addr, daemon->dhcp_buff2, 255); + strcat(daemon->dhcp_buff2, " "); + } + else + daemon->dhcp_buff2[0] = 0; + + if(option_bool(OPT_LOG_OPTS)) + my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s %s", + state->xid, + type, + state->iface_name, + daemon->dhcp_buff2, + daemon->namebuff, + string ? string : ""); + else + my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s %s", + type, + state->iface_name, + daemon->dhcp_buff2, + daemon->namebuff, + string ? string : ""); +} + +static void *opt6_find (void *opts, void *end, unsigned int search, unsigned int minsize) +{ + u16 opt, opt_len; + void *start; + + if (!opts) + return NULL; + + while (1) + { + if (end - opts < 4) + return NULL; + + start = opts; + GETSHORT(opt, opts); + GETSHORT(opt_len, opts); + + if (opt_len > (end - opts)) + return NULL; + + if (opt == search && (opt_len >= minsize)) + return start; + + opts += opt_len; + } +} + +static void *opt6_next(void *opts, void *end) +{ + u16 opt_len; + + if (end - opts < 4) + return NULL; + + opts += 2; + GETSHORT(opt_len, opts); + + if (opt_len >= (end - opts)) + return NULL; + + return opts + opt_len; +} + +static unsigned int opt6_uint(unsigned char *opt, int offset, int size) +{ + /* this worries about unaligned data and byte order */ + unsigned int ret = 0; + int i; + unsigned char *p = opt6_ptr(opt, offset); + + for (i = 0; i < size; i++) + ret = (ret << 8) | *p++; + + return ret; +} + +void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id) +{ + /* ->local is same value for all relays on ->current chain */ + + struct all_addr from; + unsigned char *header; + unsigned char *inbuff = daemon->dhcp_packet.iov_base; + int msg_type = *inbuff; + int hopcount; + struct in6_addr multicast; + unsigned int maclen, mactype; + unsigned char mac[DHCP_CHADDR_MAX]; + + inet_pton(AF_INET6, ALL_SERVERS, &multicast); + get_client_mac(peer_address, scope_id, mac, &maclen, &mactype); + + /* source address == relay address */ + from.addr.addr6 = relay->local.addr.addr6; + + /* Get hop count from nested relayed message */ + if (msg_type == DHCP6RELAYFORW) + hopcount = *((unsigned char *)inbuff+1) + 1; + else + hopcount = 0; + + /* RFC 3315 HOP_COUNT_LIMIT */ + if (hopcount > 32) + return; + + save_counter(0); + + if ((header = put_opt6(NULL, 34))) + { + int o; + + header[0] = DHCP6RELAYFORW; + header[1] = hopcount; + memcpy(&header[2], &relay->local.addr.addr6, IN6ADDRSZ); + memcpy(&header[18], peer_address, IN6ADDRSZ); + + /* RFC-6939 */ + if (maclen != 0) + { + o = new_opt6(OPTION6_CLIENT_MAC); + put_opt6_short(mactype); + put_opt6(mac, maclen); + end_opt6(o); + } + + o = new_opt6(OPTION6_RELAY_MSG); + put_opt6(inbuff, sz); + end_opt6(o); + + for (; relay; relay = relay->current) + { + union mysockaddr to; + + to.sa.sa_family = AF_INET6; + to.in6.sin6_addr = relay->server.addr.addr6; + to.in6.sin6_port = htons(DHCPV6_SERVER_PORT); + to.in6.sin6_flowinfo = 0; + to.in6.sin6_scope_id = 0; + + if (IN6_ARE_ADDR_EQUAL(&relay->server.addr.addr6, &multicast)) + { + int multicast_iface; + if (!relay->interface || strchr(relay->interface, '*') || + (multicast_iface = if_nametoindex(relay->interface)) == 0 || + setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &multicast_iface, sizeof(multicast_iface)) == -1) + my_syslog(MS_DHCP | LOG_ERR, _("Cannot multicast to DHCPv6 server without correct interface")); + } + + send_from(daemon->dhcp6fd, 0, daemon->outpacket.iov_base, save_counter(0), &to, &from, 0); + + if (option_bool(OPT_LOG_OPTS)) + { + inet_ntop(AF_INET6, &relay->local, daemon->addrbuff, ADDRSTRLEN); + inet_ntop(AF_INET6, &relay->server, daemon->namebuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, daemon->namebuff); + } + + /* Save this for replies */ + relay->iface_index = scope_id; + } + } +} + +unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface) +{ + struct dhcp_relay *relay; + struct in6_addr link; + unsigned char *inbuff = daemon->dhcp_packet.iov_base; + + /* must have at least msg_type+hopcount+link_address+peer_address+minimal size option + which is 1 + 1 + 16 + 16 + 2 + 2 = 38 */ + + if (sz < 38 || *inbuff != DHCP6RELAYREPL) + return 0; + + memcpy(&link, &inbuff[2], IN6ADDRSZ); + + for (relay = daemon->relay6; relay; relay = relay->next) + if (IN6_ARE_ADDR_EQUAL(&link, &relay->local.addr.addr6) && + (!relay->interface || wildcard_match(relay->interface, arrival_interface))) + break; + + save_counter(0); + + if (relay) + { + void *opt, *opts = inbuff + 34; + void *end = inbuff + sz; + for (opt = opts; opt; opt = opt6_next(opt, end)) + if (opt6_type(opt) == OPTION6_RELAY_MSG && opt6_len(opt) > 0) + { + int encap_type = *((unsigned char *)opt6_ptr(opt, 0)); + put_opt6(opt6_ptr(opt, 0), opt6_len(opt)); + memcpy(&peer->sin6_addr, &inbuff[18], IN6ADDRSZ); + peer->sin6_scope_id = relay->iface_index; + return encap_type == DHCP6RELAYREPL ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT; + } + } + + return 0; +} + +#endif diff --git a/src/slaac.c b/src/slaac.c new file mode 100644 index 0000000..abaad53 --- /dev/null +++ b/src/slaac.c @@ -0,0 +1,209 @@ +/* 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_DHCP6 + +#include <netinet/icmp6.h> + +static int ping_id = 0; + +void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force) +{ + struct slaac_address *slaac, *old, **up; + struct dhcp_context *context; + int dns_dirty = 0; + + if (!(lease->flags & LEASE_HAVE_HWADDR) || + (lease->flags & (LEASE_TA | LEASE_NA)) || + lease->last_interface == 0 || + !lease->hostname) + return ; + + old = lease->slaac_address; + lease->slaac_address = NULL; + + for (context = daemon->dhcp6; context; context = context->next) + if ((context->flags & CONTEXT_RA_NAME) && + !(context->flags & CONTEXT_OLD) && + lease->last_interface == context->if_index) + { + struct in6_addr addr = context->start6; + if (lease->hwaddr_len == 6 && + (lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802)) + { + /* convert MAC address to EUI-64 */ + memcpy(&addr.s6_addr[8], lease->hwaddr, 3); + memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3); + addr.s6_addr[11] = 0xff; + addr.s6_addr[12] = 0xfe; + } +#if defined(ARPHRD_EUI64) + else if (lease->hwaddr_len == 8 && + lease->hwaddr_type == ARPHRD_EUI64) + memcpy(&addr.s6_addr[8], lease->hwaddr, 8); +#endif +#if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64) + else if (lease->clid_len == 9 && + lease->clid[0] == ARPHRD_EUI64 && + lease->hwaddr_type == ARPHRD_IEEE1394) + /* firewire has EUI-64 identifier as clid */ + memcpy(&addr.s6_addr[8], &lease->clid[1], 8); +#endif + else + continue; + + addr.s6_addr[8] ^= 0x02; + + /* check if we already have this one */ + for (up = &old, slaac = old; slaac; slaac = slaac->next) + { + if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr)) + { + *up = slaac->next; + /* recheck when DHCPv4 goes through init-reboot */ + if (force) + { + slaac->ping_time = now; + slaac->backoff = 1; + dns_dirty = 1; + } + break; + } + up = &slaac->next; + } + + /* No, make new one */ + if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address)))) + { + slaac->ping_time = now; + slaac->backoff = 1; + slaac->addr = addr; + /* Do RA's to prod it */ + ra_start_unsolicted(now, context); + } + + if (slaac) + { + slaac->next = lease->slaac_address; + lease->slaac_address = slaac; + } + } + + if (old || dns_dirty) + lease_update_dns(1); + + /* Free any no reused */ + for (; old; old = slaac) + { + slaac = old->next; + free(old); + } +} + + +time_t periodic_slaac(time_t now, struct dhcp_lease *leases) +{ + struct dhcp_context *context; + struct dhcp_lease *lease; + struct slaac_address *slaac; + time_t next_event = 0; + + for (context = daemon->dhcp6; context; context = context->next) + if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD)) + break; + + /* nothing configured */ + if (!context) + return 0; + + while (ping_id == 0) + ping_id = rand16(); + + for (lease = leases; lease; lease = lease->next) + for (slaac = lease->slaac_address; slaac; slaac = slaac->next) + { + /* confirmed or given up? */ + if (slaac->backoff == 0 || slaac->ping_time == 0) + continue; + + if (difftime(slaac->ping_time, now) <= 0.0) + { + struct ping_packet *ping; + struct sockaddr_in6 addr; + + save_counter(0); + ping = expand(sizeof(struct ping_packet)); + ping->type = ICMP6_ECHO_REQUEST; + ping->code = 0; + ping->identifier = ping_id; + ping->sequence_no = slaac->backoff; + + memset(&addr, 0, sizeof(addr)); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.sin6_len = sizeof(struct sockaddr_in6); +#endif + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(IPPROTO_ICMPV6); + addr.sin6_addr = slaac->addr; + + if (sendto(daemon->icmp6fd, daemon->outpacket.iov_base, save_counter(0), 0, + (struct sockaddr *)&addr, sizeof(addr)) == -1 && + errno == EHOSTUNREACH) + slaac->ping_time = 0; /* Give up */ + else + { + slaac->ping_time += (1 << (slaac->backoff - 1)) + (rand16()/21785); /* 0 - 3 */ + if (slaac->backoff > 4) + slaac->ping_time += rand16()/4000; /* 0 - 15 */ + if (slaac->backoff < 12) + slaac->backoff++; + } + } + + if (slaac->ping_time != 0 && + (next_event == 0 || difftime(next_event, slaac->ping_time) >= 0.0)) + next_event = slaac->ping_time; + } + + return next_event; +} + + +void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases) +{ + struct dhcp_lease *lease; + struct slaac_address *slaac; + struct ping_packet *ping = (struct ping_packet *)packet; + int gotone = 0; + + if (ping->identifier == ping_id) + for (lease = leases; lease; lease = lease->next) + for (slaac = lease->slaac_address; slaac; slaac = slaac->next) + if (slaac->backoff != 0 && IN6_ARE_ADDR_EQUAL(sender, &slaac->addr)) + { + slaac->backoff = 0; + gotone = 1; + inet_ntop(AF_INET6, sender, daemon->addrbuff, ADDRSTRLEN); + if (!option_bool(OPT_QUIET_DHCP6)) + my_syslog(MS_DHCP | LOG_INFO, "SLAAC-CONFIRM(%s) %s %s", interface, daemon->addrbuff, lease->hostname); + } + + lease_update_dns(gotone); +} + +#endif diff --git a/src/tables.c b/src/tables.c new file mode 100644 index 0000000..aae1252 --- /dev/null +++ b/src/tables.c @@ -0,0 +1,173 @@ +/* tables.c is Copyright (c) 2014 Sven Falempin All Rights Reserved. + + Author's email: sfalempin@citypassenger.com + + 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 + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#if defined(HAVE_IPSET) && defined(HAVE_BSD_NETWORK) + +#ifndef __FreeBSD__ +#include <string.h> +#endif + +#include <sys/types.h> +#include <sys/ioctl.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <net/pfvar.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> + +#define UNUSED(x) (void)(x) + +static char *pf_device = "/dev/pf"; +static int dev = -1; + +static char *pfr_strerror(int errnum) +{ + switch (errnum) + { + case ESRCH: + return "Table does not exist"; + case ENOENT: + return "Anchor or Ruleset does not exist"; + default: + return strerror(errnum); + } +} + +static int pfr_add_tables(struct pfr_table *tbl, int size, int *nadd, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && tbl == NULL)) + { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRADDTABLES, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + return (0); +} + +static int fill_addr(const struct all_addr *ipaddr, int flags, struct pfr_addr* addr) { + if ( !addr || !ipaddr) + { + my_syslog(LOG_ERR, _("error: fill_addr missused")); + return -1; + } + bzero(addr, sizeof(*addr)); +#ifdef HAVE_IPV6 + if (flags & F_IPV6) + { + addr->pfra_af = AF_INET6; + addr->pfra_net = 0x80; + memcpy(&(addr->pfra_ip6addr), &(ipaddr->addr), sizeof(struct in6_addr)); + } + else +#endif + { + addr->pfra_af = AF_INET; + addr->pfra_net = 0x20; + addr->pfra_ip4addr.s_addr = ipaddr->addr.addr4.s_addr; + } + return 1; +} + +/*****************************************************************************/ + +void ipset_init(void) +{ + dev = open( pf_device, O_RDWR); + if (dev == -1) + { + err(1, "%s", pf_device); + die (_("failed to access pf devices: %s"), NULL, EC_MISC); + } +} + +int add_to_ipset(const char *setname, const struct all_addr *ipaddr, + int flags, int remove) +{ + struct pfr_addr addr; + struct pfioc_table io; + struct pfr_table table; + int n = 0, rc = 0; + + if ( dev == -1 ) + { + my_syslog(LOG_ERR, _("warning: no opened pf devices %s"), pf_device); + return -1; + } + + bzero(&table, sizeof(struct pfr_table)); + table.pfrt_flags |= PFR_TFLAG_PERSIST; + if ( strlen(setname) >= PF_TABLE_NAME_SIZE ) + { + my_syslog(LOG_ERR, _("error: cannot use table name %s"), setname); + errno = ENAMETOOLONG; + return -1; + } + + if ( strlcpy(table.pfrt_name, setname, + sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) + { + my_syslog(LOG_ERR, _("error: cannot strlcpy table name %s"), setname); + return -1; + } + + if ((rc = pfr_add_tables(&table, 1, &n, 0))) + { + my_syslog(LOG_WARNING, _("warning: pfr_add_tables: %s(%d)"), + pfr_strerror(errno),rc); + return -1; + } + table.pfrt_flags &= ~PFR_TFLAG_PERSIST; + if (n) + my_syslog(LOG_INFO, _("info: table created")); + + fill_addr(ipaddr,flags,&addr); + bzero(&io, sizeof(io)); + io.pfrio_flags = 0; + io.pfrio_table = table; + io.pfrio_buffer = &addr; + io.pfrio_esize = sizeof(addr); + io.pfrio_size = 1; + if (ioctl(dev, ( remove ? DIOCRDELADDRS : DIOCRADDADDRS ), &io)) + { + my_syslog(LOG_WARNING, _("warning: DIOCR%sADDRS: %s"), ( remove ? "DEL" : "ADD" ), pfr_strerror(errno)); + return -1; + } + + my_syslog(LOG_INFO, _("%d addresses %s"), + io.pfrio_nadd, ( remove ? "removed" : "added" )); + + return io.pfrio_nadd; +} + + +#endif @@ -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,12 +18,13 @@ #ifdef HAVE_TFTP -static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, int special); +static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix); static void free_transfer(struct tftp_transfer *transfer); static ssize_t tftp_err(int err, char *packet, char *mess, char *file); static ssize_t tftp_err_oops(char *packet, char *file); static ssize_t get_block(char *packet, struct tftp_transfer *transfer); static char *next(char **p, char *end); +static void sanitise(char *buf); #define OP_RRQ 1 #define OP_WRQ 2 @@ -47,22 +48,24 @@ void tftp_request(struct listener *listen, time_t now) struct msghdr msg; struct iovec iov; struct ifreq ifr; - int is_err = 1, if_index = 0, mtu = 0, special = 0; -#ifdef HAVE_DHCP + int is_err = 1, if_index = 0, mtu = 0; struct iname *tmp; -#endif struct tftp_transfer *transfer; int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */ #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) int mtuflag = IP_PMTUDISC_DONT; #endif char namebuff[IF_NAMESIZE]; - char pretty_addr[ADDRSTRLEN]; - char *name; + char *name = NULL; char *prefix = daemon->tftp_prefix; struct tftp_prefix *pref; - struct interface_list *ir; - + struct all_addr addra; +#ifdef HAVE_IPV6 + /* Can always get recvd interface for IPv6 */ + int check_dest = !option_bool(OPT_NOWILD) || listen->family == AF_INET6; +#else + int check_dest = !option_bool(OPT_NOWILD); +#endif union { struct cmsghdr align; /* this ensures alignment */ #ifdef HAVE_IPV6 @@ -93,18 +96,28 @@ void tftp_request(struct listener *listen, time_t now) if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) return; - - if (option_bool(OPT_NOWILD)) + + /* Can always get recvd interface for IPv6 */ + if (!check_dest) { - addr = listen->iface->addr; - mtu = listen->iface->mtu; - name = listen->iface->name; + if (listen->iface) + { + addr = listen->iface->addr; + mtu = listen->iface->mtu; + name = listen->iface->name; + } + else + { + /* we're listening on an address that doesn't appear on an interface, + ask the kernel what the socket is bound to */ + socklen_t tcp_len = sizeof(union mysockaddr); + if (getsockname(listen->tftpfd, (struct sockaddr *)&addr, &tcp_len) == -1) + return; + } } else { struct cmsghdr *cmptr; - int check; - struct interface_list *ir; if (msg.msg_controllen < sizeof(struct cmsghdr)) return; @@ -114,7 +127,7 @@ void tftp_request(struct listener *listen, time_t now) #if defined(HAVE_LINUX_NETWORK) if (listen->family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) - if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { union { unsigned char *c; @@ -163,7 +176,7 @@ void tftp_request(struct listener *listen, time_t now) if (listen->family == AF_INET6) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) - if (cmptr->cmsg_level == IPV6_LEVEL && cmptr->cmsg_type == daemon->v6pktinfo) + if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) { union { unsigned char *c; @@ -181,28 +194,40 @@ void tftp_request(struct listener *listen, time_t now) return; name = namebuff; + + addra.addr.addr4 = addr.in.sin_addr; #ifdef HAVE_IPV6 if (listen->family == AF_INET6) - check = iface_check(AF_INET6, (struct all_addr *)&addr.in6.sin6_addr, name, &if_index); - else + addra.addr.addr6 = addr.in6.sin6_addr; #endif - check = iface_check(AF_INET, (struct all_addr *)&addr.in.sin_addr, name, &if_index); - - /* wierd TFTP service override */ - for (ir = daemon->tftp_interfaces; ir; ir = ir->next) - if (strcmp(ir->interface, name) == 0) - break; - - if (!ir) + + if (daemon->tftp_interfaces) { - if (!daemon->tftp_unlimited || !check) + /* dedicated tftp interface list */ + for (tmp = daemon->tftp_interfaces; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, name)) + break; + + if (!tmp) return; + } + else + { + /* Do the same as DHCP */ + if (!iface_check(listen->family, &addra, name, NULL)) + { + if (!option_bool(OPT_CLEVERBIND)) + enumerate_interfaces(0); + if (!loopback_exception(listen->tftpfd, listen->family, &addra, name) && + !label_exception(if_index, listen->family, &addra) ) + return; + } #ifdef HAVE_DHCP /* allowed interfaces are the same as for DHCP */ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) - if (tmp->name && (strcmp(tmp->name, name) == 0)) + if (tmp->name && wildcard_match(tmp->name, name)) return; #endif } @@ -211,28 +236,31 @@ void tftp_request(struct listener *listen, time_t now) if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1) mtu = ifr.ifr_mtu; } - - /* check for per-interface prefix */ - for (pref = daemon->if_prefix; pref; pref = pref->next) - if (strcmp(pref->interface, name) == 0) - prefix = pref->prefix; - /* wierd TFTP interfaces disable special options. */ - for (ir = daemon->tftp_interfaces; ir; ir = ir->next) - if (strcmp(ir->interface, name) == 0) - special = 1; + if (name) + { + /* check for per-interface prefix */ + for (pref = daemon->if_prefix; pref; pref = pref->next) + if (strcmp(pref->interface, name) == 0) + prefix = pref->prefix; + } + if (listen->family == AF_INET) + { + addr.in.sin_port = htons(port); #ifdef HAVE_SOCKADDR_SA_LEN - addr.sa.sa_len = sa_len(&addr); + addr.in.sin_len = sizeof(addr.in); #endif - - if (listen->family == AF_INET) - addr.in.sin_port = htons(port); + } #ifdef HAVE_IPV6 else { addr.in6.sin6_port = htons(port); addr.in6.sin6_flowinfo = 0; + addr.in6.sin6_scope_id = 0; +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(addr.in6); +#endif } #endif @@ -255,14 +283,14 @@ void tftp_request(struct listener *listen, time_t now) transfer->opt_blocksize = transfer->opt_transize = 0; transfer->netascii = transfer->carrylf = 0; - prettyprint_addr(&peer, pretty_addr); + prettyprint_addr(&peer, daemon->addrbuff); /* if we have a nailed-down range, iterate until we find a free one. */ while (1) { - if (bind(transfer->sockfd, &addr.sa, sizeof(addr)) == -1 || + if (bind(transfer->sockfd, &addr.sa, sa_len(&addr)) == -1 || #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) - setsockopt(transfer->sockfd, SOL_IP, IP_MTU_DISCOVER, &mtuflag, sizeof(mtuflag)) == -1 || + setsockopt(transfer->sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &mtuflag, sizeof(mtuflag)) == -1 || #endif !fix_fd(transfer->sockfd)) { @@ -293,7 +321,10 @@ void tftp_request(struct listener *listen, time_t now) !(filename = next(&p, end)) || !(mode = next(&p, end)) || (strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0)) - len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), pretty_addr); + { + len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), daemon->addrbuff); + is_err = 1; + } else { if (strcasecmp(mode, "netascii") == 0) @@ -303,8 +334,7 @@ void tftp_request(struct listener *listen, time_t now) { if (strcasecmp(opt, "blksize") == 0) { - if ((opt = next(&p, end)) && - (special || !option_bool(OPT_TFTP_NOBLOCK))) + if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK)) { transfer->blocksize = atoi(opt); if (transfer->blocksize < 1) @@ -326,9 +356,12 @@ void tftp_request(struct listener *listen, time_t now) } /* cope with backslashes from windows boxen. */ - while ((p = strchr(filename, '\\'))) - *p = '/'; - + for (p = filename; *p; p++) + if (*p == '\\') + *p = '/'; + else if (option_bool(OPT_TFTP_LC)) + *p = tolower(*p); + strcpy(daemon->namebuff, "/"); if (prefix) { @@ -338,12 +371,12 @@ void tftp_request(struct listener *listen, time_t now) if (prefix[strlen(prefix)-1] != '/') strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); - if (!special && option_bool(OPT_TFTP_APREF)) + if (option_bool(OPT_TFTP_APREF)) { size_t oldlen = strlen(daemon->namebuff); struct stat statbuf; - strncat(daemon->namebuff, pretty_addr, (MAXDNAME-1) - strlen(daemon->namebuff)); + strncat(daemon->namebuff, daemon->addrbuff, (MAXDNAME-1) - strlen(daemon->namebuff)); strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); /* remove unique-directory if it doesn't exist */ @@ -365,7 +398,7 @@ void tftp_request(struct listener *listen, time_t now) strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff)); /* check permissions and open file */ - if ((transfer->file = check_tftp_fileperm(&len, prefix, special))) + if ((transfer->file = check_tftp_fileperm(&len, prefix))) { if ((len = get_block(packet, transfer)) == -1) len = tftp_err_oops(packet, daemon->namebuff); @@ -375,7 +408,7 @@ void tftp_request(struct listener *listen, time_t now) } while (sendto(transfer->sockfd, packet, len, 0, - (struct sockaddr *)&peer, sizeof(peer)) == -1 && errno == EINTR); + (struct sockaddr *)&peer, sa_len(&peer)) == -1 && errno == EINTR); if (is_err) free_transfer(transfer); @@ -386,7 +419,7 @@ void tftp_request(struct listener *listen, time_t now) } } -static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, int special) +static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix) { char *packet = daemon->packet, *namebuff = daemon->namebuff; struct tftp_file *file; @@ -423,7 +456,7 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, int spe goto perm; } /* in secure mode, must be owned by user running dnsmasq */ - else if (!special && option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid) + else if (option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid) goto perm; /* If we're doing many tranfers from the same file, only @@ -469,11 +502,10 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, int spe return NULL; } -void check_tftp_listeners(fd_set *rset, time_t now) +void check_tftp_listeners(time_t now) { struct tftp_transfer *transfer, *tmp, **up; ssize_t len; - char pretty_addr[ADDRSTRLEN]; struct ack { unsigned short op, block; @@ -484,12 +516,12 @@ void check_tftp_listeners(fd_set *rset, time_t now) { tmp = transfer->next; - if (FD_ISSET(transfer->sockfd, rset)) + prettyprint_addr(&transfer->peer, daemon->addrbuff); + + if (poll_check(transfer->sockfd, POLLIN)) { /* we overwrote the buffer... */ daemon->srv_save = NULL; - - prettyprint_addr(&transfer->peer, pretty_addr); if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack)) { @@ -511,17 +543,11 @@ void check_tftp_listeners(fd_set *rset, time_t now) if (!err) err = ""; else - { - unsigned char *q, *r; - for (q = r = (unsigned char *)err; *r; r++) - if (isprint(*r)) - *(q++) = *r; - *q = 0; - } - + sanitise(err); + my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"), (int)ntohs(mess->block), err, - pretty_addr); + daemon->addrbuff); /* Got err, ensure we take abort */ transfer->timeout = now; @@ -545,30 +571,33 @@ void check_tftp_listeners(fd_set *rset, time_t now) len = tftp_err_oops(daemon->packet, transfer->file->filename); endcon = 1; } - else if (++transfer->backoff > 5) + /* don't complain about timeout when we're awaiting the last + ACK, some clients never send it */ + else if (++transfer->backoff > 7 && len != 0) { - /* don't complain about timeout when we're awaiting the last - ACK, some clients never send it */ - if (len != 0) - { - my_syslog(MS_TFTP | LOG_ERR, _("failed sending %s to %s"), - transfer->file->filename, pretty_addr); - len = 0; - endcon = 1; - } + endcon = 1; + len = 0; } - + if (len != 0) while(sendto(transfer->sockfd, daemon->packet, len, 0, - (struct sockaddr *)&transfer->peer, sizeof(transfer->peer)) == -1 && errno == EINTR); + (struct sockaddr *)&transfer->peer, sa_len(&transfer->peer)) == -1 && errno == EINTR); if (endcon || len == 0) { - if (!endcon) - my_syslog(MS_TFTP | LOG_INFO, _("sent %s to %s"), transfer->file->filename, pretty_addr); + strcpy(daemon->namebuff, transfer->file->filename); + sanitise(daemon->namebuff); + my_syslog(MS_TFTP | LOG_INFO, endcon ? _("failed sending %s to %s") : _("sent %s to %s"), daemon->namebuff, daemon->addrbuff); /* unlink */ *up = tmp; - free_transfer(transfer); + if (endcon) + free_transfer(transfer); + else + { + /* put on queue to be sent to script and deleted */ + transfer->next = daemon->tftp_done_trans; + daemon->tftp_done_trans = transfer; + } continue; } } @@ -602,6 +631,16 @@ static char *next(char **p, char *end) return ret; } +static void sanitise(char *buf) +{ + unsigned char *q, *r; + for (q = r = (unsigned char *)buf; *r; r++) + if (isprint((int)*r)) + *(q++) = *r; + *q = 0; + +} + static ssize_t tftp_err(int err, char *packet, char *message, char *file) { struct errmess { @@ -610,7 +649,9 @@ static ssize_t tftp_err(int err, char *packet, char *message, char *file) } *mess = (struct errmess *)packet; ssize_t ret = 4; char *errstr = strerror(errno); - + + sanitise(file); + mess->op = htons(OP_ERR); mess->err = htons(err); ret += (snprintf(mess->message, 500, message, file, errstr) + 1); @@ -621,7 +662,9 @@ static ssize_t tftp_err(int err, char *packet, char *message, char *file) static ssize_t tftp_err_oops(char *packet, char *file) { - return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), file); + /* May have >1 refs to file, so potentially mangle a copy of the name */ + strcpy(daemon->namebuff, file); + return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), daemon->namebuff); } /* return -1 for error, zero for done. */ @@ -685,15 +728,13 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer) for (i = 0, newcarrylf = 0; i < size; i++) if (mess->data[i] == '\n' && ( i != 0 || !transfer->carrylf)) { - if (size == transfer->blocksize) - { - transfer->expansion++; - if (i == size - 1) - newcarrylf = 1; /* don't expand LF again if it moves to the next block */ - } - else + transfer->expansion++; + + if (size != transfer->blocksize) size++; /* room in this block */ - + else if (i == size - 1) + newcarrylf = 1; /* don't expand LF again if it moves to the next block */ + /* make space and insert CR */ memmove(&mess->data[i+1], &mess->data[i], size - (i + 1)); mess->data[i] = '\r'; @@ -708,4 +749,21 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer) } } + +int do_tftp_script_run(void) +{ + struct tftp_transfer *transfer; + + if ((transfer = daemon->tftp_done_trans)) + { + daemon->tftp_done_trans = transfer->next; +#ifdef HAVE_SCRIPT + queue_tftp(transfer->file->size, transfer->file->filename, &transfer->peer); +#endif + free_transfer(transfer); + return 1; + } + + return 0; +} #endif @@ -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 @@ -28,24 +28,12 @@ #include <idna.h> #endif -#ifdef HAVE_ARC4RANDOM -void rand_init(void) -{ - return; -} - -unsigned short rand16(void) -{ - return (unsigned short) (arc4random() >> 15); -} - -#else - /* SURF random number generator */ static u32 seed[32]; static u32 in[12]; static u32 out[8]; +static int outleft = 0; void rand_init() { @@ -83,19 +71,44 @@ static void surf(void) unsigned short rand16(void) { + if (!outleft) + { + if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3]; + surf(); + outleft = 8; + } + + return (unsigned short) out[--outleft]; +} + +u32 rand32(void) +{ + if (!outleft) + { + if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3]; + surf(); + outleft = 8; + } + + return out[--outleft]; +} + +u64 rand64(void) +{ static int outleft = 0; - if (!outleft) { - if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3]; - surf(); - outleft = 8; - } + if (outleft < 2) + { + if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3]; + surf(); + outleft = 8; + } + + outleft -= 2; - return (unsigned short) out[--outleft]; + return (u64)out[outleft+1] + (((u64)out[outleft]) << 32); } -#endif - static int check_name(char *in) { /* remove trailing . @@ -108,10 +121,10 @@ static int check_name(char *in) if (in[l-1] == '.') { - if (l == 1) return 0; in[l-1] = 0; + nowhite = 1; } - + for (; (c = *in); in++) { if (c == '.') @@ -142,17 +155,20 @@ static int check_name(char *in) int legal_hostname(char *name) { char c; + int first; if (!check_name(name)) return 0; - for (; (c = *name); name++) + for (first = 1; (c = *name); name++, first = 0) /* check for legal char a-z A-Z 0-9 - _ . */ { if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9') || - c == '-' || c == '_') + (c >= '0' && c <= '9')) + continue; + + if (!first && (c == '-' || c == '_')) continue; /* end of hostname part */ @@ -210,7 +226,14 @@ unsigned char *do_rfc1035_name(unsigned char *p, char *sval) { unsigned char *cp = p++; for (j = 0; *sval && (*sval != '.'); sval++, j++) - *p++ = *sval; + { +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE) + *p++ = (*(++sval))-1; + else +#endif + *p++ = *sval; + } *cp = j; if (*sval) sval++; @@ -258,6 +281,7 @@ int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) #ifdef HAVE_IPV6 if (s1->sa.sa_family == AF_INET6 && s1->in6.sin6_port == s2->in6.sin6_port && + s1->in6.sin6_scope_id == s2->in6.sin6_scope_id && IN6_ARE_ADDR_EQUAL(&s1->in6.sin6_addr, &s2->in6.sin6_addr)) return 1; #endif @@ -280,7 +304,7 @@ int sa_len(union mysockaddr *addr) } /* don't use strcasecmp and friends here - they may be messed up by LOCALE */ -int hostname_isequal(char *a, char *b) +int hostname_isequal(const char *a, const char *b) { unsigned int c1, c2; @@ -315,11 +339,66 @@ time_t dnsmasq_time(void) #endif } +int netmask_length(struct in_addr mask) +{ + int zero_count = 0; + + while (0x0 == (mask.s_addr & 0x1) && zero_count < 32) + { + mask.s_addr >>= 1; + zero_count++; + } + + return 32 - zero_count; +} + int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask) { return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr); } +#ifdef HAVE_IPV6 +int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen) +{ + int pfbytes = prefixlen >> 3; + int pfbits = prefixlen & 7; + + if (memcmp(&a->s6_addr, &b->s6_addr, pfbytes) != 0) + return 0; + + if (pfbits == 0 || + (a->s6_addr[pfbytes] >> (8 - pfbits) == b->s6_addr[pfbytes] >> (8 - pfbits))) + return 1; + + return 0; +} + +/* return least signigicant 64 bits if IPv6 address */ +u64 addr6part(struct in6_addr *addr) +{ + int i; + u64 ret = 0; + + for (i = 8; i < 16; i++) + ret = (ret << 8) + addr->s6_addr[i]; + + return ret; +} + +void setaddr6part(struct in6_addr *addr, u64 host) +{ + int i; + + for (i = 15; i >= 8; i--) + { + addr->s6_addr[i] = host; + host = host >> 8; + } +} + +#endif + + /* returns port number from address */ int prettyprint_addr(union mysockaddr *addr, char *buf) { @@ -333,7 +412,15 @@ int prettyprint_addr(union mysockaddr *addr, char *buf) } else if (addr->sa.sa_family == AF_INET6) { + char name[IF_NAMESIZE]; inet_ntop(AF_INET6, &addr->in6.sin6_addr, buf, ADDRSTRLEN); + if (addr->in6.sin6_scope_id != 0 && + if_indextoname(addr->in6.sin6_scope_id, name) && + strlen(buf) + strlen(name) + 2 <= ADDRSTRLEN) + { + strcat(buf, "%"); + strcat(buf, name); + } port = ntohs(addr->in6.sin6_port); } #else @@ -376,7 +463,7 @@ int parse_hex(char *in, unsigned char *out, int maxlen, while (maxlen == -1 || i < maxlen) { - for (r = in; *r != 0 && *r != ':' && *r != '-'; r++) + for (r = in; *r != 0 && *r != ':' && *r != '-' && *r != ' '; r++) if (*r != '*' && !isxdigit((unsigned char)*r)) return -1; @@ -394,12 +481,29 @@ int parse_hex(char *in, unsigned char *out, int maxlen, else { *r = 0; - mask = mask << 1; if (strcmp(in, "*") == 0) - mask |= 1; + { + mask = (mask << 1) | 1; + i++; + } else - out[i] = strtol(in, NULL, 16); - i++; + { + int j, bytes = (1 + (r - in))/2; + for (j = 0; j < bytes; j++) + { + char sav = sav; + if (j < bytes - 1) + { + sav = in[(j+1)*2]; + in[(j+1)*2] = 0; + } + out[i] = strtol(&in[j*2], NULL, 16); + mask = mask << 1; + i++; + if (j < bytes - 1) + in[(j+1)*2] = sav; + } + } } } in = r+1; @@ -466,27 +570,41 @@ char *print_mac(char *buff, unsigned char *mac, int len) return buff; } -void bump_maxfd(int fd, int *max) +/* rc is return from sendto and friends. + Return 1 if we should retry. + Set errno to zero if we succeeded. */ +int retry_send(ssize_t rc) { - if (fd > *max) - *max = fd; -} + static int retries = 0; + struct timespec waiter; + + if (rc != -1) + { + retries = 0; + errno = 0; + return 0; + } + + /* Linux kernels can return EAGAIN in perpetuity when calling + sendmsg() and the relevant interface has gone. Here we loop + retrying in EAGAIN for 1 second max, to avoid this hanging + dnsmasq. */ -int retry_send(void) -{ - struct timespec waiter; - if (errno == EAGAIN) + if (errno == EAGAIN || errno == EWOULDBLOCK) { waiter.tv_sec = 0; waiter.tv_nsec = 10000; nanosleep(&waiter, NULL); - return 1; + if (retries++ < 1000) + return 1; } - - if (errno == EINTR) - return 1; - - return 0; + + retries = 0; + + if (errno == EINTR) + return 1; + + return 0; } int read_write(int fd, unsigned char *packet, int size, int rw) @@ -495,22 +613,57 @@ int read_write(int fd, unsigned char *packet, int size, int rw) for (done = 0; done < size; done += n) { - retry: - if (rw) - n = read(fd, &packet[done], (size_t)(size - done)); - else - n = write(fd, &packet[done], (size_t)(size - done)); - - if (n == 0) - return 0; - else if (n == -1) - { - if (retry_send() || errno == ENOMEM || errno == ENOBUFS) - goto retry; - else - return 0; - } + do { + if (rw) + n = read(fd, &packet[done], (size_t)(size - done)); + else + n = write(fd, &packet[done], (size_t)(size - done)); + + if (n == 0) + return 0; + + } while (retry_send(n) || errno == ENOMEM || errno == ENOBUFS); + + if (errno != 0) + return 0; } + return 1; } +/* Basically match a string value against a wildcard pattern. */ +int wildcard_match(const char* wildcard, const char* match) +{ + while (*wildcard && *match) + { + if (*wildcard == '*') + return 1; + + if (*wildcard != *match) + return 0; + + ++wildcard; + ++match; + } + + return *wildcard == *match; +} + +/* The same but comparing a maximum of NUM characters, like strncmp. */ +int wildcard_matchn(const char* wildcard, const char* match, int num) +{ + while (*wildcard && *match && num) + { + if (*wildcard == '*') + return 1; + + if (*wildcard != *match) + return 0; + + ++wildcard; + ++match; + --num; + } + + return (!num) || (*wildcard == *match); +} |