@@ -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 */
}
@@ -0,0 +1,3 @@
/* SPDX-License-Identifier: LGPL-2.1+ */

int resolvconf_parse_argv(int argc, char *argv[]);
@@ -36,18 +36,21 @@
#include "netlink-util.h"
#include "pager.h"
#include "parse-util.h"
#include "resolvconf-compat.h"
#include "resolve-tool.h"
#include "resolved-def.h"
#include "resolved-dns-packet.h"
#include "strv.h"
#include "terminal-util.h"

static int arg_family = AF_UNSPEC;
static int arg_ifindex = 0;
int arg_ifindex = 0;
static uint16_t arg_type = 0;
static uint16_t arg_class = 0;
static bool arg_legend = true;
static uint64_t arg_flags = 0;
static bool arg_no_pager = false;
bool arg_ifindex_permissive = false; /* If true, don't generate an error if the specified interface index doesn't exist */

typedef enum ServiceFamily {
SERVICE_FAMILY_TCP,
@@ -64,24 +67,11 @@ typedef enum RawType {
} RawType;
static RawType arg_raw = RAW_NONE;

static enum {
MODE_RESOLVE_HOST,
MODE_RESOLVE_RECORD,
MODE_RESOLVE_SERVICE,
MODE_RESOLVE_OPENPGP,
MODE_RESOLVE_TLSA,
MODE_STATISTICS,
MODE_RESET_STATISTICS,
MODE_FLUSH_CACHES,
MODE_RESET_SERVER_FEATURES,
MODE_STATUS,
MODE_SET_LINK,
MODE_REVERT_LINK,
} arg_mode = MODE_RESOLVE_HOST;

static struct in_addr_data *arg_set_dns = NULL;
static size_t arg_n_set_dns = 0;
static char **arg_set_domain = NULL;
ExecutionMode arg_mode = MODE_RESOLVE_HOST;

struct in_addr_data *arg_set_dns = NULL;
size_t arg_n_set_dns = 0;
char **arg_set_domain = NULL;
static char *arg_set_llmnr = NULL;
static char *arg_set_mdns = NULL;
static char *arg_set_dnssec = NULL;
@@ -1609,6 +1599,9 @@ static int set_link(sd_bus *bus) {
if (q < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY))
goto is_managed;
if (arg_ifindex_permissive &&
sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK))
return 0;

log_error_errno(q, "Failed to set DNS configuration: %s", bus_error_message(&error, q));
if (r == 0)
@@ -1655,6 +1648,9 @@ static int set_link(sd_bus *bus) {
if (q < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY))
goto is_managed;
if (arg_ifindex_permissive &&
sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK))
return 0;

log_error_errno(q, "Failed to set domain configuration: %s", bus_error_message(&error, q));
if (r == 0)
@@ -1777,8 +1773,13 @@ static int revert_link(sd_bus *bus) {
&error,
NULL,
"i", arg_ifindex);
if (r < 0)
if (r < 0) {
if (arg_ifindex_permissive &&
sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK))
return 0;

return log_error_errno(r, "Failed to revert interface configuration: %s", bus_error_message(&error, r));
}

return 0;
}
@@ -1815,7 +1816,7 @@ static void help_dns_classes(void) {
}
}

static void help(void) {
static void native_help(void) {
printf("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n"
"%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n"
"%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n"
@@ -1858,7 +1859,7 @@ static void help(void) {
, program_invocation_short_name);
}

static int parse_argv(int argc, char *argv[]) {
static int native_parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_LEGEND,
@@ -1926,7 +1927,7 @@ static int parse_argv(int argc, char *argv[]) {
switch(c) {

case 'h':
help();
native_help();
return 0; /* done */;

case ARG_VERSION:
@@ -2234,7 +2235,10 @@ int main(int argc, char **argv) {
log_parse_environment();
log_open();

r = parse_argv(argc, argv);
if (streq(program_invocation_short_name, "resolvconf"))
r = resolvconf_parse_argv(argc, argv);
else
r = native_parse_argv(argc, argv);
if (r <= 0)
goto finish;

@@ -2435,6 +2439,9 @@ int main(int argc, char **argv) {

r = revert_link(bus);
break;

case _MODE_INVALID:
assert_not_reached("invalid mode");
}

finish:
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: LGPL-2.1+ */

#include <in-addr-util.h>
#include <stdbool.h>
#include <sys/types.h>

extern int arg_ifindex;
extern bool arg_ifindex_permissive;

typedef enum ExecutionMode {
MODE_RESOLVE_HOST,
MODE_RESOLVE_RECORD,
MODE_RESOLVE_SERVICE,
MODE_RESOLVE_OPENPGP,
MODE_RESOLVE_TLSA,
MODE_STATISTICS,
MODE_RESET_STATISTICS,
MODE_FLUSH_CACHES,
MODE_RESET_SERVER_FEATURES,
MODE_STATUS,
MODE_SET_LINK,
MODE_REVERT_LINK,
_MODE_INVALID = -1,
} ExecutionMode;

extern ExecutionMode arg_mode;

extern struct in_addr_data *arg_set_dns;
extern size_t arg_n_set_dns;
extern char **arg_set_domain;
@@ -41,3 +41,6 @@
#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)

#define SD_RESOLVED_QUERY_TIMEOUT_USEC (120 * USEC_PER_SEC)

/* 127.0.0.53 in native endian */
#define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U)
@@ -22,8 +22,5 @@

#include "resolved-manager.h"

/* 127.0.0.53 in native endian */
#define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U)

void manager_dns_stub_stop(Manager *m);
int manager_dns_stub_start(Manager *m);