1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "alloc-util.h"
8 #include "dns-domain.h"
9 #include "extract-word.h"
11 #include "parse-util.h"
12 #include "resolvconf-compat.h"
13 #include "resolvectl.h"
14 #include "resolved-def.h"
15 #include "string-util.h"
18 static void resolvconf_help(void) {
19 printf("%1$s -a INTERFACE < FILE\n"
22 "Register DNS server and domain configuration with systemd-resolved.\n\n"
23 " -h --help Show this help\n"
24 " --version Show package version\n"
25 " -a Register per-interface DNS server and domain data\n"
26 " -d Unregister per-interface DNS server and domain data\n"
27 " -f Ignore if specified interface does not exist\n"
28 " -x Send DNS traffic preferably over this interface\n"
30 "This is a compatibility alias for the systemd-resolve(1) tool, providing native\n"
31 "command line compatibility with the resolvconf(8) tool of various Linux\n"
32 "distributions and BSD systems. Some options supported by other implementations\n"
33 "are not supported and are ignored: -m, -p. Various options supported by other\n"
34 "implementations are not supported and will cause the invocation to fail: -u,\n"
35 "-I, -i, -l, -R, -r, -v, -V, --enable-updates, --disable-updates,\n"
36 "--updates-are-enabled.\n"
37 , program_invocation_short_name
);
40 static int parse_nameserver(const char *string
) {
46 _cleanup_free_
char *word
= NULL
;
48 r
= extract_first_word(&string
, &word
, NULL
, 0);
54 if (strv_push(&arg_set_dns
, word
) < 0)
61 static int parse_search_domain(const char *string
) {
67 _cleanup_free_
char *word
= NULL
;
69 r
= extract_first_word(&string
, &word
, NULL
, EXTRACT_QUOTES
);
75 if (strv_push(&arg_set_domain
, word
) < 0)
84 int resolvconf_parse_argv(int argc
, char *argv
[]) {
90 ARG_UPDATES_ARE_ENABLED
,
93 static const struct option options
[] = {
94 { "help", no_argument
, NULL
, 'h' },
95 { "version", no_argument
, NULL
, ARG_VERSION
},
97 /* The following are specific to Debian's original resolvconf */
98 { "enable-updates", no_argument
, NULL
, ARG_ENABLE_UPDATES
},
99 { "disable-updates", no_argument
, NULL
, ARG_DISABLE_UPDATES
},
100 { "updates-are-enabled", no_argument
, NULL
, ARG_UPDATES_ARE_ENABLED
},
107 TYPE_PRIVATE
, /* -p: Not supported, treated identically to TYPE_REGULAR */
108 TYPE_EXCLUSIVE
, /* -x */
109 } type
= TYPE_REGULAR
;
111 const char *dot
, *iface
;
117 /* openresolv checks these environment variables */
118 if (getenv("IF_EXCLUSIVE"))
119 type
= TYPE_EXCLUSIVE
;
120 if (getenv("IF_PRIVATE"))
121 type
= TYPE_PRIVATE
; /* not actually supported */
123 arg_mode
= _MODE_INVALID
;
125 while ((c
= getopt_long(argc
, argv
, "hadxpfm:uIi:l:Rr:vV", options
, NULL
)) >= 0)
130 return 0; /* done */;
135 /* -a and -d is what everybody can agree on */
137 arg_mode
= MODE_SET_LINK
;
141 arg_mode
= MODE_REVERT_LINK
;
144 /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */
146 type
= TYPE_EXCLUSIVE
;
150 type
= TYPE_PRIVATE
; /* not actually supported */
154 arg_ifindex_permissive
= true;
157 /* The metrics stuff is an openresolv invention we ignore (and don't really need) */
159 log_debug("Switch -%c ignored.", c
);
162 /* Everybody else can agree on the existance of -u but we don't support it. */
165 /* The following options are openresolv inventions we don't support. */
173 log_error("Switch -%c not supported.", c
);
176 /* The Debian resolvconf commands we don't support. */
177 case ARG_ENABLE_UPDATES
:
178 log_error("Switch --enable-updates not supported.");
180 case ARG_DISABLE_UPDATES
:
181 log_error("Switch --disable-updates not supported.");
183 case ARG_UPDATES_ARE_ENABLED
:
184 log_error("Switch --updates-are-enabled not supported.");
191 assert_not_reached("Unhandled option");
194 if (arg_mode
== _MODE_INVALID
) {
195 log_error("Expected either -a or -d on the command line.");
199 if (optind
+1 != argc
) {
200 log_error("Expected interface name as argument.");
204 dot
= strchr(argv
[optind
], '.');
206 iface
= strndupa(argv
[optind
], dot
- argv
[optind
]);
207 log_debug("Ignoring protocol specifier '%s'.", dot
+ 1);
209 iface
= argv
[optind
];
212 if (parse_ifindex(iface
, &arg_ifindex
) < 0) {
215 ifi
= if_nametoindex(iface
);
217 if (errno
== ENODEV
&& arg_ifindex_permissive
) {
218 log_debug("Interface '%s' not found, but -f specified, ignoring.", iface
);
222 return log_error_errno(errno
, "Unknown interface '%s': %m", iface
);
229 if (arg_mode
== MODE_SET_LINK
) {
233 _cleanup_free_
char *line
= NULL
;
236 r
= read_line(stdin
, LONG_LINE_MAX
, &line
);
238 return log_error_errno(r
, "Failed to read from stdin: %m");
245 if (IN_SET(*l
, '#', ';', 0))
248 a
= first_word(l
, "nameserver");
250 (void) parse_nameserver(a
);
254 a
= first_word(l
, "domain");
256 a
= first_word(l
, "search");
258 (void) parse_search_domain(a
);
262 log_syntax(NULL
, LOG_DEBUG
, "stdin", n
, 0, "Ignoring resolv.conf line: %s", l
);
265 if (type
== TYPE_EXCLUSIVE
) {
267 /* If -x mode is selected, let's preferably route non-suffixed lookups to this interface. This
268 * somewhat matches the original -x behaviour */
270 r
= strv_extend(&arg_set_domain
, "~.");
274 } else if (type
== TYPE_PRIVATE
)
275 log_debug("Private DNS server data not supported, ignoring.");
278 log_error("No DNS servers specified, refusing operation.");
283 return 1; /* work to do */