diff options
Diffstat (limited to 'src/option.c')
-rw-r--r-- | src/option.c | 979 |
1 files changed, 728 insertions, 251 deletions
diff --git a/src/option.c b/src/option.c index ffce9fc..8e61a6b 100644 --- a/src/option.c +++ b/src/option.c @@ -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 @@ -174,7 +174,18 @@ struct myoption { #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 + #ifdef HAVE_GETOPT_LONG static const struct option opts[] = #else @@ -211,6 +222,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' }, @@ -218,11 +231,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' }, @@ -309,7 +324,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 }, @@ -327,6 +344,7 @@ 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 }, @@ -351,8 +369,11 @@ static const struct myoption opts[] = { "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 }, + { "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 }, { NULL, 0, 0, 0 } }; @@ -380,6 +401,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, "<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 }, @@ -408,6 +431,7 @@ static struct { { '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 }, + { LOPT_STALE_CACHE, ARG_ONE, "[=<max_expired>]", 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, "<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}, @@ -415,6 +439,7 @@ static struct { { '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 }, + { 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, "<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 }, @@ -428,6 +453,7 @@ static struct { { 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 }, + { LOPT_FAST_RETRY, ARG_ONE, "<milliseconds>", gettext_noop("Retry DNS queries after this many milliseconds."), 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 }, @@ -455,6 +481,7 @@ static struct { { LOPT_SCRIPTUSR, ARG_ONE, "<username>", 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, "<path>", gettext_noop("Read configuration from all the files in this directory."), NULL }, + { LOPT_CONF_SCRIPT, ARG_DUP, "<path>", gettext_noop("Execute file and read configuration from stdin."), NULL }, { '8', ARG_ONE, "<facility>|<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, "<integer>", gettext_noop("Maximum number of concurrent DNS queries. (defaults to %s)"), "!" }, @@ -493,7 +520,9 @@ static struct { { 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, 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, "<v4 pref>[,<v6 pref>]", 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, "<text>", 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 }, @@ -514,6 +543,7 @@ static struct { { 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>[/<domain>...]/<ipset>...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL }, + { LOPT_NFTSET, ARG_DUP, "/<domain>[/<domain>...]/<nftset>...", gettext_noop("Specify nftables sets to which matching domains should be added"), NULL }, { LOPT_CMARK_ALST_EN, ARG_ONE, "[=<mask>]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL }, { LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL }, { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL }, @@ -539,6 +569,7 @@ static struct { { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL }, { LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", 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 }, { 0, 0, NULL, NULL, NULL } }; @@ -654,7 +685,7 @@ static char *canonicalise_opt(char *s) return 0; if (strlen(s) == 0) - return opt_string_alloc(""); + return opt_malloc(1); /* Heap-allocated empty string */ unhide_metas(s); if (!(ret = canonicalise(s, &nomem)) && nomem) @@ -798,7 +829,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); @@ -824,163 +855,407 @@ static char *parse_mysockaddr(char *arg, union mysockaddr *addr) return NULL; } -char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, u16 *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; - - *interface = 0; + sdetails->serv_port = NAMESERVER_PORT; + char *portno; + int ecode = 0; + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + + *sdetails->interface = 0; + sdetails->addr_type = AF_UNSPEC; + if (strcmp(arg, "#") == 0) { - if (flags) - *flags |= SERV_USE_RESOLV; + 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, source, IF_NAMESIZE); - source = interface_opt; + 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 + { + /* 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 <arg> 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 <hints> + above, and returns a pointer to the start of the list in <hostinfo>. + The items in the linked list are linked by the <ai_next> 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) { - addr->in.sin_port = htons(serv_port); - addr->sa.sa_family = source_addr->sa.sa_family = 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 int domain_rev4(char *domain, struct in_addr addr, int msize) +int parse_server_next(struct server_details *sdetails) +{ + /* Looping over resolved addresses? */ + if (sdetails->hostinfo) + { + /* Get address type */ + sdetails->addr_type = sdetails->hostinfo->ai_family; + + /* 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)); + + /* 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) + { + /* 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; +} + +static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int size) { - in_addr_t a = ntohl(addr.s_addr); + 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; + + if (from_file) + flags |= SERV_FROM_FILE; - *domain = 0; + 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; - switch (msize) + if (rem != 0) + count = 1 << (8 - rem); + + for (i = 0; i < count; i++) { - case 32: - domain += sprintf(domain, "%u.", a & 0xff); - /* fall through */ - case 24: - domain += sprintf(domain, "%d.", (a >> 8) & 0xff); - /* fall through */ - case 16: - domain += sprintf(domain, "%d.", (a >> 16) & 0xff); - /* fall through */ - case 8: - domain += sprintf(domain, "%d.", (a >> 24) & 0xff); - break; - default: - return 0; + *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"); + + if (flags & SERV_LITERAL_ADDRESS) + { + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + else + { + 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); + } } - domain += sprintf(domain, "in-addr.arpa"); - - return 1; + return NULL; } -static int domain_rev6(char *domain, struct in6_addr *addr, int msize) +static char *domain_rev6(int from_file, char *server, struct in6_addr *addr6, int size) { - int i; + int i, j; + char *string; + int msize; + u16 flags = 0; + char domain[73]; /* strlen("32*<n.>ip6.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; - if (msize > 128 || msize%4) - return 0; + if (from_file) + flags |= SERV_FROM_FILE; + + rem = size & 0x3; + addrbytes = (128 - size) >> 3; + addrbits = (128 - size) & 7; + + 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; - *domain = 0; + 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"); - for (i = msize-1; i >= 0; i -= 4) - { - int dig = ((unsigned char *)addr)[i>>3]; - domain += sprintf(domain, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4); + if (flags & SERV_LITERAL_ADDRESS) + { + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + else + { + 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); + } } - domain += sprintf(domain, "ip6.arpa"); - return 1; + return NULL; } #ifdef HAVE_DHCP @@ -1723,6 +1998,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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; @@ -1829,6 +2115,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->next = li; *up = new; } + else + free(path); } @@ -1995,7 +2283,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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; @@ -2045,15 +2337,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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') { @@ -2069,21 +2357,29 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { 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); @@ -2146,8 +2442,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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; @@ -2302,17 +2600,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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 { - char domain[29]; /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */ /* local=/xxx.yyy.zzz.in-addr.arpa/ */ - /* domain_rev4 can't fail here, msize checked above. */ - domain_rev4(domain, new->start, msize); - add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL); - + domain_rev4(0, NULL, &new->start, msize); + /* local=/<domain>/ */ /* d_raw can't failed to canonicalise here, checked above. */ add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL); @@ -2347,16 +2641,14 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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 { - char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */ /* generate the equivalent of local=/xxx.yyy.zzz.ip6.arpa/ */ - domain_rev6(domain, &new->start6, msize); - add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL); - + domain_rev6(0, NULL, &new->start6, msize); + /* local=/<domain>/ */ /* d_raw can't failed to canonicalise here, checked above. */ add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL); @@ -2388,9 +2680,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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)) || @@ -2436,39 +2734,48 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_UMBRELLA: /* --umbrella */ set_option_bool(OPT_UMBRELLA); - while (arg) { - comma = split(arg); - if (strstr(arg, "deviceid:")) { - arg += 9; - if (strlen(arg) != 16) - ret_err(gen_err); - for (char *p = arg; *p; p++) { - if (!isxdigit((int)*p)) - ret_err(gen_err); - } - set_option_bool(OPT_UMBRELLA_DEVID); - - u8 *u = daemon->umbrella_device; - char word[3]; - for (u8 i = 0; i < 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); - } - } - arg = comma; - } + 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((int)*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); @@ -2635,7 +2942,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_NO_REBIND: /* --rebind-domain-ok */ { - struct server *new; + struct rebind_domain *new; unhide_metas(arg); @@ -2644,9 +2951,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma do { comma = split_chr(arg, '/'); - new = opt_malloc(sizeof(struct serv_local)); - new->domain = opt_string_alloc(arg); - new->domain_len = strlen(arg); + new = opt_malloc(sizeof(struct rebind_domain)); + new->domain = canonicalise_opt(arg); new->next = daemon->no_rebind; daemon->no_rebind = new; arg = comma; @@ -2659,13 +2965,20 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_LOCAL: /* --local */ case 'A': /* --address */ { - char *lastdomain = NULL, *domain = ""; + 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); /* split the domain args, if any and skip to the end of them. */ @@ -2698,29 +3011,55 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } else { - if ((err = parse_server(arg, &serv_addr, &source_addr, interface, &flags))) + if ((err = parse_server(arg, &sdetails))) ret_err(err); } if (servers_only && option == 'S') flags |= SERV_FROM_FILE; - - while (1) + + cur_domain = domain; + while ((flags & SERV_LITERAL_ADDRESS) || parse_server_next(&sdetails)) { - /* server=//1.2.3.4 is special. */ - if (strlen(domain) == 0 && lastdomain) - flags |= SERV_FOR_NODOTS; - else - flags &= ~SERV_FOR_NODOTS; + cur_domain = domain; - if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, &addr)) - ret_err(gen_err); - - if (!lastdomain || domain == lastdomain) + 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=//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; - - domain += strlen(domain) + 1; } + + if (sdetails.orig_hostinfo) + freeaddrinfo(sdetails.orig_hostinfo); break; } @@ -2729,57 +3068,62 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { char *string; int size; - u16 flags = 0; - char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */ struct in_addr addr4; struct in6_addr addr6; - union mysockaddr serv_addr, source_addr; - char interface[IF_NAMESIZE+1]; - + 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)) { - if (!domain_rev4(domain, addr4, size)) - ret_err(_("bad IPv4 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)) { - if (!domain_rev6(domain, &addr6, size)) - ret_err(_("bad IPv6 prefix")); + if (size == -1) + size = 128; + + if ((string = domain_rev6(servers_only, comma, &addr6, size))) + ret_err(string); } else ret_err(gen_err); - if (!comma) - flags |= SERV_LITERAL_ADDRESS; - else if ((string = parse_server(comma, &serv_addr, &source_addr, interface, &flags))) - ret_err(string); - - if (servers_only) - flags |= SERV_FROM_FILE; - - if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) - ret_err(gen_err); - 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; @@ -2824,19 +3168,24 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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; } -#endif case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */ #ifndef HAVE_CONNTRACK @@ -3044,6 +3393,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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 */ @@ -3079,7 +3433,30 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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)) @@ -3616,6 +3993,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma inet_ntop(AF_INET, &in, daemon->addrbuff, ADDRSTRLEN); sprintf(errstr, _("duplicate dhcp-host IP address %s"), daemon->addrbuff); + dhcp_config_free(new); return 0; } } @@ -3779,16 +4157,16 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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] == '*') { @@ -3992,6 +4370,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } } + dhcp_netid_free(new->netid); + free(new); ret_err(gen_err); } @@ -4026,7 +4406,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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))) @@ -4050,13 +4430,16 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 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); @@ -4179,26 +4562,66 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int 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)); + char *two = split(arg); + char *three = split(two); + new->iface_index = 0; - if (comma && 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 (comma && 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); @@ -4367,7 +4790,7 @@ err: 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)) @@ -4382,13 +4805,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); @@ -4691,7 +5114,7 @@ err: 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; @@ -4728,6 +5151,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); @@ -4809,7 +5250,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; @@ -4817,10 +5258,12 @@ static void read_file(char *file, FILE *f, int hard_opt) 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) { @@ -4828,7 +5271,7 @@ static void read_file(char *file, FILE *f, int hard_opt) continue; mem_recover = 1; } - + arg = NULL; lineno++; errmess = NULL; @@ -4934,7 +5377,11 @@ static void read_file(char *file, FILE *f, int hard_opt) 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 @@ -4943,7 +5390,6 @@ static void read_file(char *file, FILE *f, int hard_opt) } mem_recover = 0; - fclose(f); } #if defined(HAVE_DHCP) && defined(HAVE_INOTIFY) @@ -4963,7 +5409,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; @@ -4971,14 +5417,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; @@ -5005,8 +5457,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. */ @@ -5024,7 +5481,21 @@ 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; } @@ -5164,7 +5635,8 @@ void read_servers_file(void) } mark_servers(SERV_FROM_FILE); - read_file(daemon->servers_file, f, LOPT_REV_SERV); + read_file(daemon->servers_file, f, LOPT_REV_SERV, 0); + fclose(f); cleanup_servers(); check_servers(0); } @@ -5282,6 +5754,8 @@ void read_opts(int argc, char **argv, char *compile_opts) daemon->soa_refresh = SOA_REFRESH; daemon->soa_retry = SOA_RETRY; daemon->soa_expiry = SOA_EXPIRY; + daemon->randport_limit = 1; + daemon->host_index = SRC_AH; #ifndef NO_ID add_txt("version.bind", "dnsmasq-" VERSION, 0 ); @@ -5297,7 +5771,10 @@ void read_opts(int argc, char **argv, char *compile_opts) #endif add_txt("servers.bind", NULL, TXT_STAT_SERVERS); #endif - + + /* See comment above make_servers(). Optimises server-read code. */ + mark_servers(0); + while (1) { #ifdef HAVE_GETOPT_LONG @@ -5390,7 +5867,7 @@ void read_opts(int argc, char **argv, char *compile_opts) free(conffile); } else - one_file(CONFFILE, '7'); + one_file(CONFFILE, LOPT_CONF_OPT); /* port might not be known when the address is parsed - fill in here */ if (daemon->servers) |