|
|
@@ -0,0 +1,312 @@ |
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */ |
|
|
|
|
|
|
|
#include <getopt.h> |
|
|
|
#include <net/if.h> |
|
|
|
|
|
|
|
#include "alloc-util.h" |
|
|
|
#include "def.h" |
|
|
|
#include "dns-domain.h" |
|
|
|
#include "extract-word.h" |
|
|
|
#include "fileio.h" |
|
|
|
#include "parse-util.h" |
|
|
|
#include "resolvconf-compat.h" |
|
|
|
#include "resolve-tool.h" |
|
|
|
#include "resolved-def.h" |
|
|
|
#include "string-util.h" |
|
|
|
#include "strv.h" |
|
|
|
|
|
|
|
static void resolvconf_help(void) { |
|
|
|
printf("%1$s -a INTERFACE < FILE\n" |
|
|
|
"%1$s -d INTERFACE\n" |
|
|
|
"\n" |
|
|
|
"Register DNS server and domain configuration with systemd-resolved.\n\n" |
|
|
|
" -h --help Show this help\n" |
|
|
|
" --version Show package version\n" |
|
|
|
" -a Register per-interface DNS server and domain data\n" |
|
|
|
" -d Unregister per-interface DNS server and domain data\n" |
|
|
|
" -f Ignore if specified interface does not exist\n" |
|
|
|
" -x Send DNS traffic preferably over this interface\n" |
|
|
|
"\n" |
|
|
|
"This is a compatibility alias for the systemd-resolve(1) tool, providing native\n" |
|
|
|
"command line compatibility with the resolvconf(8) tool of various Linux\n" |
|
|
|
"distributions and BSD systems. Some options supported by other implementations\n" |
|
|
|
"are not supported and are ignored: -m, -p. Various options supported by other\n" |
|
|
|
"implementations are not supported and will cause the invocation to fail: -u,\n" |
|
|
|
"-I, -i, -l, -R, -r, -v, -V, --enable-updates, --disable-updates,\n" |
|
|
|
"--updates-are-enabled.\n" |
|
|
|
, program_invocation_short_name); |
|
|
|
} |
|
|
|
|
|
|
|
static int parse_nameserver(const char *string) { |
|
|
|
int r; |
|
|
|
|
|
|
|
assert(string); |
|
|
|
|
|
|
|
for (;;) { |
|
|
|
_cleanup_free_ char *word = NULL; |
|
|
|
struct in_addr_data data, *n; |
|
|
|
int ifindex = 0; |
|
|
|
|
|
|
|
r = extract_first_word(&string, &word, NULL, 0); |
|
|
|
if (r < 0) |
|
|
|
return r; |
|
|
|
if (r == 0) |
|
|
|
break; |
|
|
|
|
|
|
|
r = in_addr_ifindex_from_string_auto(word, &data.family, &data.address, &ifindex); |
|
|
|
if (r < 0) |
|
|
|
return log_error_errno(r, "Failed to parse name server '%s': %m", word); |
|
|
|
|
|
|
|
if (ifindex > 0 && ifindex != arg_ifindex) { |
|
|
|
log_error("Name server interface '%s' does not match selected interface: %m", word); |
|
|
|
return -EINVAL; |
|
|
|
} |
|
|
|
|
|
|
|
/* Some superficial filtering */ |
|
|
|
if (in_addr_is_null(data.family, &data.address)) |
|
|
|
continue; |
|
|
|
if (data.family == AF_INET && data.address.in.s_addr == htobe32(INADDR_DNS_STUB)) /* resolved's own stub? */ |
|
|
|
continue; |
|
|
|
|
|
|
|
n = reallocarray(arg_set_dns, arg_n_set_dns + 1, sizeof(struct in_addr_data)); |
|
|
|
if (!n) |
|
|
|
return log_oom(); |
|
|
|
arg_set_dns = n; |
|
|
|
|
|
|
|
arg_set_dns[arg_n_set_dns++] = data; |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static int parse_search_domain(const char *string) { |
|
|
|
int r; |
|
|
|
|
|
|
|
assert(string); |
|
|
|
|
|
|
|
for (;;) { |
|
|
|
_cleanup_free_ char *word = NULL; |
|
|
|
|
|
|
|
r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); |
|
|
|
if (r < 0) |
|
|
|
return r; |
|
|
|
if (r == 0) |
|
|
|
break; |
|
|
|
|
|
|
|
r = dns_name_is_valid(word); |
|
|
|
if (r < 0) |
|
|
|
return log_error_errno(r, "Failed to validate specified domain '%s': %m", word); |
|
|
|
if (r == 0) { |
|
|
|
log_error("Domain not valid: %s", word); |
|
|
|
return -EINVAL; |
|
|
|
} |
|
|
|
|
|
|
|
if (strv_push(&arg_set_domain, word) < 0) |
|
|
|
return log_oom(); |
|
|
|
|
|
|
|
word = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
int resolvconf_parse_argv(int argc, char *argv[]) { |
|
|
|
|
|
|
|
enum { |
|
|
|
ARG_VERSION = 0x100, |
|
|
|
ARG_ENABLE_UPDATES, |
|
|
|
ARG_DISABLE_UPDATES, |
|
|
|
ARG_UPDATES_ARE_ENABLED, |
|
|
|
}; |
|
|
|
|
|
|
|
static const struct option options[] = { |
|
|
|
{ "help", no_argument, NULL, 'h' }, |
|
|
|
{ "version", no_argument, NULL, ARG_VERSION }, |
|
|
|
|
|
|
|
/* The following are specific to Debian's original resolvconf */ |
|
|
|
{ "enable-updates", no_argument, NULL, ARG_ENABLE_UPDATES }, |
|
|
|
{ "disable-updates", no_argument, NULL, ARG_DISABLE_UPDATES }, |
|
|
|
{ "updates-are-enabled", no_argument, NULL, ARG_UPDATES_ARE_ENABLED }, |
|
|
|
{} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
enum { |
|
|
|
TYPE_REGULAR, |
|
|
|
TYPE_PRIVATE, /* -p: Not supported, treated identically to TYPE_REGULAR */ |
|
|
|
TYPE_EXCLUSIVE, /* -x */ |
|
|
|
} type = TYPE_REGULAR; |
|
|
|
|
|
|
|
const char *dot, *iface; |
|
|
|
int c, r; |
|
|
|
|
|
|
|
assert(argc >= 0); |
|
|
|
assert(argv); |
|
|
|
|
|
|
|
/* openresolv checks these environment variables */ |
|
|
|
if (getenv("IF_EXCLUSIVE")) |
|
|
|
type = TYPE_EXCLUSIVE; |
|
|
|
if (getenv("IF_PRIVATE")) |
|
|
|
type = TYPE_PRIVATE; /* not actually supported */ |
|
|
|
|
|
|
|
arg_mode = _MODE_INVALID; |
|
|
|
|
|
|
|
while ((c = getopt_long(argc, argv, "hadxpfm:uIi:l:Rr:vV", options, NULL)) >= 0) |
|
|
|
switch(c) { |
|
|
|
|
|
|
|
case 'h': |
|
|
|
resolvconf_help(); |
|
|
|
return 0; /* done */; |
|
|
|
|
|
|
|
case ARG_VERSION: |
|
|
|
return version(); |
|
|
|
|
|
|
|
/* -a and -d is what everybody can agree on */ |
|
|
|
case 'a': |
|
|
|
arg_mode = MODE_SET_LINK; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'd': |
|
|
|
arg_mode = MODE_REVERT_LINK; |
|
|
|
break; |
|
|
|
|
|
|
|
/* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ |
|
|
|
case 'x': |
|
|
|
type = TYPE_EXCLUSIVE; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'p': |
|
|
|
type = TYPE_PRIVATE; /* not actually supported */ |
|
|
|
break; |
|
|
|
|
|
|
|
case 'f': |
|
|
|
arg_ifindex_permissive = true; |
|
|
|
break; |
|
|
|
|
|
|
|
/* The metrics stuff is an openresolv invention we ignore (and don't really need) */ |
|
|
|
case 'm': |
|
|
|
log_debug("Switch -%c ignored.", c); |
|
|
|
break; |
|
|
|
|
|
|
|
/* Everybody else can agree on the existance of -u but we don't support it. */ |
|
|
|
case 'u': |
|
|
|
|
|
|
|
/* The following options are openresolv inventions we don't support. */ |
|
|
|
case 'I': |
|
|
|
case 'i': |
|
|
|
case 'l': |
|
|
|
case 'R': |
|
|
|
case 'r': |
|
|
|
case 'v': |
|
|
|
case 'V': |
|
|
|
log_error("Switch -%c not supported.", c); |
|
|
|
return -EINVAL; |
|
|
|
|
|
|
|
/* The Debian resolvconf commands we don't support. */ |
|
|
|
case ARG_ENABLE_UPDATES: |
|
|
|
log_error("Switch --enable-updates not supported."); |
|
|
|
return -EINVAL; |
|
|
|
case ARG_DISABLE_UPDATES: |
|
|
|
log_error("Switch --disable-updates not supported."); |
|
|
|
return -EINVAL; |
|
|
|
case ARG_UPDATES_ARE_ENABLED: |
|
|
|
log_error("Switch --updates-are-enabled not supported."); |
|
|
|
return -EINVAL; |
|
|
|
|
|
|
|
case '?': |
|
|
|
return -EINVAL; |
|
|
|
|
|
|
|
default: |
|
|
|
assert_not_reached("Unhandled option"); |
|
|
|
} |
|
|
|
|
|
|
|
if (arg_mode == _MODE_INVALID) { |
|
|
|
log_error("Expected either -a or -d on the command line."); |
|
|
|
return -EINVAL; |
|
|
|
} |
|
|
|
|
|
|
|
if (optind+1 != argc) { |
|
|
|
log_error("Expected interface name as argument."); |
|
|
|
return -EINVAL; |
|
|
|
} |
|
|
|
|
|
|
|
dot = strchr(argv[optind], '.'); |
|
|
|
if (dot) { |
|
|
|
iface = strndupa(argv[optind], dot - argv[optind]); |
|
|
|
log_debug("Ignoring protocol specifier '%s'.", dot + 1); |
|
|
|
} else |
|
|
|
iface = argv[optind]; |
|
|
|
optind++; |
|
|
|
|
|
|
|
if (parse_ifindex(iface, &arg_ifindex) < 0) { |
|
|
|
int ifi; |
|
|
|
|
|
|
|
ifi = if_nametoindex(iface); |
|
|
|
if (ifi <= 0) { |
|
|
|
if (errno == ENODEV && arg_ifindex_permissive) { |
|
|
|
log_debug("Interface '%s' not found, but -f specified, ignoring.", iface); |
|
|
|
return 0; /* done */ |
|
|
|
} |
|
|
|
|
|
|
|
return log_error_errno(errno, "Unknown interface '%s': %m", iface); |
|
|
|
} |
|
|
|
|
|
|
|
arg_ifindex = ifi; |
|
|
|
} |
|
|
|
|
|
|
|
if (arg_mode == MODE_SET_LINK) { |
|
|
|
unsigned n = 0; |
|
|
|
|
|
|
|
for (;;) { |
|
|
|
_cleanup_free_ char *line = NULL; |
|
|
|
const char *a, *l; |
|
|
|
|
|
|
|
r = read_line(stdin, LONG_LINE_MAX, &line); |
|
|
|
if (r < 0) |
|
|
|
return log_error_errno(r, "Failed to read from stdin: %m"); |
|
|
|
if (r == 0) |
|
|
|
break; |
|
|
|
|
|
|
|
n++; |
|
|
|
|
|
|
|
l = strstrip(line); |
|
|
|
if (IN_SET(*l, '#', ';', 0)) |
|
|
|
continue; |
|
|
|
|
|
|
|
a = first_word(l, "nameserver"); |
|
|
|
if (a) { |
|
|
|
(void) parse_nameserver(a); |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
a = first_word(l, "domain"); |
|
|
|
if (!a) |
|
|
|
a = first_word(l, "search"); |
|
|
|
if (a) { |
|
|
|
(void) parse_search_domain(a); |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
log_syntax(NULL, LOG_DEBUG, "stdin", n, 0, "Ignoring resolv.conf line: %s", l); |
|
|
|
} |
|
|
|
|
|
|
|
if (type == TYPE_EXCLUSIVE) { |
|
|
|
|
|
|
|
/* If -x mode is selected, let's preferably route non-suffixed lookups to this interface. This |
|
|
|
* somewhat matches the original -x behaviour */ |
|
|
|
|
|
|
|
r = strv_extend(&arg_set_domain, "~."); |
|
|
|
if (r < 0) |
|
|
|
return log_oom(); |
|
|
|
|
|
|
|
} else if (type == TYPE_PRIVATE) |
|
|
|
log_debug("Private DNS server data not supported, ignoring."); |
|
|
|
|
|
|
|
if (arg_n_set_dns == 0) { |
|
|
|
log_error("No DNS servers specified, refusing operation."); |
|
|
|
return -EINVAL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return 1; /* work to do */ |
|
|
|
} |