]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolvconf-compat.c
resolvectl: change syntax to use verb_dispatch()
[thirdparty/systemd.git] / src / resolve / resolvconf-compat.c
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 "resolvectl.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
48 r = extract_first_word(&string, &word, NULL, 0);
49 if (r < 0)
50 return r;
51 if (r == 0)
52 break;
53
54 if (strv_push(&arg_set_dns, word) < 0)
55 return log_oom();
56 }
57
58 return 0;
59 }
60
61 static int parse_search_domain(const char *string) {
62 int r;
63
64 assert(string);
65
66 for (;;) {
67 _cleanup_free_ char *word = NULL;
68
69 r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES);
70 if (r < 0)
71 return r;
72 if (r == 0)
73 break;
74
75 if (strv_push(&arg_set_domain, word) < 0)
76 return log_oom();
77
78 word = NULL;
79 }
80
81 return 0;
82 }
83
84 int resolvconf_parse_argv(int argc, char *argv[]) {
85
86 enum {
87 ARG_VERSION = 0x100,
88 ARG_ENABLE_UPDATES,
89 ARG_DISABLE_UPDATES,
90 ARG_UPDATES_ARE_ENABLED,
91 };
92
93 static const struct option options[] = {
94 { "help", no_argument, NULL, 'h' },
95 { "version", no_argument, NULL, ARG_VERSION },
96
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 },
101 {}
102 };
103
104
105 enum {
106 TYPE_REGULAR,
107 TYPE_PRIVATE, /* -p: Not supported, treated identically to TYPE_REGULAR */
108 TYPE_EXCLUSIVE, /* -x */
109 } type = TYPE_REGULAR;
110
111 const char *dot, *iface;
112 int c, r;
113
114 assert(argc >= 0);
115 assert(argv);
116
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 */
122
123 arg_mode = _MODE_INVALID;
124
125 while ((c = getopt_long(argc, argv, "hadxpfm:uIi:l:Rr:vV", options, NULL)) >= 0)
126 switch(c) {
127
128 case 'h':
129 resolvconf_help();
130 return 0; /* done */;
131
132 case ARG_VERSION:
133 return version();
134
135 /* -a and -d is what everybody can agree on */
136 case 'a':
137 arg_mode = MODE_SET_LINK;
138 break;
139
140 case 'd':
141 arg_mode = MODE_REVERT_LINK;
142 break;
143
144 /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */
145 case 'x':
146 type = TYPE_EXCLUSIVE;
147 break;
148
149 case 'p':
150 type = TYPE_PRIVATE; /* not actually supported */
151 break;
152
153 case 'f':
154 arg_ifindex_permissive = true;
155 break;
156
157 /* The metrics stuff is an openresolv invention we ignore (and don't really need) */
158 case 'm':
159 log_debug("Switch -%c ignored.", c);
160 break;
161
162 /* Everybody else can agree on the existance of -u but we don't support it. */
163 case 'u':
164
165 /* The following options are openresolv inventions we don't support. */
166 case 'I':
167 case 'i':
168 case 'l':
169 case 'R':
170 case 'r':
171 case 'v':
172 case 'V':
173 log_error("Switch -%c not supported.", c);
174 return -EINVAL;
175
176 /* The Debian resolvconf commands we don't support. */
177 case ARG_ENABLE_UPDATES:
178 log_error("Switch --enable-updates not supported.");
179 return -EINVAL;
180 case ARG_DISABLE_UPDATES:
181 log_error("Switch --disable-updates not supported.");
182 return -EINVAL;
183 case ARG_UPDATES_ARE_ENABLED:
184 log_error("Switch --updates-are-enabled not supported.");
185 return -EINVAL;
186
187 case '?':
188 return -EINVAL;
189
190 default:
191 assert_not_reached("Unhandled option");
192 }
193
194 if (arg_mode == _MODE_INVALID) {
195 log_error("Expected either -a or -d on the command line.");
196 return -EINVAL;
197 }
198
199 if (optind+1 != argc) {
200 log_error("Expected interface name as argument.");
201 return -EINVAL;
202 }
203
204 dot = strchr(argv[optind], '.');
205 if (dot) {
206 iface = strndupa(argv[optind], dot - argv[optind]);
207 log_debug("Ignoring protocol specifier '%s'.", dot + 1);
208 } else
209 iface = argv[optind];
210 optind++;
211
212 if (parse_ifindex(iface, &arg_ifindex) < 0) {
213 int ifi;
214
215 ifi = if_nametoindex(iface);
216 if (ifi <= 0) {
217 if (errno == ENODEV && arg_ifindex_permissive) {
218 log_debug("Interface '%s' not found, but -f specified, ignoring.", iface);
219 return 0; /* done */
220 }
221
222 return log_error_errno(errno, "Unknown interface '%s': %m", iface);
223 }
224
225 arg_ifindex = ifi;
226 arg_ifname = iface;
227 }
228
229 if (arg_mode == MODE_SET_LINK) {
230 unsigned n = 0;
231
232 for (;;) {
233 _cleanup_free_ char *line = NULL;
234 const char *a, *l;
235
236 r = read_line(stdin, LONG_LINE_MAX, &line);
237 if (r < 0)
238 return log_error_errno(r, "Failed to read from stdin: %m");
239 if (r == 0)
240 break;
241
242 n++;
243
244 l = strstrip(line);
245 if (IN_SET(*l, '#', ';', 0))
246 continue;
247
248 a = first_word(l, "nameserver");
249 if (a) {
250 (void) parse_nameserver(a);
251 continue;
252 }
253
254 a = first_word(l, "domain");
255 if (!a)
256 a = first_word(l, "search");
257 if (a) {
258 (void) parse_search_domain(a);
259 continue;
260 }
261
262 log_syntax(NULL, LOG_DEBUG, "stdin", n, 0, "Ignoring resolv.conf line: %s", l);
263 }
264
265 if (type == TYPE_EXCLUSIVE) {
266
267 /* If -x mode is selected, let's preferably route non-suffixed lookups to this interface. This
268 * somewhat matches the original -x behaviour */
269
270 r = strv_extend(&arg_set_domain, "~.");
271 if (r < 0)
272 return log_oom();
273
274 } else if (type == TYPE_PRIVATE)
275 log_debug("Private DNS server data not supported, ignoring.");
276
277 if (!arg_set_dns) {
278 log_error("No DNS servers specified, refusing operation.");
279 return -EINVAL;
280 }
281 }
282
283 return 1; /* work to do */
284 }