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