1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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 "pretty-print.h"
13 #include "resolvconf-compat.h"
14 #include "resolvectl.h"
15 #include "resolved-def.h"
16 #include "string-util.h"
17 #include "strv.h"
18 #include "terminal-util.h"
19
resolvconf_help(void)20 static int resolvconf_help(void) {
21 _cleanup_free_ char *link = NULL;
22 int r;
23
24 r = terminal_urlify_man("resolvectl", "1", &link);
25 if (r < 0)
26 return log_oom();
27
28 printf("%1$s -a INTERFACE < FILE\n"
29 "%1$s -d INTERFACE\n"
30 "\n"
31 "Register DNS server and domain configuration with systemd-resolved.\n\n"
32 " -h --help Show this help\n"
33 " --version Show package version\n"
34 " -a Register per-interface DNS server and domain data\n"
35 " -d Unregister per-interface DNS server and domain data\n"
36 " -f Ignore if specified interface does not exist\n"
37 " -x Send DNS traffic preferably over this interface\n"
38 "\n"
39 "This is a compatibility alias for the resolvectl(1) tool, providing native\n"
40 "command line compatibility with the resolvconf(8) tool of various Linux\n"
41 "distributions and BSD systems. Some options supported by other implementations\n"
42 "are not supported and are ignored: -m, -p, -u. Various options supported by other\n"
43 "implementations are not supported and will cause the invocation to fail:\n"
44 "-I, -i, -l, -R, -r, -v, -V, --enable-updates, --disable-updates,\n"
45 "--updates-are-enabled.\n"
46 "\nSee the %2$s for details.\n",
47 program_invocation_short_name,
48 link);
49
50 return 0;
51 }
52
parse_nameserver(const char * string)53 static int parse_nameserver(const char *string) {
54 int r;
55
56 assert(string);
57
58 for (;;) {
59 _cleanup_free_ char *word = NULL;
60
61 r = extract_first_word(&string, &word, NULL, 0);
62 if (r < 0)
63 return r;
64 if (r == 0)
65 break;
66
67 if (strv_push(&arg_set_dns, word) < 0)
68 return log_oom();
69
70 word = NULL;
71 }
72
73 return 0;
74 }
75
parse_search_domain(const char * string)76 static int parse_search_domain(const char *string) {
77 int r;
78
79 assert(string);
80
81 for (;;) {
82 _cleanup_free_ char *word = NULL;
83
84 r = extract_first_word(&string, &word, NULL, EXTRACT_UNQUOTE);
85 if (r < 0)
86 return r;
87 if (r == 0)
88 break;
89
90 if (strv_push(&arg_set_domain, word) < 0)
91 return log_oom();
92
93 word = NULL;
94 }
95
96 return 0;
97 }
98
resolvconf_parse_argv(int argc,char * argv[])99 int resolvconf_parse_argv(int argc, char *argv[]) {
100
101 enum {
102 ARG_VERSION = 0x100,
103 ARG_ENABLE_UPDATES,
104 ARG_DISABLE_UPDATES,
105 ARG_UPDATES_ARE_ENABLED,
106 };
107
108 static const struct option options[] = {
109 { "help", no_argument, NULL, 'h' },
110 { "version", no_argument, NULL, ARG_VERSION },
111
112 /* The following are specific to Debian's original resolvconf */
113 { "enable-updates", no_argument, NULL, ARG_ENABLE_UPDATES },
114 { "disable-updates", no_argument, NULL, ARG_DISABLE_UPDATES },
115 { "updates-are-enabled", no_argument, NULL, ARG_UPDATES_ARE_ENABLED },
116 {}
117 };
118
119 enum {
120 TYPE_REGULAR,
121 TYPE_PRIVATE, /* -p: Not supported, treated identically to TYPE_REGULAR */
122 TYPE_EXCLUSIVE, /* -x */
123 } type = TYPE_REGULAR;
124
125 int c, r;
126
127 assert(argc >= 0);
128 assert(argv);
129
130 /* openresolv checks these environment variables */
131 if (getenv("IF_EXCLUSIVE"))
132 type = TYPE_EXCLUSIVE;
133 if (getenv("IF_PRIVATE"))
134 type = TYPE_PRIVATE; /* not actually supported */
135
136 arg_mode = _MODE_INVALID;
137
138 while ((c = getopt_long(argc, argv, "hadxpfm:uIi:l:Rr:vV", options, NULL)) >= 0)
139 switch (c) {
140
141 case 'h':
142 return resolvconf_help();
143
144 case ARG_VERSION:
145 return version();
146
147 /* -a and -d is what everybody can agree on */
148 case 'a':
149 arg_mode = MODE_SET_LINK;
150 break;
151
152 case 'd':
153 arg_mode = MODE_REVERT_LINK;
154 break;
155
156 /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */
157 case 'x':
158 type = TYPE_EXCLUSIVE;
159 break;
160
161 case 'p':
162 type = TYPE_PRIVATE; /* not actually supported */
163 break;
164
165 case 'f':
166 arg_ifindex_permissive = true;
167 break;
168
169 /* The metrics stuff is an openresolv invention we ignore (and don't really need) */
170 case 'm':
171 log_debug("Switch -%c ignored.", c);
172 break;
173
174 /* -u supposedly should "update all subscribers". We have no subscribers, hence let's make
175 this a NOP, and exit immediately, cleanly. */
176 case 'u':
177 log_info("Switch -%c ignored.", c);
178 return 0;
179
180 /* The following options are openresolv inventions we don't support. */
181 case 'I':
182 case 'i':
183 case 'l':
184 case 'R':
185 case 'r':
186 case 'v':
187 case 'V':
188 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
189 "Switch -%c not supported.", c);
190
191 /* The Debian resolvconf commands we don't support. */
192 case ARG_ENABLE_UPDATES:
193 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
194 "Switch --enable-updates not supported.");
195 case ARG_DISABLE_UPDATES:
196 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
197 "Switch --disable-updates not supported.");
198 case ARG_UPDATES_ARE_ENABLED:
199 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
200 "Switch --updates-are-enabled not supported.");
201
202 case '?':
203 return -EINVAL;
204
205 default:
206 assert_not_reached();
207 }
208
209 if (arg_mode == _MODE_INVALID)
210 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
211 "Expected either -a or -d on the command line.");
212
213 if (optind+1 != argc)
214 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
215 "Expected interface name as argument.");
216
217 r = ifname_resolvconf_mangle(argv[optind]);
218 if (r <= 0)
219 return r;
220
221 optind++;
222
223 if (arg_mode == MODE_SET_LINK) {
224 unsigned n = 0;
225
226 for (;;) {
227 _cleanup_free_ char *line = NULL;
228 const char *a, *l;
229
230 r = read_line(stdin, LONG_LINE_MAX, &line);
231 if (r < 0)
232 return log_error_errno(r, "Failed to read from stdin: %m");
233 if (r == 0)
234 break;
235
236 n++;
237
238 l = strstrip(line);
239 if (IN_SET(*l, '#', ';', 0))
240 continue;
241
242 a = first_word(l, "nameserver");
243 if (a) {
244 (void) parse_nameserver(a);
245 continue;
246 }
247
248 a = first_word(l, "domain");
249 if (!a)
250 a = first_word(l, "search");
251 if (a) {
252 (void) parse_search_domain(a);
253 continue;
254 }
255
256 log_syntax(NULL, LOG_DEBUG, "stdin", n, 0, "Ignoring resolv.conf line: %s", l);
257 }
258
259 if (type == TYPE_EXCLUSIVE) {
260
261 /* If -x mode is selected, let's preferably route non-suffixed lookups to this interface. This
262 * somewhat matches the original -x behaviour */
263
264 r = strv_extend(&arg_set_domain, "~.");
265 if (r < 0)
266 return log_oom();
267
268 } else if (type == TYPE_PRIVATE)
269 log_debug("Private DNS server data not supported, ignoring.");
270
271 if (!arg_set_dns)
272 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
273 "No DNS servers specified, refusing operation.");
274 }
275
276 return 1; /* work to do */
277 }
278