--- embedaddon/dnsmasq/src/option.c 2021/03/17 00:56:46 1.1.1.4 +++ embedaddon/dnsmasq/src/option.c 2023/09/27 11:02:07 1.1.1.5 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2022 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 @@ -168,7 +168,25 @@ struct myoption { #define LOPT_SINGLE_PORT 359 #define LOPT_SCRIPT_TIME 360 #define LOPT_PXE_VENDOR 361 - +#define LOPT_DYNHOST 362 +#define LOPT_LOG_DEBUG 363 +#define LOPT_UMBRELLA 364 +#define LOPT_CMARK_ALST_EN 365 +#define LOPT_CMARK_ALST 366 +#define LOPT_QUIET_TFTP 367 +#define LOPT_NFTSET 368 +#define LOPT_FILTER_A 369 +#define LOPT_FILTER_AAAA 370 +#define LOPT_STRIP_SBNET 371 +#define LOPT_STRIP_MAC 372 +#define LOPT_CONF_OPT 373 +#define LOPT_CONF_SCRIPT 374 +#define LOPT_RANDPORT_LIM 375 +#define LOPT_FAST_RETRY 376 +#define LOPT_STALE_CACHE 377 +#define LOPT_NORR 378 +#define LOPT_NO_IDENT 379 + #ifdef HAVE_GETOPT_LONG static const struct option opts[] = #else @@ -205,6 +223,8 @@ static const struct myoption opts[] = { "ignore-address", 1, 0, LOPT_IGNORE_ADDR }, { "selfmx", 0, 0, 'e' }, { "filterwin2k", 0, 0, 'f' }, + { "filter-A", 0, 0, LOPT_FILTER_A }, + { "filter-AAAA", 0, 0, LOPT_FILTER_AAAA }, { "pid-file", 2, 0, 'x' }, { "strict-order", 0, 0, 'o' }, { "server", 1, 0, 'S' }, @@ -212,11 +232,13 @@ static const struct myoption opts[] = { "local", 1, 0, LOPT_LOCAL }, { "address", 1, 0, 'A' }, { "conf-file", 2, 0, 'C' }, + { "conf-script", 1, 0, LOPT_CONF_SCRIPT }, { "no-resolv", 0, 0, 'R' }, { "expand-hosts", 0, 0, 'E' }, { "localmx", 0, 0, 'L' }, { "local-ttl", 1, 0, 'T' }, { "no-negcache", 0, 0, 'N' }, + { "no-round-robin", 0, 0, LOPT_NORR }, { "addn-hosts", 1, 0, 'H' }, { "hostsdir", 1, 0, LOPT_HOST_INOTIFY }, { "query-port", 1, 0, 'Q' }, @@ -303,7 +325,9 @@ static const struct myoption opts[] = { "dhcp-generate-names", 2, 0, LOPT_GEN_NAMES }, { "rebind-localhost-ok", 0, 0, LOPT_LOC_REBND }, { "add-mac", 2, 0, LOPT_ADD_MAC }, + { "strip-mac", 0, 0, LOPT_STRIP_MAC }, { "add-subnet", 2, 0, LOPT_ADD_SBNET }, + { "strip-subnet", 0, 0, LOPT_STRIP_SBNET }, { "add-cpe-id", 1, 0 , LOPT_CPE_ID }, { "proxy-dnssec", 0, 0, LOPT_DNSSEC }, { "dhcp-sequential-ip", 0, 0, LOPT_INCR_ADDR }, @@ -321,6 +345,9 @@ static const struct myoption opts[] = { "auth-sec-servers", 1, 0, LOPT_AUTHSFS }, { "auth-peer", 1, 0, LOPT_AUTHPEER }, { "ipset", 1, 0, LOPT_IPSET }, + { "nftset", 1, 0, LOPT_NFTSET }, + { "connmark-allowlist-enable", 2, 0, LOPT_CMARK_ALST_EN }, + { "connmark-allowlist", 1, 0, LOPT_CMARK_ALST }, { "synth-domain", 1, 0, LOPT_SYNTH }, { "dnssec", 0, 0, LOPT_SEC_VALID }, { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR }, @@ -341,6 +368,14 @@ static const struct myoption opts[] = { "dumpfile", 1, 0, LOPT_DUMPFILE }, { "dumpmask", 1, 0, LOPT_DUMPMASK }, { "dhcp-ignore-clid", 0, 0, LOPT_IGNORE_CLID }, + { "dynamic-host", 1, 0, LOPT_DYNHOST }, + { "log-debug", 0, 0, LOPT_LOG_DEBUG }, + { "umbrella", 2, 0, LOPT_UMBRELLA }, + { "quiet-tftp", 0, 0, LOPT_QUIET_TFTP }, + { "port-limit", 1, 0, LOPT_RANDPORT_LIM }, + { "fast-dns-retry", 2, 0, LOPT_FAST_RETRY }, + { "use-stale-cache", 2, 0 , LOPT_STALE_CACHE }, + { "no-ident", 0, 0, LOPT_NO_IDENT }, { NULL, 0, 0, 0 } }; @@ -368,6 +403,8 @@ static struct { { '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 }, + { LOPT_FILTER_A, OPT_FILTER_A, NULL, gettext_noop("Don't include IPv4 addresses in DNS answers."), NULL }, + { LOPT_FILTER_AAAA, OPT_FILTER_AAAA, NULL, gettext_noop("Don't include IPv6 addresses in DNS answers."), NULL }, { 'F', ARG_DUP, ",...", gettext_noop("Enable DHCP in the range given with lease duration."), NULL }, { 'g', ARG_ONE, "", gettext_noop("Change to this group after startup (defaults to %s)."), CHGRP }, { 'G', ARG_DUP, "", gettext_noop("Set address or hostname for a specified machine."), NULL }, @@ -396,6 +433,7 @@ static struct { { 'M', ARG_DUP, "", 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 }, + { LOPT_STALE_CACHE, ARG_ONE, "[=]", gettext_noop("Use expired cache data for faster reply."), NULL }, { 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE }, { 'O', ARG_DUP, "", gettext_noop("Specify options to be sent to DHCP clients."), NULL }, { LOPT_FORCE, ARG_DUP, "", gettext_noop("DHCP option sent even if the client does not request it."), NULL}, @@ -403,6 +441,7 @@ static struct { { 'P', ARG_ONE, "", 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, "", gettext_noop("Force the originating port for upstream DNS queries."), NULL }, + { LOPT_RANDPORT_LIM, ARG_ONE, "#ports", gettext_noop("Set maximum number of random originating ports for a query."), NULL }, { 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL }, { 'r', ARG_DUP, "", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE }, { LOPT_SERVERS_FILE, ARG_ONE, "", gettext_noop("Specify path to file with server= options"), NULL }, @@ -416,6 +455,7 @@ static struct { { LOPT_MAXTTL, ARG_ONE, "", gettext_noop("Specify time-to-live in seconds for maximum TTL to send to clients."), NULL }, { LOPT_MAXCTTL, ARG_ONE, "", gettext_noop("Specify time-to-live ceiling for cache."), NULL }, { LOPT_MINCTTL, ARG_ONE, "", gettext_noop("Specify time-to-live floor for cache."), NULL }, + { LOPT_FAST_RETRY, ARG_ONE, "", gettext_noop("Retry DNS queries after this many milliseconds."), NULL}, { 'u', ARG_ONE, "", gettext_noop("Change to this user after startup. (defaults to %s)."), CHUSER }, { 'U', ARG_DUP, "set:,", gettext_noop("Map DHCP vendor class to tag."), NULL }, { 'v', 0, NULL, gettext_noop("Display dnsmasq version and copyright information."), NULL }, @@ -443,6 +483,7 @@ static struct { { LOPT_SCRIPTUSR, ARG_ONE, "", gettext_noop("Run lease-change scripts as this user."), NULL }, { LOPT_SCRIPT_ARP, OPT_SCRIPT_ARP, NULL, gettext_noop("Call dhcp-script with changes to local ARP table."), NULL }, { '7', ARG_DUP, "", gettext_noop("Read configuration from all the files in this directory."), NULL }, + { LOPT_CONF_SCRIPT, ARG_DUP, "", gettext_noop("Execute file and read configuration from stdin."), NULL }, { '8', ARG_ONE, "|", 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, "", gettext_noop("Maximum number of concurrent DNS queries. (defaults to %s)"), "!" }, @@ -481,7 +522,9 @@ static struct { { LOPT_PXE_SERV, ARG_DUP, "", gettext_noop("Boot service for PXE menu."), NULL }, { LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL }, { LOPT_ADD_MAC, ARG_DUP, "[=base64|text]", gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL }, + { LOPT_STRIP_MAC, OPT_STRIP_MAC, NULL, gettext_noop("Strip MAC information from queries."), NULL }, { LOPT_ADD_SBNET, ARG_ONE, "[,]", gettext_noop("Add specified IP subnet to forwarded DNS queries."), NULL }, + { LOPT_STRIP_SBNET, OPT_STRIP_ECS, NULL, gettext_noop("Strip ECS information from queries."), NULL }, { LOPT_CPE_ID, ARG_ONE, "", gettext_noop("Add client identification 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 }, @@ -491,6 +534,7 @@ static struct { { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL }, { LOPT_DUID, ARG_ONE, ",", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, { LOPT_HOST_REC, ARG_DUP, ",
[,]", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, + { LOPT_DYNHOST, ARG_DUP, ",[][,],", gettext_noop("Specify host record in interface subnet"), NULL }, { LOPT_CAA, ARG_DUP, ",,,", gettext_noop("Specify certification authority authorization record"), NULL }, { LOPT_RR, ARG_DUP, ",,[]", 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 }, @@ -501,6 +545,9 @@ static struct { { LOPT_AUTHSFS, ARG_DUP, "[,...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL }, { LOPT_AUTHPEER, ARG_DUP, "[,...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL }, { LOPT_IPSET, ARG_DUP, "/[/...]/...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL }, + { LOPT_NFTSET, ARG_DUP, "/[/...]/...", gettext_noop("Specify nftables sets to which matching domains should be added"), NULL }, + { LOPT_CMARK_ALST_EN, ARG_ONE, "[=]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL }, + { LOPT_CMARK_ALST, ARG_DUP, "[/][,[/...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL }, { LOPT_SYNTH, ARG_DUP, ",,[]", 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, ",[],...", gettext_noop("Specify trust anchor key digest."), NULL }, @@ -512,6 +559,7 @@ static struct { { 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_LOG_DEBUG, OPT_LOG_DEBUG, NULL, gettext_noop("Log debugging information."), 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, "", gettext_noop("Ignore DNS responses containing ipaddr."), NULL }, @@ -521,6 +569,10 @@ static struct { { LOPT_DUMPFILE, ARG_ONE, "", gettext_noop("Path to debug packet dump file"), NULL }, { LOPT_DUMPMASK, ARG_ONE, "", gettext_noop("Mask which packets to dump"), NULL }, { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL }, + { LOPT_UMBRELLA, ARG_ONE, "[=]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL }, + { LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL }, + { LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL }, + { LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL }, { 0, 0, NULL, NULL, NULL } }; @@ -635,6 +687,9 @@ static char *canonicalise_opt(char *s) if (!s) return 0; + if (strlen(s) == 0) + return opt_malloc(1); /* Heap-allocated empty string */ + unhide_metas(s); if (!(ret = canonicalise(s, &nomem)) && nomem) { @@ -647,7 +702,7 @@ static char *canonicalise_opt(char *s) return ret; } -static int atoi_check(char *a, int *res) +static int numeric_check(char *a) { char *p; @@ -660,10 +715,32 @@ static int atoi_check(char *a, int *res) if (*p < '0' || *p > '9') return 0; + return 1; +} + +static int atoi_check(char *a, int *res) +{ + if (!numeric_check(a)) + return 0; *res = atoi(a); return 1; } +static int strtoul_check(char *a, u32 *res) +{ + unsigned long x; + + if (!numeric_check(a)) + return 0; + x = strtoul(a, NULL, 10); + if (errno || x > UINT32_MAX) { + errno = 0; + return 0; + } + *res = (u32)x; + return 1; +} + static int atoi_check16(char *a, int *res) { if (!(atoi_check(a, res)) || @@ -755,7 +832,7 @@ static void do_usage(void) if (usage[i].arg) { - strcpy(buff, usage[i].arg); + safe_strncpy(buff, usage[i].arg, sizeof(buff)); for (j = 0; tab[j].handle; j++) if (tab[j].handle == *(usage[i].arg)) sprintf(buff, "%d", tab[j].val); @@ -781,174 +858,413 @@ static char *parse_mysockaddr(char *arg, union mysocka return NULL; } -char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, int *flags) +char *parse_server(char *arg, struct server_details *sdetails) { - int source_port = 0, serv_port = NAMESERVER_PORT; - char *portno, *source; - char *interface_opt = NULL; - int scope_index = 0; - char *scope_id; + sdetails->serv_port = NAMESERVER_PORT; + char *portno; + int ecode = 0; + struct addrinfo hints; + + memset(&hints, 0, sizeof(struct addrinfo)); - if (!arg || strlen(arg) == 0) + *sdetails->interface = 0; + sdetails->addr_type = AF_UNSPEC; + + if (strcmp(arg, "#") == 0) { - *flags |= SERV_NO_ADDR; - *interface = 0; + if (sdetails->flags) + *sdetails->flags |= SERV_USE_RESOLV; + sdetails->addr_type = AF_LOCAL; + sdetails->valid = 1; return NULL; } - - if ((source = split_chr(arg, '@')) && /* is there a source. */ - (portno = split_chr(source, '#')) && - !atoi_check16(portno, &source_port)) + + if ((sdetails->source = split_chr(arg, '@')) && /* is there a source. */ + (portno = split_chr(sdetails->source, '#')) && + !atoi_check16(portno, &sdetails->source_port)) return _("bad port"); if ((portno = split_chr(arg, '#')) && /* is there a port no. */ - !atoi_check16(portno, &serv_port)) + !atoi_check16(portno, &sdetails->serv_port)) return _("bad port"); - scope_id = split_chr(arg, '%'); + sdetails->scope_id = split_chr(arg, '%'); - if (source) { - interface_opt = split_chr(source, '@'); + if (sdetails->source) { + sdetails->interface_opt = split_chr(sdetails->source, '@'); - if (interface_opt) + if (sdetails->interface_opt) { #if defined(SO_BINDTODEVICE) - safe_strncpy(interface, interface_opt, IF_NAMESIZE); + safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE); + sdetails->source = sdetails->interface_opt; #else return _("interface binding not supported"); #endif } } - if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0) + if (inet_pton(AF_INET, arg, &sdetails->addr->in.sin_addr) > 0) + sdetails->addr_type = AF_INET; + else if (inet_pton(AF_INET6, arg, &sdetails->addr->in6.sin6_addr) > 0) + sdetails->addr_type = AF_INET6; + else { - addr->in.sin_port = htons(serv_port); - addr->sa.sa_family = source_addr->sa.sa_family = AF_INET; + /* if the argument is neither an IPv4 not an IPv6 address, it might be a + hostname and we should try to resolve it to a suitable address. */ + memset(&hints, 0, sizeof(hints)); + /* The AI_ADDRCONFIG flag ensures that then IPv4 addresses are returned in + the result only if the local system has at least one IPv4 address + configured, and IPv6 addresses are returned only if the local system + has at least one IPv6 address configured. The loopback address is not + considered for this case as valid as a configured address. This flag is + useful on, for example, IPv4-only systems, to ensure that getaddrinfo() + does not return IPv6 socket addresses that would always fail in + subsequent connect() or bind() attempts. */ + hints.ai_flags = AI_ADDRCONFIG; +#if defined(HAVE_IDN) && defined(AI_IDN) + /* If the AI_IDN flag is specified and we have glibc 2.3.4 or newer, then + the node name given in node is converted to IDN format if necessary. + The source encoding is that of the current locale. */ + hints.ai_flags |= AI_IDN; +#endif + /* The value AF_UNSPEC indicates that getaddrinfo() should return socket + addresses for any address family (either IPv4 or IPv6, for example) + that can be used with node and service "domain". */ + hints.ai_family = AF_UNSPEC; + + /* Get addresses suitable for sending datagrams. We assume that we can use the + same addresses for TCP connections. Settting this to zero gets each address + threes times, for SOCK_STREAM, SOCK_RAW and SOCK_DGRAM, which is not useful. */ + hints.ai_socktype = SOCK_DGRAM; + + /* Get address associated with this hostname */ + ecode = getaddrinfo(arg, NULL, &hints, &sdetails->hostinfo); + if (ecode == 0) + { + /* The getaddrinfo() function allocated and initialized a linked list of + addrinfo structures, one for each network address that matches node + and service, subject to the restrictions imposed by our + above, and returns a pointer to the start of the list in . + The items in the linked list are linked by the field. */ + sdetails->valid = 1; + sdetails->orig_hostinfo = sdetails->hostinfo; + return NULL; + } + else + { + /* Lookup failed, return human readable error string */ + if (ecode == EAI_AGAIN) + return _("Cannot resolve server name"); + else + return _((char*)gai_strerror(ecode)); + } + } + + sdetails->valid = 1; + return NULL; +} + +char *parse_server_addr(struct server_details *sdetails) +{ + if (sdetails->addr_type == AF_INET) + { + sdetails->addr->in.sin_port = htons(sdetails->serv_port); + sdetails->addr->sa.sa_family = sdetails->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); + sdetails->source_addr->in.sin_len = sdetails->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); + sdetails->source_addr->in.sin_addr.s_addr = INADDR_ANY; + sdetails->source_addr->in.sin_port = htons(daemon->query_port); - if (source) + if (sdetails->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 (sdetails->flags) + *sdetails->flags |= SERV_HAS_SOURCE; + sdetails->source_addr->in.sin_port = htons(sdetails->source_port); + if (inet_pton(AF_INET, sdetails->source, &sdetails->source_addr->in.sin_addr) == 0) { + if (inet_pton(AF_INET6, sdetails->source, &sdetails->source_addr->in6.sin6_addr) == 1) + { + sdetails->source_addr->sa.sa_family = AF_INET6; + /* When resolving a server IP by hostname, we can simply skip mismatching + server / source IP pairs. Otherwise, when an IP address is given directly, + this is a fatal error. */ + if (!sdetails->orig_hostinfo) + return _("cannot use IPv4 server address with IPv6 source address"); + } + else + { #if defined(SO_BINDTODEVICE) - if (interface_opt) - return _("interface can only be specified once"); - - source_addr->in.sin_addr.s_addr = INADDR_ANY; - safe_strncpy(interface, source, IF_NAMESIZE); + if (sdetails->interface_opt) + return _("interface can only be specified once"); + + sdetails->source_addr->in.sin_addr.s_addr = INADDR_ANY; + safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE); #else - return _("interface binding not supported"); + return _("interface binding not supported"); #endif + } } } } - else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0) + else if (sdetails->addr_type == AF_INET6) { - if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0) + if (sdetails->scope_id && (sdetails->scope_index = if_nametoindex(sdetails->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; + + sdetails->addr->in6.sin6_port = htons(sdetails->serv_port); + sdetails->addr->in6.sin6_scope_id = sdetails->scope_index; + sdetails->source_addr->in6.sin6_addr = in6addr_any; + sdetails->source_addr->in6.sin6_port = htons(daemon->query_port); + sdetails->source_addr->in6.sin6_scope_id = 0; + sdetails->addr->sa.sa_family = sdetails->source_addr->sa.sa_family = AF_INET6; + sdetails->addr->in6.sin6_flowinfo = sdetails->source_addr->in6.sin6_flowinfo = 0; #ifdef HAVE_SOCKADDR_SA_LEN - addr->in6.sin6_len = source_addr->in6.sin6_len = sizeof(addr->in6); + sdetails->addr->in6.sin6_len = sdetails->source_addr->in6.sin6_len = sizeof(sdetails->addr->in6); #endif - if (source) + if (sdetails->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 (sdetails->flags) + *sdetails->flags |= SERV_HAS_SOURCE; + sdetails->source_addr->in6.sin6_port = htons(sdetails->source_port); + if (inet_pton(AF_INET6, sdetails->source, &sdetails->source_addr->in6.sin6_addr) == 0) { + if (inet_pton(AF_INET, sdetails->source, &sdetails->source_addr->in.sin_addr) == 1) + { + sdetails->source_addr->sa.sa_family = AF_INET; + /* When resolving a server IP by hostname, we can simply skip mismatching + server / source IP pairs. Otherwise, when an IP address is given directly, + this is a fatal error. */ + if(!sdetails->orig_hostinfo) + return _("cannot use IPv6 server address with IPv4 source address"); + } + else + { #if defined(SO_BINDTODEVICE) - if (interface_opt) - return _("interface can only be specified once"); - - source_addr->in6.sin6_addr = in6addr_any; - safe_strncpy(interface, source, IF_NAMESIZE); + if (sdetails->interface_opt) + return _("interface can only be specified once"); + + sdetails->source_addr->in6.sin6_addr = in6addr_any; + safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE); #else - return _("interface binding not supported"); + return _("interface binding not supported"); #endif + } } } } - else + else if (sdetails->addr_type != AF_LOCAL) return _("bad address"); - + return NULL; } -static struct server *add_rev4(struct in_addr addr, int msize) +int parse_server_next(struct server_details *sdetails) { - struct server *serv = opt_malloc(sizeof(struct server)); - in_addr_t a = ntohl(addr.s_addr); - char *p; + /* Looping over resolved addresses? */ + if (sdetails->hostinfo) + { + /* Get address type */ + sdetails->addr_type = sdetails->hostinfo->ai_family; - memset(serv, 0, sizeof(struct server)); - p = serv->domain = opt_malloc(29); /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */ + /* Get address */ + if (sdetails->addr_type == AF_INET) + memcpy(&sdetails->addr->in.sin_addr, + &((struct sockaddr_in *) sdetails->hostinfo->ai_addr)->sin_addr, + sizeof(sdetails->addr->in.sin_addr)); + else if (sdetails->addr_type == AF_INET6) + memcpy(&sdetails->addr->in6.sin6_addr, + &((struct sockaddr_in6 *) sdetails->hostinfo->ai_addr)->sin6_addr, + sizeof(sdetails->addr->in6.sin6_addr)); - switch (msize) + /* Iterate to the next available address */ + sdetails->valid = sdetails->hostinfo->ai_next != NULL; + sdetails->hostinfo = sdetails->hostinfo->ai_next; + return 1; + } + else if (sdetails->valid) { - case 32: - p += sprintf(p, "%u.", a & 0xff); - /* fall through */ - case 24: - p += sprintf(p, "%d.", (a >> 8) & 0xff); - /* fall through */ - case 16: - p += sprintf(p, "%d.", (a >> 16) & 0xff); - /* fall through */ - case 8: - p += sprintf(p, "%d.", (a >> 24) & 0xff); - break; - default: - free(serv->domain); - free(serv); - return NULL; + /* When using an IP address, we return the address only once */ + sdetails->valid = 0; + return 1; } + /* Stop iterating here, we used all available addresses */ + return 0; +} - p += sprintf(p, "in-addr.arpa"); +static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int size) +{ + int i, j; + char *string; + int msize; + u16 flags = 0; + char domain[29]; /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */ + union mysockaddr serv_addr, source_addr; + char interface[IF_NAMESIZE+1]; + int count = 1, rem, addrbytes, addrbits; + struct server_details sdetails; + + memset(&sdetails, 0, sizeof(struct server_details)); + sdetails.addr = &serv_addr; + sdetails.source_addr = &source_addr; + sdetails.interface = interface; + sdetails.flags = &flags; + + if (!server) + flags = SERV_LITERAL_ADDRESS; + else if ((string = parse_server(server, &sdetails))) + return string; - serv->flags = SERV_HAS_DOMAIN; - serv->next = daemon->servers; - daemon->servers = serv; + if (from_file) + flags |= SERV_FROM_FILE; + + rem = size & 0x7; + addrbytes = (32 - size) >> 3; + addrbits = (32 - size) & 7; + + if (size > 32 || size < 1) + return _("bad IPv4 prefix length"); + + /* Zero out last address bits according to CIDR mask */ + ((u8 *)addr4)[3-addrbytes] &= ~((1 << addrbits)-1); + + size = size & ~0x7; + + if (rem != 0) + count = 1 << (8 - rem); + + for (i = 0; i < count; i++) + { + *domain = 0; + string = domain; + msize = size/8; + + for (j = (rem == 0) ? msize-1 : msize; j >= 0; j--) + { + int dig = ((unsigned char *)addr4)[j]; + + if (j == msize) + dig += i; + + string += sprintf(string, "%d.", dig); + } + + sprintf(string, "in-addr.arpa"); - return serv; + if (flags & SERV_LITERAL_ADDRESS) + { + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + else + { + /* Always reset server as valid here, so we can add the same upstream + server address multiple times for each x.y.z.in-addr.arpa */ + sdetails.valid = 1; + while (parse_server_next(&sdetails)) + { + if ((string = parse_server_addr(&sdetails))) + return string; + + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + if (sdetails.orig_hostinfo) + freeaddrinfo(sdetails.orig_hostinfo); + } + } + + return NULL; } -static struct server *add_rev6(struct in6_addr *addr, int msize) +static char *domain_rev6(int from_file, char *server, struct in6_addr *addr6, int size) { - 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*ip6.arpa")+1 */ + int i, j; + char *string; + int msize; + u16 flags = 0; + char domain[73]; /* strlen("32*ip6.arpa")+1 */ + union mysockaddr serv_addr, source_addr; + char interface[IF_NAMESIZE+1]; + int count = 1, rem, addrbytes, addrbits; + struct server_details sdetails; - 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"); + memset(&sdetails, 0, sizeof(struct server_details)); + sdetails.addr = &serv_addr; + sdetails.source_addr = &source_addr; + sdetails.interface = interface; + sdetails.flags = &flags; + + if (!server) + flags = SERV_LITERAL_ADDRESS; + else if ((string = parse_server(server, &sdetails))) + return string; + + if (from_file) + flags |= SERV_FROM_FILE; - serv->flags = SERV_HAS_DOMAIN; - serv->next = daemon->servers; - daemon->servers = serv; + rem = size & 0x3; + addrbytes = (128 - size) >> 3; + addrbits = (128 - size) & 7; - return serv; + if (size > 128 || size < 1) + return _("bad IPv6 prefix length"); + + /* Zero out last address bits according to CIDR mask */ + addr6->s6_addr[15-addrbytes] &= ~((1 << addrbits) - 1); + + size = size & ~0x3; + + if (rem != 0) + count = 1 << (4 - rem); + + for (i = 0; i < count; i++) + { + *domain = 0; + string = domain; + msize = size/4; + + for (j = (rem == 0) ? msize-1 : msize; j >= 0; j--) + { + int dig = ((unsigned char *)addr6)[j>>1]; + + dig = j & 1 ? dig & 15 : dig >> 4; + + if (j == msize) + dig += i; + + string += sprintf(string, "%.1x.", dig); + } + + sprintf(string, "ip6.arpa"); + + if (flags & SERV_LITERAL_ADDRESS) + { + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + else + { + /* Always reset server as valid here, so we can add the same upstream + server address multiple times for each x.y.z.ip6.arpa */ + sdetails.valid = 1; + while (parse_server_next(&sdetails)) + { + if ((string = parse_server_addr(&sdetails))) + return string; + + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + + if (sdetails.orig_hostinfo) + freeaddrinfo(sdetails.orig_hostinfo); + } + } + + return NULL; } #ifdef HAVE_DHCP @@ -1038,6 +1354,8 @@ static void dhcp_config_free(struct dhcp_config *confi if (config->flags & CONFIG_CLID) free(config->clid); + if (config->flags & CONFIG_NAME) + free(config->hostname); #ifdef HAVE_DHCP6 if (config->flags & CONFIG_ADDR6) @@ -1154,11 +1472,15 @@ static int parse_dhcp_opt(char *errstr, char *arg, int { new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7); new->flags |= DHOPT_VENDOR; + if ((new->flags & DHOPT_ENCAPSULATE) || flags == DHOPT_MATCH) + goto_err(_("inappropriate vendor:")); } else if (strstr(arg, "encap:") == arg) { new->u.encap = atoi(arg+6); new->flags |= DHOPT_ENCAPSULATE; + if ((new->flags & DHOPT_VENDOR) || flags == DHOPT_MATCH) + goto_err(_("inappropriate encap:")); } else if (strstr(arg, "vi-encap:") == arg) { @@ -1633,16 +1955,6 @@ void reset_option_bool(unsigned int opt) option_var(opt) &= ~(option_val(opt)); } -static void server_list_free(struct server *list) -{ - while (list) - { - struct server *tmp = list; - list = list->next; - free(tmp); - } -} - static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only) { int i; @@ -1695,6 +2007,17 @@ static int one_opt(int option, char *arg, char *errstr break; } + case LOPT_CONF_SCRIPT: /* --conf-script */ + { + char *file = opt_string_alloc(arg); + if (file) + { + one_file(file, LOPT_CONF_SCRIPT); + free(file); + } + break; + } + case '7': /* --conf-dir */ { DIR *dir_stream; @@ -1801,6 +2124,8 @@ static int one_opt(int option, char *arg, char *errstr new->next = li; *up = new; } + else + free(path); } @@ -1967,7 +2292,11 @@ static int one_opt(int option, char *arg, char *errstr if (!(name = canonicalise_opt(arg)) || (comma && !(target = canonicalise_opt(comma)))) - ret_err(_("bad MX name")); + { + free(name); + free(target); + ret_err(_("bad MX name")); + } new = opt_malloc(sizeof(struct mx_srv_record)); new->next = daemon->mxnames; @@ -2017,15 +2346,11 @@ static int one_opt(int option, char *arg, char *errstr 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 unsigned int hosts_index = SRC_AH; new->fname = opt_string_alloc(arg); - new->index = hosts_index++; + new->index = daemon->host_index++; new->flags = 0; if (option == 'H') { @@ -2041,21 +2366,29 @@ static int one_opt(int option, char *arg, char *errstr { 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 LOPT_DHCP_INOTIFY: /* --dhcp-hostsdir */ + case LOPT_DHOPT_INOTIFY: /* --dhcp-optsdir */ + case LOPT_HOST_INOTIFY: /* --hostsdir */ + { + struct dyndir *new = opt_malloc(sizeof(struct dyndir)); + new->dname = opt_string_alloc(arg); + new->flags = 0; + 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 LOPT_AUTHSERV: /* --auth-server */ comma = split(arg); @@ -2118,8 +2451,10 @@ static int one_opt(int option, char *arg, char *errstr comma = split(arg); new = opt_malloc(sizeof(struct auth_zone)); - new->domain = opt_string_alloc(arg); - new->subnet = NULL; + new->domain = canonicalise_opt(arg); + if (!new->domain) + ret_err_free(_("invalid auth-zone"), new); + new->subnet = NULL; new->exclude = NULL; new->interface_names = NULL; new->next = daemon->auth_zones; @@ -2203,7 +2538,7 @@ static int one_opt(int option, char *arg, char *errstr arg = comma; comma = split(arg); daemon->hostmaster = opt_string_alloc(arg); - for (cp = daemon->hostmaster; *cp; cp++) + for (cp = daemon->hostmaster; cp && *cp; cp++) if (*cp == '@') *cp = '.'; @@ -2231,12 +2566,13 @@ static int one_opt(int option, char *arg, char *errstr set_option_bool(OPT_RESOLV_DOMAIN); else { - char *d; + char *d, *d_raw = arg; comma = split(arg); - if (!(d = canonicalise_opt(arg))) + if (!(d = canonicalise_opt(d_raw))) ret_err(gen_err); else { + free(d); /* allocate this again below. */ if (comma) { struct cond_domain *new = opt_malloc(sizeof(struct cond_domain)); @@ -2244,6 +2580,7 @@ static int one_opt(int option, char *arg, char *errstr new->prefix = NULL; new->indexed = 0; + new->prefixlen = 0; unhide_metas(comma); if ((netpart = split_chr(comma, '/'))) @@ -2255,7 +2592,12 @@ static int one_opt(int option, char *arg, char *errstr ret_err_free(gen_err, new); else if (inet_pton(AF_INET, comma, &new->start)) { - int mask = (1 << (32 - msize)) - 1; + int mask; + + if (msize > 32) + ret_err_free(_("bad prefix length"), new); + + 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); @@ -2267,46 +2609,40 @@ static int one_opt(int option, char *arg, char *errstr strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN) ret_err_free(_("bad prefix"), new); } - else if (strcmp(arg, "local") != 0 || - (msize != 8 && msize != 16 && msize != 24)) + else if (strcmp(arg, "local") != 0) ret_err_free(gen_err, new); else { - /* generate the equivalent of - local=/xxx.yyy.zzz.in-addr.arpa/ */ - struct server *serv = add_rev4(new->start, msize); - if (!serv) - ret_err_free(_("bad prefix"), new); - - serv->flags |= SERV_NO_ADDR; - + /* local=/xxx.yyy.zzz.in-addr.arpa/ */ + domain_rev4(0, NULL, &new->start, msize); + /* local=// */ - 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; + /* d_raw can't failed to canonicalise here, checked above. */ + add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL); } } } else if (inet_pton(AF_INET6, comma, &new->start6)) { - u64 mask = (1LLU << (128 - msize)) - 1LLU; - u64 addrpart = addr6part(&new->start6); + u64 mask, addrpart = addr6part(&new->start6); + + if (msize > 128) + ret_err_free(_("bad prefix length"), new); + + mask = (1LLU << (128 - msize)) - 1LLU; + new->is6 = 1; + new->prefixlen = msize; /* prefix==64 overflows the mask calculation above */ - if (msize == 64) + 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_free(gen_err, new); - else if (arg) + if (arg) { if (option != 's') { @@ -2314,22 +2650,17 @@ static int one_opt(int option, char *arg, char *errstr strlen(new->prefix) > MAXLABEL - INET6_ADDRSTRLEN) ret_err_free(_("bad prefix"), new); } - else if (strcmp(arg, "local") != 0 || ((msize & 4) != 0)) + else if (strcmp(arg, "local") != 0) ret_err_free(gen_err, new); else { /* generate the equivalent of local=/xxx.yyy.zzz.ip6.arpa/ */ - struct server *serv = add_rev6(&new->start6, msize); - serv->flags |= SERV_NO_ADDR; + domain_rev6(0, NULL, &new->start6, msize); /* local=// */ - 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; + /* d_raw can't failed to canonicalise here, checked above. */ + add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL); } } } @@ -2358,9 +2689,15 @@ static int one_opt(int option, char *arg, char *errstr else if (!inet_pton(AF_INET6, arg, &new->end6)) ret_err_free(gen_err, new); } - else + else if (option == 's') + { + /* subnet from interface. */ + new->interface = opt_string_alloc(comma); + new->al = NULL; + } + else ret_err_free(gen_err, new); - + if (option != 's' && prefstr) { if (!(new->prefix = canonicalise_opt(prefstr)) || @@ -2369,7 +2706,7 @@ static int one_opt(int option, char *arg, char *errstr } } - new->domain = d; + new->domain = canonicalise_opt(d_raw); if (option == 's') { new->next = daemon->cond_domain; @@ -2378,19 +2715,21 @@ static int one_opt(int option, char *arg, char *errstr else { char *star; - new->next = daemon->synth_domains; - daemon->synth_domains = new; if (new->prefix && (star = strrchr(new->prefix, '*')) && *(star+1) == 0) { *star = 0; new->indexed = 1; + if (new->is6 && new->prefixlen < 64) + ret_err_free(_("prefix length too small"), new); } + new->next = daemon->synth_domains; + daemon->synth_domains = new; } } else if (option == 's') - daemon->domain_suffix = d; + daemon->domain_suffix = canonicalise_opt(d_raw); else ret_err(gen_err); } @@ -2402,6 +2741,50 @@ static int one_opt(int option, char *arg, char *errstr daemon->dns_client_id = opt_string_alloc(arg); break; + case LOPT_UMBRELLA: /* --umbrella */ + set_option_bool(OPT_UMBRELLA); + while (arg) + { + comma = split(arg); + if (strstr(arg, "deviceid:")) + { + char *p; + u8 *u = daemon->umbrella_device; + char word[3]; + + arg += 9; + if (strlen(arg) != 16) + ret_err(gen_err); + + for (p = arg; *p; p++) + if (!isxdigit((unsigned char)*p)) + ret_err(gen_err); + + set_option_bool(OPT_UMBRELLA_DEVID); + + for (i = 0; i < (int)sizeof(daemon->umbrella_device); i++, arg+=2) + { + memcpy(word, &(arg[0]), 2); + *u++ = strtoul(word, NULL, 16); + } + } + else if (strstr(arg, "orgid:")) + { + if (!strtoul_check(arg+6, &daemon->umbrella_org)) + ret_err(gen_err); + } + else if (strstr(arg, "assetid:")) + { + if (!strtoul_check(arg+8, &daemon->umbrella_asset)) + ret_err(gen_err); + } + else + ret_err(gen_err); + + arg = comma; + } + break; + case LOPT_ADD_MAC: /* --add-mac */ if (!arg) set_option_bool(OPT_ADD_MAC); @@ -2480,27 +2863,49 @@ static int one_opt(int option, char *arg, char *errstr case 'B': /* --bogus-nxdomain */ case LOPT_IGNORE_ADDR: /* --ignore-address */ { - struct in_addr addr; + union all_addr addr; + int prefix, is6 = 0; + struct bogus_addr *baddr; + unhide_metas(arg); - if (arg && (inet_pton(AF_INET, arg, &addr) > 0)) + + if (!arg || + ((comma = split_chr(arg, '/')) && !atoi_check(comma, &prefix))) + ret_err(gen_err); + + if (inet_pton(AF_INET6, arg, &addr.addr6) == 1) + is6 = 1; + else if (inet_pton(AF_INET, arg, &addr.addr4) != 1) + ret_err(gen_err); + + if (!comma) { - struct bogus_addr *baddr = opt_malloc(sizeof(struct bogus_addr)); - if (option == 'B') - { - baddr->next = daemon->bogus_addr; - daemon->bogus_addr = baddr; - } + if (is6) + prefix = 128; else - { - baddr->next = daemon->ignore_addr; - daemon->ignore_addr = baddr; - } - baddr->addr = addr; + prefix = 32; } + + if (prefix > 128 || (!is6 && prefix > 32)) + ret_err(gen_err); + + baddr = opt_malloc(sizeof(struct bogus_addr)); + if (option == 'B') + { + baddr->next = daemon->bogus_addr; + daemon->bogus_addr = baddr; + } else - ret_err(gen_err); /* error */ - break; - } + { + baddr->next = daemon->ignore_addr; + daemon->ignore_addr = baddr; + } + + baddr->prefix = prefix; + baddr->is6 = is6; + baddr->addr = addr; + break; + } case 'a': /* --listen-address */ case LOPT_AUTHPEER: /* --auth-peer */ @@ -2544,147 +2949,190 @@ static int one_opt(int option, char *arg, char *errstr } while (arg); break; + case LOPT_NO_REBIND: /* --rebind-domain-ok */ + { + struct rebind_domain *new; + + unhide_metas(arg); + + if (*arg == '/') + arg++; + + do { + comma = split_chr(arg, '/'); + new = opt_malloc(sizeof(struct rebind_domain)); + new->domain = canonicalise_opt(arg); + new->next = daemon->no_rebind; + daemon->no_rebind = new; + arg = comma; + } while (arg && *arg); + + break; + } + case 'S': /* --server */ case LOPT_LOCAL: /* --local */ case 'A': /* --address */ - case LOPT_NO_REBIND: /* --rebind-domain-ok */ { - struct server *serv, *newlist = NULL; - + char *lastdomain = NULL, *domain = "", *cur_domain; + u16 flags = 0; + char *err; + union all_addr addr; + union mysockaddr serv_addr, source_addr; + char interface[IF_NAMESIZE+1]; + struct server_details sdetails; + + memset(&sdetails, 0, sizeof(struct server_details)); + sdetails.addr = &serv_addr; + sdetails.source_addr = &source_addr; + sdetails.interface = interface; + sdetails.flags = &flags; + unhide_metas(arg); - if (arg && (*arg == '/' || option == LOPT_NO_REBIND)) + /* split the domain args, if any and skip to the end of them. */ + if (arg && *arg == '/') { - int rebind = !(*arg == '/'); - char *end = NULL; - if (!rebind) - arg++; - while (rebind || (end = split_chr(arg, '/'))) + char *last; + + domain = lastdomain = ++arg; + + while ((last = 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) - domain = ""; - else if (strlen (arg) != 0 && !(domain = canonicalise_opt(arg))) - ret_err(gen_err); - serv = opt_malloc(sizeof(struct server)); - memset(serv, 0, sizeof(struct server)); - serv->next = newlist; - newlist = serv; - serv->domain = domain; - serv->flags = domain ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS; - arg = end; - if (rebind) - break; + lastdomain = arg; + arg = last; } - if (!newlist) - 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') + if (!arg || !*arg) + flags = SERV_LITERAL_ADDRESS; + else if (option == 'A') { - newlist->flags |= SERV_LITERAL_ADDRESS; - if (!(newlist->flags & SERV_TYPE)) - { - server_list_free(newlist); - ret_err(gen_err); - } + /* # as literal address means return zero address for 4 and 6 */ + if (strcmp(arg, "#") == 0) + flags = SERV_ALL_ZEROS | SERV_LITERAL_ADDRESS; + else if (inet_pton(AF_INET, arg, &addr.addr4) > 0) + flags = SERV_4ADDR | SERV_LITERAL_ADDRESS; + else if (inet_pton(AF_INET6, arg, &addr.addr6) > 0) + flags = SERV_6ADDR | SERV_LITERAL_ADDRESS; + else + ret_err(_("Bad address in --address")); } - else if (option == LOPT_NO_REBIND) - newlist->flags |= SERV_NO_REBIND; - - if (!arg || !*arg) + else { - if (!(newlist->flags & SERV_NO_REBIND)) - newlist->flags |= SERV_NO_ADDR; /* no server */ + if ((err = parse_server(arg, &sdetails))) + ret_err(err); } - else if (strcmp(arg, "#") == 0) - newlist->flags |= SERV_USE_RESOLV; /* treat in ordinary way */ - else + if (servers_only && option == 'S') + flags |= SERV_FROM_FILE; + + cur_domain = domain; + while ((flags & SERV_LITERAL_ADDRESS) || parse_server_next(&sdetails)) { - char *err = parse_server(arg, &newlist->addr, &newlist->source_addr, newlist->interface, &newlist->flags); - if (err) + cur_domain = domain; + + if (!(flags & SERV_LITERAL_ADDRESS) && (err = parse_server_addr(&sdetails))) + ret_err(err); + + /* When source is set only use DNS records of the same type and skip all others */ + if (flags & SERV_HAS_SOURCE && sdetails.addr_type != sdetails.source_addr->sa.sa_family) + continue; + + while (1) { - server_list_free(newlist); - ret_err(err); + /* server=//1.2.3.4 is special. */ + if (lastdomain) + { + if (strlen(cur_domain) == 0) + flags |= SERV_FOR_NODOTS; + else + flags &= ~SERV_FOR_NODOTS; + + /* address=/#/ matches the same as without domain */ + if (option == 'A' && cur_domain[0] == '#' && cur_domain[1] == 0) + cur_domain[0] = 0; + } + + if (!add_update_server(flags, sdetails.addr, sdetails.source_addr, sdetails.interface, cur_domain, &addr)) + ret_err(gen_err); + + if (!lastdomain || cur_domain == lastdomain) + break; + + cur_domain += strlen(cur_domain) + 1; } + + if (flags & SERV_LITERAL_ADDRESS) + break; } + + if (sdetails.orig_hostinfo) + freeaddrinfo(sdetails.orig_hostinfo); - serv = newlist; - while (serv->next) - { - serv->next->flags |= serv->flags & ~(SERV_HAS_DOMAIN | SERV_FOR_NODOTS); - 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; + break; } case LOPT_REV_SERV: /* --rev-server */ { char *string; int size; - struct server *serv; struct in_addr addr4; struct in6_addr addr6; - + unhide_metas(arg); if (!arg) ret_err(gen_err); comma=split(arg); - - if (!(string = split_chr(arg, '/')) || !atoi_check(string, &size)) - ret_err(gen_err); + if (!(string = split_chr(arg, '/')) || !atoi_check(string, &size)) + size = -1; + if (inet_pton(AF_INET, arg, &addr4)) { - serv = add_rev4(addr4, size); - if (!serv) - ret_err(_("bad prefix")); + if (size == -1) + size = 32; + + if ((string = domain_rev4(servers_only, comma, &addr4, size))) + ret_err(string); } else if (inet_pton(AF_INET6, arg, &addr6)) - serv = add_rev6(&addr6, size); + { + if (size == -1) + size = 128; + + if ((string = domain_rev6(servers_only, comma, &addr6, size))) + ret_err(string); + } 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 */ + case LOPT_NFTSET: /* --nftset */ #ifndef HAVE_IPSET - ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives")); - break; -#else + if (option == LOPT_IPSET) + { + ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives")); + break; + } +#endif +#ifndef HAVE_NFTSET + if (option == LOPT_NFTSET) + { + ret_err(_("recompile with HAVE_NFTSET defined to enable nftset directives")); + break; + } +#endif + { struct ipsets ipsets_head; struct ipsets *ipsets = &ipsets_head; + struct ipsets **daemon_sets = + (option == LOPT_IPSET) ? &daemon->ipsets : &daemon->nftsets; int size; char *end; char **sets, **sets_pos; @@ -2729,20 +3177,154 @@ static int one_opt(int option, char *arg, char *errstr sets = sets_pos = opt_malloc(sizeof(char *) * size); do { + char *p; end = split(arg); - *sets_pos++ = opt_string_alloc(arg); + *sets_pos = opt_string_alloc(arg); + /* Use '#' to delimit table and set */ + if (option == LOPT_NFTSET) + while ((p = strchr(*sets_pos, '#'))) + *p = ' '; + sets_pos++; 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; + ipsets->next = *daemon_sets; + *daemon_sets = ipsets_head.next; break; } + + case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */ +#ifndef HAVE_CONNTRACK + ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives")); + break; +#else + { + u32 mask = UINT32_MAX; + + if (arg) + if (!strtoul_check(arg, &mask) || mask < 1) + ret_err(gen_err); + + set_option_bool(OPT_CMARK_ALST_EN); + daemon->allowlist_mask = mask; + break; + } #endif + case LOPT_CMARK_ALST: /* --connmark-allowlist */ +#ifndef HAVE_CONNTRACK + ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives")); + break; +#else + { + struct allowlist *allowlists; + char **patterns, **patterns_pos; + u32 mark, mask = UINT32_MAX; + size_t num_patterns = 0; + + char *c, *m = NULL; + char *separator; + unhide_metas(arg); + if (!arg) + ret_err(gen_err); + c = arg; + if (*c < '0' || *c > '9') + ret_err(gen_err); + while (*c && *c != ',') + { + if (*c == '/') + { + if (m) + ret_err(gen_err); + *c = '\0'; + m = ++c; + } + if (*c < '0' || *c > '9') + ret_err(gen_err); + c++; + } + separator = c; + if (!*separator) + break; + while (c && *c) + { + char *end = strchr(++c, '/'); + if (end) + *end = '\0'; + if (strcmp(c, "*") && !is_valid_dns_name_pattern(c)) + ret_err(gen_err); + if (end) + *end = '/'; + if (num_patterns >= UINT16_MAX - 1) + ret_err(gen_err); + num_patterns++; + c = end; + } + + *separator = '\0'; + if (!strtoul_check(arg, &mark) || mark < 1 || mark > UINT32_MAX) + ret_err(gen_err); + if (m) + if (!strtoul_check(m, &mask) || mask < 1 || mask > UINT32_MAX || (mark & ~mask)) + ret_err(gen_err); + if (num_patterns) + *separator = ','; + for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next) + if (allowlists->mark == mark && allowlists->mask == mask) + ret_err(gen_err); + + patterns = opt_malloc((num_patterns + 1) * sizeof(char *)); + if (!patterns) + goto fail_cmark_allowlist; + patterns_pos = patterns; + c = separator; + while (c && *c) + { + char *end = strchr(++c, '/'); + if (end) + *end = '\0'; + if (!(*patterns_pos++ = opt_string_alloc(c))) + goto fail_cmark_allowlist; + if (end) + *end = '/'; + c = end; + } + *patterns_pos++ = NULL; + + allowlists = opt_malloc(sizeof(struct allowlist)); + if (!allowlists) + goto fail_cmark_allowlist; + memset(allowlists, 0, sizeof(struct allowlist)); + allowlists->mark = mark; + allowlists->mask = mask; + allowlists->patterns = patterns; + allowlists->next = daemon->allowlists; + daemon->allowlists = allowlists; + break; + + fail_cmark_allowlist: + if (patterns) + { + for (patterns_pos = patterns; *patterns_pos; patterns_pos++) + { + free(*patterns_pos); + *patterns_pos = NULL; + } + free(patterns); + patterns = NULL; + } + if (allowlists) + { + free(allowlists); + allowlists = NULL; + } + ret_err(gen_err); + } +#endif + case 'c': /* --cache-size */ { int size; @@ -2820,6 +3402,11 @@ static int one_opt(int option, char *arg, char *errstr if (daemon->query_port == 0) daemon->osport = 1; break; + + case LOPT_RANDPORT_LIM: /* --port-limit */ + if (!atoi_check(arg, &daemon->randport_limit) || (daemon->randport_limit < 1)) + ret_err(gen_err); + break; case 'T': /* --local-ttl */ case LOPT_NEGTTL: /* --neg-ttl */ @@ -2855,7 +3442,30 @@ static int one_opt(int option, char *arg, char *errstr daemon->local_ttl = (unsigned long)ttl; break; } + + case LOPT_FAST_RETRY: + daemon->fast_retry_timeout = TIMEOUT; + if (!arg) + daemon->fast_retry_time = DEFAULT_FAST_RETRY; + else + { + int retry; + + comma = split(arg); + if (!atoi_check(arg, &retry) || retry < 50) + ret_err(gen_err); + daemon->fast_retry_time = retry; + + if (comma) + { + if (!atoi_check(comma, &retry)) + ret_err(gen_err); + daemon->fast_retry_timeout = retry/1000; + } + } + break; + #ifdef HAVE_DHCP case 'X': /* --dhcp-lease-max */ if (!atoi_check(arg, &daemon->dhcp_max)) @@ -3389,7 +3999,10 @@ static int one_opt(int option, char *arg, char *errstr 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)); + inet_ntop(AF_INET, &in, daemon->addrbuff, ADDRSTRLEN); + sprintf(errstr, _("duplicate dhcp-host IP address %s"), + daemon->addrbuff); + dhcp_config_free(new); return 0; } } @@ -3553,16 +4166,16 @@ static int one_opt(int option, char *arg, char *errstr case LOPT_NAME_MATCH: /* --dhcp-name-match */ { - struct dhcp_match_name *new = opt_malloc(sizeof(struct dhcp_match_name)); - struct dhcp_netid *id = opt_malloc(sizeof(struct dhcp_netid)); + struct dhcp_match_name *new; ssize_t len; if (!(comma = split(arg)) || (len = strlen(comma)) == 0) ret_err(gen_err); + new = opt_malloc(sizeof(struct dhcp_match_name)); new->wildcard = 0; - new->netid = id; - id->net = opt_string_alloc(set_prefix(arg)); + new->netid = opt_malloc(sizeof(struct dhcp_netid)); + new->netid->net = opt_string_alloc(set_prefix(arg)); if (comma[len-1] == '*') { @@ -3766,6 +4379,8 @@ static int one_opt(int option, char *arg, char *errstr } } + dhcp_netid_free(new->netid); + free(new); ret_err(gen_err); } @@ -3800,7 +4415,7 @@ static int one_opt(int option, char *arg, char *errstr case LOPT_SUBSCR: /* --dhcp-subscrid */ { unsigned char *p; - int dig = 0; + int dig, colon; struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor)); if (!(comma = split(arg))) @@ -3824,13 +4439,16 @@ static int one_opt(int option, char *arg, char *errstr else comma = arg; - for (p = (unsigned char *)comma; *p; p++) + for (dig = 0, colon = 0, p = (unsigned char *)comma; *p; p++) if (isxdigit(*p)) dig = 1; - else if (*p != ':') + else if (*p == ':') + colon = 1; + else break; + unhide_metas(comma); - if (option == 'U' || option == 'j' || *p || !dig) + if (option == 'U' || option == 'j' || *p || !dig || !colon) { new->len = strlen(comma); new->data = opt_malloc(new->len); @@ -3953,26 +4571,66 @@ static int one_opt(int option, char *arg, char *errstr } } 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)); + char *two = split(arg); + char *three = split(two); + new->iface_index = 0; - if (inet_pton(AF_INET, arg, &new->local) && inet_pton(AF_INET, comma, &new->server)) + + if (two) { - new->next = daemon->relay4; - daemon->relay4 = new; - } + if (inet_pton(AF_INET, arg, &new->local)) + { + char *hash = split_chr(two, '#'); + + if (!hash || !atoi_check16(hash, &new->port)) + new->port = DHCP_SERVER_PORT; + + if (!inet_pton(AF_INET, two, &new->server)) + { + new->server.addr4.s_addr = 0; + + /* Fail for three arg version where there are not two addresses. + Also fail when broadcasting to wildcard address. */ + if (three || strchr(two, '*')) + two = NULL; + else + three = two; + } + + 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; - } + else if (inet_pton(AF_INET6, arg, &new->local)) + { + char *hash = split_chr(two, '#'); + + if (!hash || !atoi_check16(hash, &new->port)) + new->port = DHCPV6_SERVER_PORT; + + if (!inet_pton(AF_INET6, two, &new->server)) + { + inet_pton(AF_INET6, ALL_SERVERS, &new->server.addr6); + /* Fail for three arg version where there are not two addresses. + Also fail when multicasting to wildcard address. */ + if (three || strchr(two, '*')) + two = NULL; + else + three = two; + } + new->next = daemon->relay6; + daemon->relay6 = new; + } #endif - else + + new->interface = opt_string_alloc(three); + } + + if (!two) { free(new->interface); ret_err_free(_("Bad dhcp-relay"), new); @@ -4075,43 +4733,73 @@ err: } case LOPT_INTNAME: /* --interface-name */ + case LOPT_DYNHOST: /* --dynamic-host */ { struct interface_name *new, **up; - char *domain = NULL; - - comma = split(arg); + char *domain = arg; - if (!comma || !(domain = canonicalise_opt(arg))) - ret_err(_("bad interface name")); + arg = split(arg); new = opt_malloc(sizeof(struct interface_name)); - new->next = NULL; - new->addr = NULL; + memset(new, 0, sizeof(struct interface_name)); + new->flags = IN4 | IN6; /* 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) + + while ((comma = split(arg))) { - if (strcmp(arg, "4") == 0) - new->family = AF_INET; - else if (strcmp(arg, "6") == 0) - new->family = AF_INET6; + if (inet_pton(AF_INET, arg, &new->proto4)) + new->flags |= INP4; + else if (inet_pton(AF_INET6, arg, &new->proto6)) + new->flags |= INP6; else + break; + + arg = comma; + } + + if ((comma = split_chr(arg, '/'))) + { + if (strcmp(comma, "4") == 0) + new->flags &= ~IN6; + else if (strcmp(comma, "6") == 0) + new->flags &= ~IN4; + else ret_err_free(gen_err, new); - } - new->intr = opt_string_alloc(comma); + } + + new->intr = opt_string_alloc(arg); + + if (option == LOPT_DYNHOST) + { + if (!(new->flags & (INP4 | INP6))) + ret_err(_("missing address in dynamic host")); + + if (!(new->flags & IN4) || !(new->flags & IN6)) + arg = NULL; /* provoke error below */ + + new->flags &= ~(IN4 | IN6); + } + else + { + if (new->flags & (INP4 | INP6)) + arg = NULL; /* provoke error below */ + } + + if (!domain || !arg || !(new->name = canonicalise_opt(domain))) + ret_err(option == LOPT_DYNHOST ? + _("bad dynamic host") : _("bad interface name")); + break; } case LOPT_CNAME: /* --cname */ { struct cname *new; - char *alias, *target, *last, *pen; + char *alias, *target=NULL, *last, *pen; int ttl = -1; for (last = pen = NULL, comma = arg; comma; comma = split(comma)) @@ -4126,13 +4814,13 @@ err: if (pen != arg && atoi_check(last, &ttl)) last = pen; - target = canonicalise_opt(last); - while (arg != last) { int arglen = strlen(arg); alias = canonicalise_opt(arg); + if (!target) + target = canonicalise_opt(last); if (!alias || !target) { free(target); @@ -4154,7 +4842,7 @@ err: new->target = target; new->ttl = ttl; - for (arg += arglen+1; *arg && isspace(*arg); arg++); + for (arg += arglen+1; *arg && isspace((unsigned char)*arg); arg++); } break; @@ -4431,12 +5119,11 @@ err: } else { - int nomem; - char *canon = canonicalise(arg, &nomem); + char *canon = canonicalise_opt(arg); struct name_list *nl; if (!canon) { - struct name_list *tmp = new->names, *next; + struct name_list *tmp, *next; for (tmp = new->names; tmp; tmp = next) { next = tmp->next; @@ -4473,6 +5160,24 @@ err: break; } + case LOPT_STALE_CACHE: + { + int max_expiry = STALE_CACHE_EXPIRY; + if (arg) + { + /* Don't accept negative TTLs here, they'd have the counter-intuitive + side-effect of evicting cache records before they expire */ + if (!atoi_check(arg, &max_expiry) || max_expiry < 0) + ret_err(gen_err); + /* Store "serve expired forever" as -1 internally, the option isn't + active for daemon->cache_max_expiry == 0 */ + if (max_expiry == 0) + max_expiry = -1; + } + daemon->cache_max_expiry = max_expiry; + break; + } + #ifdef HAVE_DNSSEC case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */ daemon->timestamp_file = opt_string_alloc(arg); @@ -4528,7 +5233,7 @@ err: unhide_metas(keyhex); /* 4034: "Whitespace is allowed within digits" */ for (cp = keyhex; *cp; ) - if (isspace(*cp)) + if (isspace((unsigned char)*cp)) for (cp1 = cp; *cp1; cp1++) *cp1 = *(cp1+1); else @@ -4554,7 +5259,7 @@ err: return 1; } -static void read_file(char *file, FILE *f, int hard_opt) +static void read_file(char *file, FILE *f, int hard_opt, int from_script) { volatile int lineno = 0; char *buff = daemon->namebuff; @@ -4562,10 +5267,12 @@ static void read_file(char *file, FILE *f, int hard_op while (fgets(buff, MAXDNAME, f)) { int white, i; - volatile int option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt; + volatile int option; char *errmess, *p, *arg, *start; size_t len; + option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt; + /* Memory allocation failure longjmps here if mem_recover == 1 */ if (option != 0 || hard_opt == LOPT_REV_SERV) { @@ -4573,7 +5280,7 @@ static void read_file(char *file, FILE *f, int hard_op continue; mem_recover = 1; } - + arg = NULL; lineno++; errmess = NULL; @@ -4614,7 +5321,7 @@ static void read_file(char *file, FILE *f, int hard_op memmove(p, p+1, strlen(p+1)+1); } - if (isspace(*p)) + if (isspace((unsigned char)*p)) { *p = ' '; white = 1; @@ -4679,7 +5386,11 @@ static void read_file(char *file, FILE *f, int hard_op if (errmess || !one_opt(option, arg, daemon->namebuff, _("error"), 0, hard_opt == LOPT_REV_SERV)) { - sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file); + if (from_script) + sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" in output from %s"), file); + else + sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file); + if (hard_opt != 0) my_syslog(LOG_ERR, "%s", daemon->namebuff); else @@ -4688,7 +5399,6 @@ static void read_file(char *file, FILE *f, int hard_op } mem_recover = 0; - fclose(f); } #if defined(HAVE_DHCP) && defined(HAVE_INOTIFY) @@ -4708,7 +5418,7 @@ int option_read_dynfile(char *file, int flags) static int one_file(char *file, int hard_opt) { FILE *f; - int nofile_ok = 0; + int nofile_ok = 0, do_popen = 0; static int read_stdin = 0; static struct fileread { dev_t dev; @@ -4716,14 +5426,20 @@ static int one_file(char *file, int hard_opt) struct fileread *next; } *filesread = NULL; - if (hard_opt == '7') + if (hard_opt == LOPT_CONF_OPT) { /* default conf-file reading */ hard_opt = 0; nofile_ok = 1; } - if (hard_opt == 0 && strcmp(file, "-") == 0) + if (hard_opt == LOPT_CONF_SCRIPT) + { + hard_opt = 0; + do_popen = 1; + } + + if (hard_opt == 0 && !do_popen && strcmp(file, "-") == 0) { if (read_stdin == 1) return 1; @@ -4750,8 +5466,13 @@ static int one_file(char *file, int hard_opt) r->dev = statbuf.st_dev; r->ino = statbuf.st_ino; } - - if (!(f = fopen(file, "r"))) + + if (do_popen) + { + if (!(f = popen(file, "r"))) + die(_("cannot execute %s: %s"), file, EC_FILE); + } + else if (!(f = fopen(file, "r"))) { if (errno == ENOENT && nofile_ok) return 1; /* No conffile, all done. */ @@ -4769,19 +5490,51 @@ static int one_file(char *file, int hard_opt) } } - read_file(file, f, hard_opt); + read_file(file, f, hard_opt, do_popen); + + if (do_popen) + { + int rc; + + if ((rc = pclose(f)) == -1) + die(_("error executing %s: %s"), file, EC_MISC); + + if (rc != 0) + die(_("%s returns non-zero error code"), file, rc+10); + } + else + fclose(f); + return 1; } +static int file_filter(const struct dirent *ent) +{ + size_t lenfile = strlen(ent->d_name); + + /* 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] == '.') + return 0; + + return 1; +} /* expand any name which is a directory */ struct hostsfile *expand_filelist(struct hostsfile *list) { unsigned int i; - struct hostsfile *ah; + int entcnt, n; + struct hostsfile *ah, *last, *next, **up; + struct dirent **namelist; /* find largest used index */ for (i = SRC_AH, ah = list; ah; ah = ah->next) { + last = ah; + if (i <= ah->index) i = ah->index + 1; @@ -4797,46 +5550,48 @@ struct hostsfile *expand_filelist(struct hostsfile *li struct stat buf; if (stat(ah->fname, &buf) != -1 && S_ISDIR(buf.st_mode)) { - DIR *dir_stream; struct dirent *ent; /* don't read this as a file */ ah->flags |= AH_INACTIVE; - if (!(dir_stream = opendir(ah->fname))) + entcnt = scandir(ah->fname, &namelist, file_filter, alphasort); + if (entcnt < 0) my_syslog(LOG_ERR, _("cannot access directory %s: %s"), ah->fname, strerror(errno)); else { - while ((ent = readdir(dir_stream))) + for (n = 0; n < entcnt; n++) { + ent = namelist[n]; size_t lendir = strlen(ah->fname); size_t lenfile = strlen(ent->d_name); struct hostsfile *ah1; 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; - /* see if we have an existing record. dir is ah->fname file is ent->d_name path to match is ah1->fname */ - for (ah1 = list; ah1; ah1 = ah1->next) + for (up = &list, ah1 = list; ah1; ah1 = next) { + next = ah1->next; + if (lendir < strlen(ah1->fname) && strstr(ah1->fname, ah->fname) == ah1->fname && ah1->fname[lendir] == '/' && strcmp(ah1->fname + lendir + 1, ent->d_name) == 0) { ah1->flags &= ~AH_INACTIVE; + /* If found, remove from list to re-insert at the end. + Unless it's already at the end. */ + if (last != ah1) + *up = next; break; } + + up = &ah1->next; } /* make new record */ @@ -4857,17 +5612,21 @@ struct hostsfile *expand_filelist(struct hostsfile *li ah1->fname = path; ah1->index = i++; ah1->flags = AH_DIR; - ah1->next = list; - list = ah1; } + + /* Edge case, may be the last in the list anyway */ + if (last != ah1) + last->next = ah1; + ah1->next = NULL; + last = ah1; /* inactivate record if not regular file */ if ((ah1->flags & AH_DIR) && stat(ah1->fname, &buf) != -1 && !S_ISREG(buf.st_mode)) ah1->flags |= AH_INACTIVE; } - closedir(dir_stream); } + free(namelist); } } @@ -4885,9 +5644,10 @@ void read_servers_file(void) } mark_servers(SERV_FROM_FILE); + read_file(daemon->servers_file, f, LOPT_REV_SERV, 0); + fclose(f); cleanup_servers(); - - read_file(daemon->servers_file, f, LOPT_REV_SERV); + check_servers(0); } @@ -4903,30 +5663,8 @@ static void clear_dynamic_conf(void) if (configs->flags & CONFIG_BANK) { - struct hwaddr_config *mac, *tmp; - struct dhcp_netid_list *list, *tmplist; - - for (mac = configs->hwaddr; mac; mac = tmp) - { - tmp = mac->next; - free(mac); - } - - if (configs->flags & CONFIG_CLID) - free(configs->clid); - - for (list = configs->netid; list; list = tmplist) - { - free(list->list); - tmplist = list->next; - free(list); - } - - if (configs->flags & CONFIG_NAME) - free(configs->hostname); - - *up = configs->next; - free(configs); + *up = cp; + dhcp_config_free(configs); } else up = &configs->next; @@ -4936,7 +5674,6 @@ static void clear_dynamic_conf(void) static void clear_dynamic_opt(void) { struct dhcp_opt *opts, *cp, **up; - struct dhcp_netid *id, *next; for (up = &daemon->dhcp_opts, opts = daemon->dhcp_opts; opts; opts = cp) { @@ -4944,17 +5681,8 @@ static void clear_dynamic_opt(void) if (opts->flags & DHOPT_BANK) { - if ((opts->flags & DHOPT_VENDOR)) - free(opts->u.vendor_class); - free(opts->val); - for (id = opts->netid; id; id = next) - { - next = id->next; - free(id->net); - free(id); - } - *up = opts->next; - free(opts); + *up = cp; + dhcp_opt_free(opts); } else up = &opts->next; @@ -5014,7 +5742,8 @@ void read_opts(int argc, char **argv, char *compile_op daemon = opt_malloc(sizeof(struct daemon)); memset(daemon, 0, sizeof(struct daemon)); daemon->namebuff = buff; - + daemon->addrbuff = safe_malloc(ADDRSTRLEN); + /* Set defaults - everything else is zero or NULL */ daemon->cachesize = CACHESIZ; daemon->ftabsize = FTABSIZ; @@ -5034,24 +5763,12 @@ void read_opts(int argc, char **argv, char *compile_op daemon->soa_refresh = SOA_REFRESH; daemon->soa_retry = SOA_RETRY; daemon->soa_expiry = SOA_EXPIRY; - daemon->max_port = MAX_PORT; - daemon->min_port = MIN_PORT; - -#ifndef NO_ID - 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); -#endif - + daemon->randport_limit = 1; + daemon->host_index = SRC_AH; + + /* See comment above make_servers(). Optimises server-read code. */ + mark_servers(0); + while (1) { #ifdef HAVE_GETOPT_LONG @@ -5144,7 +5861,26 @@ void read_opts(int argc, char **argv, char *compile_op free(conffile); } else - one_file(CONFFILE, '7'); + one_file(CONFFILE, LOPT_CONF_OPT); + + /* Add TXT records if wanted */ +#ifndef NO_ID + if (!option_bool(OPT_NO_IDENT)) + { + 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); + } +#endif /* port might not be known when the address is parsed - fill in here */ if (daemon->servers)