]>
Commit | Line | Data |
---|---|---|
088c1363 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | ||
3 | #include <getopt.h> | |
4 | #include <net/if.h> | |
5 | ||
6 | #include "alloc-util.h" | |
7 | #include "def.h" | |
8 | #include "dns-domain.h" | |
9 | #include "extract-word.h" | |
10 | #include "fileio.h" | |
11 | #include "parse-util.h" | |
12 | #include "resolvconf-compat.h" | |
13 | #include "resolve-tool.h" | |
14 | #include "resolved-def.h" | |
15 | #include "string-util.h" | |
16 | #include "strv.h" | |
17 | ||
18 | static void resolvconf_help(void) { | |
19 | printf("%1$s -a INTERFACE < FILE\n" | |
20 | "%1$s -d INTERFACE\n" | |
21 | "\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" | |
29 | "\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); | |
38 | } | |
39 | ||
40 | static int parse_nameserver(const char *string) { | |
41 | int r; | |
42 | ||
43 | assert(string); | |
44 | ||
45 | for (;;) { | |
46 | _cleanup_free_ char *word = NULL; | |
47 | struct in_addr_data data, *n; | |
48 | int ifindex = 0; | |
49 | ||
50 | r = extract_first_word(&string, &word, NULL, 0); | |
51 | if (r < 0) | |
52 | return r; | |
53 | if (r == 0) | |
54 | break; | |
55 | ||
56 | r = in_addr_ifindex_from_string_auto(word, &data.family, &data.address, &ifindex); | |
57 | if (r < 0) | |
58 | return log_error_errno(r, "Failed to parse name server '%s': %m", word); | |
59 | ||
60 | if (ifindex > 0 && ifindex != arg_ifindex) { | |
61 | log_error("Name server interface '%s' does not match selected interface: %m", word); | |
62 | return -EINVAL; | |
63 | } | |
64 | ||
65 | /* Some superficial filtering */ | |
66 | if (in_addr_is_null(data.family, &data.address)) | |
67 | continue; | |
68 | if (data.family == AF_INET && data.address.in.s_addr == htobe32(INADDR_DNS_STUB)) /* resolved's own stub? */ | |
69 | continue; | |
70 | ||
71 | n = reallocarray(arg_set_dns, arg_n_set_dns + 1, sizeof(struct in_addr_data)); | |
72 | if (!n) | |
73 | return log_oom(); | |
74 | arg_set_dns = n; | |
75 | ||
76 | arg_set_dns[arg_n_set_dns++] = data; | |
77 | } | |
78 | ||
79 | return 0; | |
80 | } | |
81 | ||
82 | static int parse_search_domain(const char *string) { | |
83 | int r; | |
84 | ||
85 | assert(string); | |
86 | ||
87 | for (;;) { | |
88 | _cleanup_free_ char *word = NULL; | |
89 | ||
90 | r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); | |
91 | if (r < 0) | |
92 | return r; | |
93 | if (r == 0) | |
94 | break; | |
95 | ||
96 | r = dns_name_is_valid(word); | |
97 | if (r < 0) | |
98 | return log_error_errno(r, "Failed to validate specified domain '%s': %m", word); | |
99 | if (r == 0) { | |
100 | log_error("Domain not valid: %s", word); | |
101 | return -EINVAL; | |
102 | } | |
103 | ||
104 | if (strv_push(&arg_set_domain, word) < 0) | |
105 | return log_oom(); | |
106 | ||
107 | word = NULL; | |
108 | } | |
109 | ||
110 | return 0; | |
111 | } | |
112 | ||
113 | int resolvconf_parse_argv(int argc, char *argv[]) { | |
114 | ||
115 | enum { | |
116 | ARG_VERSION = 0x100, | |
117 | ARG_ENABLE_UPDATES, | |
118 | ARG_DISABLE_UPDATES, | |
119 | ARG_UPDATES_ARE_ENABLED, | |
120 | }; | |
121 | ||
122 | static const struct option options[] = { | |
123 | { "help", no_argument, NULL, 'h' }, | |
124 | { "version", no_argument, NULL, ARG_VERSION }, | |
125 | ||
126 | /* The following are specific to Debian's original resolvconf */ | |
127 | { "enable-updates", no_argument, NULL, ARG_ENABLE_UPDATES }, | |
128 | { "disable-updates", no_argument, NULL, ARG_DISABLE_UPDATES }, | |
129 | { "updates-are-enabled", no_argument, NULL, ARG_UPDATES_ARE_ENABLED }, | |
130 | {} | |
131 | }; | |
132 | ||
133 | ||
134 | enum { | |
135 | TYPE_REGULAR, | |
136 | TYPE_PRIVATE, /* -p: Not supported, treated identically to TYPE_REGULAR */ | |
137 | TYPE_EXCLUSIVE, /* -x */ | |
138 | } type = TYPE_REGULAR; | |
139 | ||
140 | const char *dot, *iface; | |
141 | int c, r; | |
142 | ||
143 | assert(argc >= 0); | |
144 | assert(argv); | |
145 | ||
146 | /* openresolv checks these environment variables */ | |
147 | if (getenv("IF_EXCLUSIVE")) | |
148 | type = TYPE_EXCLUSIVE; | |
149 | if (getenv("IF_PRIVATE")) | |
150 | type = TYPE_PRIVATE; /* not actually supported */ | |
151 | ||
152 | arg_mode = _MODE_INVALID; | |
153 | ||
154 | while ((c = getopt_long(argc, argv, "hadxpfm:uIi:l:Rr:vV", options, NULL)) >= 0) | |
155 | switch(c) { | |
156 | ||
157 | case 'h': | |
158 | resolvconf_help(); | |
159 | return 0; /* done */; | |
160 | ||
161 | case ARG_VERSION: | |
162 | return version(); | |
163 | ||
164 | /* -a and -d is what everybody can agree on */ | |
165 | case 'a': | |
166 | arg_mode = MODE_SET_LINK; | |
167 | break; | |
168 | ||
169 | case 'd': | |
170 | arg_mode = MODE_REVERT_LINK; | |
171 | break; | |
172 | ||
173 | /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ | |
174 | case 'x': | |
175 | type = TYPE_EXCLUSIVE; | |
176 | break; | |
177 | ||
178 | case 'p': | |
179 | type = TYPE_PRIVATE; /* not actually supported */ | |
180 | break; | |
181 | ||
182 | case 'f': | |
183 | arg_ifindex_permissive = true; | |
184 | break; | |
185 | ||
186 | /* The metrics stuff is an openresolv invention we ignore (and don't really need) */ | |
187 | case 'm': | |
188 | log_debug("Switch -%c ignored.", c); | |
189 | break; | |
190 | ||
191 | /* Everybody else can agree on the existance of -u but we don't support it. */ | |
192 | case 'u': | |
193 | ||
194 | /* The following options are openresolv inventions we don't support. */ | |
195 | case 'I': | |
196 | case 'i': | |
197 | case 'l': | |
198 | case 'R': | |
199 | case 'r': | |
200 | case 'v': | |
201 | case 'V': | |
202 | log_error("Switch -%c not supported.", c); | |
203 | return -EINVAL; | |
204 | ||
205 | /* The Debian resolvconf commands we don't support. */ | |
206 | case ARG_ENABLE_UPDATES: | |
207 | log_error("Switch --enable-updates not supported."); | |
208 | return -EINVAL; | |
209 | case ARG_DISABLE_UPDATES: | |
210 | log_error("Switch --disable-updates not supported."); | |
211 | return -EINVAL; | |
212 | case ARG_UPDATES_ARE_ENABLED: | |
213 | log_error("Switch --updates-are-enabled not supported."); | |
214 | return -EINVAL; | |
215 | ||
216 | case '?': | |
217 | return -EINVAL; | |
218 | ||
219 | default: | |
220 | assert_not_reached("Unhandled option"); | |
221 | } | |
222 | ||
223 | if (arg_mode == _MODE_INVALID) { | |
224 | log_error("Expected either -a or -d on the command line."); | |
225 | return -EINVAL; | |
226 | } | |
227 | ||
228 | if (optind+1 != argc) { | |
229 | log_error("Expected interface name as argument."); | |
230 | return -EINVAL; | |
231 | } | |
232 | ||
233 | dot = strchr(argv[optind], '.'); | |
234 | if (dot) { | |
235 | iface = strndupa(argv[optind], dot - argv[optind]); | |
236 | log_debug("Ignoring protocol specifier '%s'.", dot + 1); | |
237 | } else | |
238 | iface = argv[optind]; | |
239 | optind++; | |
240 | ||
241 | if (parse_ifindex(iface, &arg_ifindex) < 0) { | |
242 | int ifi; | |
243 | ||
244 | ifi = if_nametoindex(iface); | |
245 | if (ifi <= 0) { | |
246 | if (errno == ENODEV && arg_ifindex_permissive) { | |
247 | log_debug("Interface '%s' not found, but -f specified, ignoring.", iface); | |
248 | return 0; /* done */ | |
249 | } | |
250 | ||
251 | return log_error_errno(errno, "Unknown interface '%s': %m", iface); | |
252 | } | |
253 | ||
254 | arg_ifindex = ifi; | |
255 | } | |
256 | ||
257 | if (arg_mode == MODE_SET_LINK) { | |
258 | unsigned n = 0; | |
259 | ||
260 | for (;;) { | |
261 | _cleanup_free_ char *line = NULL; | |
262 | const char *a, *l; | |
263 | ||
264 | r = read_line(stdin, LONG_LINE_MAX, &line); | |
265 | if (r < 0) | |
266 | return log_error_errno(r, "Failed to read from stdin: %m"); | |
267 | if (r == 0) | |
268 | break; | |
269 | ||
270 | n++; | |
271 | ||
272 | l = strstrip(line); | |
273 | if (IN_SET(*l, '#', ';', 0)) | |
274 | continue; | |
275 | ||
276 | a = first_word(l, "nameserver"); | |
277 | if (a) { | |
278 | (void) parse_nameserver(a); | |
279 | continue; | |
280 | } | |
281 | ||
282 | a = first_word(l, "domain"); | |
283 | if (!a) | |
284 | a = first_word(l, "search"); | |
285 | if (a) { | |
286 | (void) parse_search_domain(a); | |
287 | continue; | |
288 | } | |
289 | ||
290 | log_syntax(NULL, LOG_DEBUG, "stdin", n, 0, "Ignoring resolv.conf line: %s", l); | |
291 | } | |
292 | ||
293 | if (type == TYPE_EXCLUSIVE) { | |
294 | ||
295 | /* If -x mode is selected, let's preferably route non-suffixed lookups to this interface. This | |
296 | * somewhat matches the original -x behaviour */ | |
297 | ||
298 | r = strv_extend(&arg_set_domain, "~."); | |
299 | if (r < 0) | |
300 | return log_oom(); | |
301 | ||
302 | } else if (type == TYPE_PRIVATE) | |
303 | log_debug("Private DNS server data not supported, ignoring."); | |
304 | ||
305 | if (arg_n_set_dns == 0) { | |
306 | log_error("No DNS servers specified, refusing operation."); | |
307 | return -EINVAL; | |
308 | } | |
309 | } | |
310 | ||
311 | return 1; /* work to do */ | |
312 | } |