]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-resolv-conf.c
license: LGPL-2.1+ -> LGPL-2.1-or-later
[thirdparty/systemd.git] / src / resolve / resolved-resolv-conf.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
f8dc7e34
LP
2
3#include <resolv.h>
ca78ad1d
ZJS
4#include <sys/stat.h>
5#include <sys/types.h>
6#include <unistd.h>
f8dc7e34
LP
7
8#include "alloc-util.h"
9#include "dns-domain.h"
10#include "fd-util.h"
f8dc7e34 11#include "fileio.h"
ca8b81d9 12#include "fs-util.h"
f8dc7e34
LP
13#include "ordered-set.h"
14#include "resolved-conf.h"
59c0fd0e 15#include "resolved-dns-server.h"
f8dc7e34 16#include "resolved-resolv-conf.h"
61c12865 17#include "stat-util.h"
4261ab65 18#include "string-table.h"
f8dc7e34
LP
19#include "string-util.h"
20#include "strv.h"
e4de7287 21#include "tmpfile-util-label.h"
f8dc7e34 22
f43580f1 23int manager_check_resolv_conf(const Manager *m) {
ca8b81d9 24 struct stat st, own;
f43580f1
YW
25
26 assert(m);
27
28 /* This warns only when our stub listener is disabled and /etc/resolv.conf is a symlink to
ca8b81d9 29 * PRIVATE_STATIC_RESOLV_CONF. */
f43580f1
YW
30
31 if (m->dns_stub_listener_mode != DNS_STUB_LISTENER_NO)
32 return 0;
33
ca8b81d9 34 if (stat("/etc/resolv.conf", &st) < 0) {
f43580f1
YW
35 if (errno == ENOENT)
36 return 0;
37
38 return log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m");
39 }
40
ca8b81d9
ZJS
41 /* Is it symlinked to our own uplink file? */
42 if (stat(PRIVATE_STATIC_RESOLV_CONF, &own) >= 0 &&
43 st.st_dev == own.st_dev &&
44 st.st_ino == own.st_ino)
45 return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
46 "DNSStubListener= is disabled, but /etc/resolv.conf is a symlink to "
47 PRIVATE_STATIC_RESOLV_CONF " which expects DNSStubListener= to be enabled.");
f43580f1
YW
48
49 return 0;
50}
51
043d3928 52static bool file_is_our_own(const struct stat *st) {
b6de578d 53 const char *path;
043d3928
LP
54
55 assert(st);
56
b6de578d
LP
57 FOREACH_STRING(path,
58 PRIVATE_UPLINK_RESOLV_CONF,
59 PRIVATE_STUB_RESOLV_CONF,
60 PRIVATE_STATIC_RESOLV_CONF) {
61
62 struct stat own;
63
64 /* Is it symlinked to our own uplink file? */
65 if (stat(path, &own) >= 0 &&
66 st->st_dev == own.st_dev &&
67 st->st_ino == own.st_ino)
68 return true;
69 }
043d3928
LP
70
71 return false;
72}
73
f8dc7e34
LP
74int manager_read_resolv_conf(Manager *m) {
75 _cleanup_fclose_ FILE *f = NULL;
043d3928 76 struct stat st;
b351c300 77 unsigned n = 0;
f8dc7e34
LP
78 int r;
79
80 assert(m);
81
82 /* Reads the system /etc/resolv.conf, if it exists and is not
83 * symlinked to our own resolv.conf instance */
84
85 if (!m->read_resolv_conf)
86 return 0;
87
88 r = stat("/etc/resolv.conf", &st);
89 if (r < 0) {
90 if (errno == ENOENT)
bf7fabd6
LP
91 return 0;
92
93 r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m");
f8dc7e34
LP
94 goto clear;
95 }
96
97 /* Have we already seen the file? */
61c12865 98 if (stat_inode_unmodified(&st, &m->resolv_conf_stat))
f8dc7e34
LP
99 return 0;
100
043d3928 101 if (file_is_our_own(&st))
bf7fabd6 102 return 0;
f8dc7e34
LP
103
104 f = fopen("/etc/resolv.conf", "re");
105 if (!f) {
106 if (errno == ENOENT)
bf7fabd6
LP
107 return 0;
108
109 r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m");
f8dc7e34
LP
110 goto clear;
111 }
112
113 if (fstat(fileno(f), &st) < 0) {
114 r = log_error_errno(errno, "Failed to stat open file: %m");
115 goto clear;
116 }
117
043d3928
LP
118 if (file_is_our_own(&st))
119 return 0;
120
4b95f179 121 dns_server_mark_all(m->dns_servers);
a51c1048 122 dns_search_domain_mark_all(m->search_domains);
f8dc7e34 123
e1b9fc23
LP
124 for (;;) {
125 _cleanup_free_ char *line = NULL;
f8dc7e34
LP
126 const char *a;
127 char *l;
128
e1b9fc23
LP
129 r = read_line(f, LONG_LINE_MAX, &line);
130 if (r < 0) {
131 log_error_errno(r, "Failed to read /etc/resolv.conf: %m");
132 goto clear;
133 }
134 if (r == 0)
135 break;
136
b351c300
LP
137 n++;
138
f8dc7e34 139 l = strstrip(line);
b351c300 140 if (IN_SET(*l, '#', ';', 0))
f8dc7e34
LP
141 continue;
142
143 a = first_word(l, "nameserver");
144 if (a) {
2817157b 145 r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_SYSTEM, a);
f8dc7e34
LP
146 if (r < 0)
147 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
148
149 continue;
150 }
a51c1048
LP
151
152 a = first_word(l, "domain");
153 if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */
154 a = first_word(l, "search");
155 if (a) {
156 r = manager_parse_search_domains_and_warn(m, a);
157 if (r < 0)
158 log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a);
02c20535
LP
159
160 continue;
a51c1048 161 }
b351c300
LP
162
163 log_syntax(NULL, LOG_DEBUG, "/etc/resolv.conf", n, 0, "Ignoring resolv.conf line: %s", l);
f8dc7e34
LP
164 }
165
61c12865 166 m->resolv_conf_stat = st;
bf7fabd6 167
a51c1048
LP
168 /* Flush out all servers and search domains that are still
169 * marked. Those are then ones that didn't appear in the new
170 * /etc/resolv.conf */
4b95f179 171 dns_server_unlink_marked(m->dns_servers);
a51c1048 172 dns_search_domain_unlink_marked(m->search_domains);
f8dc7e34
LP
173
174 /* Whenever /etc/resolv.conf changes, start using the first
175 * DNS server of it. This is useful to deal with broken
176 * network managing implementations (like NetworkManager),
177 * that when connecting to a VPN place both the VPN DNS
178 * servers and the local ones in /etc/resolv.conf. Without
179 * resetting the DNS server to use back to the first entry we
180 * will continue to use the local one thus being unable to
181 * resolve VPN domains. */
182 manager_set_dns_server(m, m->dns_servers);
183
452b4e32
LP
184 /* Unconditionally flush the cache when /etc/resolv.conf is
185 * modified, even if the data it contained was completely
186 * identical to the previous version we used. We do this
187 * because altering /etc/resolv.conf is typically done when
188 * the network configuration changes, and that should be
189 * enough to flush the global unicast DNS cache. */
190 if (m->unicast_scope)
191 dns_cache_flush(&m->unicast_scope->cache);
192
59c0fd0e
LP
193 /* If /etc/resolv.conf changed, make sure to forget everything we learned about the DNS servers. After all we
194 * might now talk to a very different DNS server that just happens to have the same IP address as an old one
195 * (think 192.168.1.1). */
196 dns_server_reset_features_all(m->dns_servers);
197
f8dc7e34
LP
198 return 0;
199
200clear:
4b95f179 201 dns_server_unlink_all(m->dns_servers);
a51c1048 202 dns_search_domain_unlink_all(m->search_domains);
f8dc7e34
LP
203 return r;
204}
205
206static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
f76fa088
LP
207 DnsScope *scope;
208
f8dc7e34
LP
209 assert(s);
210 assert(f);
211 assert(count);
212
2817157b 213 if (!dns_server_string(s)) {
be4bf266 214 log_warning("Out of memory, or invalid DNS address. Ignoring server.");
f8dc7e34
LP
215 return;
216 }
217
ca5394d2
LP
218 /* Check if the scope this DNS server belongs to is suitable as 'default' route for lookups; resolv.conf does
219 * not have a syntax to express that, so it must not appear as a global name server to avoid routing unrelated
220 * domains to it (which is a privacy violation, will most probably fail anyway, and adds unnecessary load) */
f76fa088 221 scope = dns_server_scope(s);
ca5394d2 222 if (scope && !dns_scope_is_default_route(scope)) {
f76fa088 223 log_debug("Scope of DNS server %s has only route-only domains, not using as global name server", dns_server_string(s));
b9fe94ca
MP
224 return;
225 }
226
f8dc7e34 227 if (*count == MAXNS)
0d536673 228 fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f);
313cefa1 229 (*count)++;
f8dc7e34 230
2817157b 231 fprintf(f, "nameserver %s\n", dns_server_string(s));
f8dc7e34
LP
232}
233
234static void write_resolv_conf_search(
d2bc1251
MP
235 OrderedSet *domains,
236 FILE *f) {
d2bc1251 237 char *domain;
f8dc7e34 238
d2bc1251 239 assert(domains);
f8dc7e34 240 assert(f);
f8dc7e34 241
0d536673 242 fputs("search", f);
f8dc7e34 243
90e74a66 244 ORDERED_SET_FOREACH(domain, domains) {
0d536673
LP
245 fputc(' ', f);
246 fputs(domain, f);
f8dc7e34
LP
247 }
248
0d536673 249 fputs("\n", f);
f8dc7e34
LP
250}
251
e6b2d948 252static int write_uplink_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) {
f8dc7e34 253
0d536673
LP
254 fputs("# This file is managed by man:systemd-resolved(8). Do not edit.\n"
255 "#\n"
256 "# This is a dynamic resolv.conf file for connecting local clients directly to\n"
257 "# all known uplink DNS servers. This file lists all configured search domains.\n"
258 "#\n"
ce416f42
LP
259 "# Third party programs should typically not access this file directly, but only\n"
260 "# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a\n"
261 "# different way, replace this symlink by a static file or a different symlink.\n"
0d536673
LP
262 "#\n"
263 "# See man:systemd-resolved.service(8) for details about the supported modes of\n"
264 "# operation for /etc/resolv.conf.\n"
265 "\n", f);
f8dc7e34
LP
266
267 if (ordered_set_isempty(dns))
0d536673 268 fputs("# No DNS servers known.\n", f);
f8dc7e34
LP
269 else {
270 unsigned count = 0;
271 DnsServer *s;
272
90e74a66 273 ORDERED_SET_FOREACH(s, dns)
f8dc7e34
LP
274 write_resolv_conf_server(s, f, &count);
275 }
276
b3ffa2b5 277 if (ordered_set_isempty(domains))
31619e2f
ZJS
278 fputs("search .\n", f); /* Make sure that if the local hostname is chosen as fqdn this does not
279 * imply a search domain */
b3ffa2b5 280 else
d2bc1251 281 write_resolv_conf_search(domains, f);
f8dc7e34
LP
282
283 return fflush_and_check(f);
284}
285
e6b2d948 286static int write_stub_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) {
ce416f42
LP
287 fputs("# This file is managed by man:systemd-resolved(8). Do not edit.\n"
288 "#\n"
289 "# This is a dynamic resolv.conf file for connecting local clients to the\n"
290 "# internal DNS stub resolver of systemd-resolved. This file lists all\n"
291 "# configured search domains.\n"
292 "#\n"
293 "# Run \"resolvectl status\" to see details about the uplink DNS servers\n"
294 "# currently in use.\n"
295 "#\n"
296 "# Third party programs should typically not access this file directly, but only\n"
297 "# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a\n"
298 "# different way, replace this symlink by a static file or a different symlink.\n"
299 "#\n"
300 "# See man:systemd-resolved.service(8) for details about the supported modes of\n"
301 "# operation for /etc/resolv.conf.\n"
302 "\n"
303 "nameserver 127.0.0.53\n"
a742f982 304 "options edns0 trust-ad\n", f);
e6b2d948 305
b3ffa2b5 306 if (ordered_set_isempty(domains))
31619e2f
ZJS
307 fputs("search .\n", f); /* Make sure that if the local hostname is chosen as fqdn this does not
308 * imply a search domain */
b3ffa2b5 309 else
e6b2d948
DJL
310 write_resolv_conf_search(domains, f);
311
312 return fflush_and_check(f);
313}
314
f8dc7e34 315int manager_write_resolv_conf(Manager *m) {
9176a57c 316 _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL;
0d536673
LP
317 _cleanup_free_ char *temp_path_uplink = NULL, *temp_path_stub = NULL;
318 _cleanup_fclose_ FILE *f_uplink = NULL, *f_stub = NULL;
f8dc7e34
LP
319 int r;
320
321 assert(m);
322
323 /* Read the system /etc/resolv.conf first */
7207052d 324 (void) manager_read_resolv_conf(m);
f8dc7e34
LP
325
326 /* Add the full list to a set, to filter out duplicates */
9176a57c
LP
327 r = manager_compile_dns_servers(m, &dns);
328 if (r < 0)
7207052d 329 return log_warning_errno(r, "Failed to compile list of DNS servers: %m");
f8dc7e34 330
6f7da49d 331 r = manager_compile_search_domains(m, &domains, false);
9176a57c 332 if (r < 0)
7207052d 333 return log_warning_errno(r, "Failed to compile list of search domains: %m");
f8dc7e34 334
e6b2d948 335 r = fopen_temporary_label(PRIVATE_UPLINK_RESOLV_CONF, PRIVATE_UPLINK_RESOLV_CONF, &f_uplink, &temp_path_uplink);
f8dc7e34 336 if (r < 0)
5c35cd5f 337 return log_warning_errno(r, "Failed to open new %s for writing: %m", PRIVATE_UPLINK_RESOLV_CONF);
0d536673 338
0d536673
LP
339 (void) fchmod(fileno(f_uplink), 0644);
340
e6b2d948 341 r = write_uplink_resolv_conf_contents(f_uplink, dns, domains);
7207052d 342 if (r < 0) {
5c35cd5f 343 log_error_errno(r, "Failed to write new %s: %m", PRIVATE_UPLINK_RESOLV_CONF);
f8dc7e34 344 goto fail;
7207052d 345 }
f8dc7e34 346
ca8b81d9
ZJS
347 if (m->dns_stub_listener_mode != DNS_STUB_LISTENER_NO) {
348 r = fopen_temporary_label(PRIVATE_STUB_RESOLV_CONF, PRIVATE_STUB_RESOLV_CONF, &f_stub, &temp_path_stub);
349 if (r < 0) {
350 log_warning_errno(r, "Failed to open new %s for writing: %m", PRIVATE_STUB_RESOLV_CONF);
351 goto fail;
352 }
f8dc7e34 353
ca8b81d9 354 (void) fchmod(fileno(f_stub), 0644);
965228a8 355
ca8b81d9
ZJS
356 r = write_stub_resolv_conf_contents(f_stub, dns, domains);
357 if (r < 0) {
358 log_error_errno(r, "Failed to write new %s: %m", PRIVATE_STUB_RESOLV_CONF);
359 goto fail;
360 }
361
362 if (rename(temp_path_stub, PRIVATE_STUB_RESOLV_CONF) < 0)
363 r = log_error_errno(errno, "Failed to move new %s into place: %m", PRIVATE_STUB_RESOLV_CONF);
364
365 } else {
366 r = symlink_atomic(basename(PRIVATE_UPLINK_RESOLV_CONF), PRIVATE_STUB_RESOLV_CONF);
367 if (r < 0)
368 log_error_errno(r, "Failed to symlink %s: %m", PRIVATE_STUB_RESOLV_CONF);
e6b2d948
DJL
369 }
370
965228a8
ZJS
371 if (rename(temp_path_uplink, PRIVATE_UPLINK_RESOLV_CONF) < 0)
372 r = log_error_errno(errno, "Failed to move new %s into place: %m", PRIVATE_UPLINK_RESOLV_CONF);
e6b2d948 373
965228a8
ZJS
374 fail:
375 if (r < 0) {
376 /* Something went wrong, perform cleanup... */
377 (void) unlink(temp_path_uplink);
378 (void) unlink(temp_path_stub);
379 }
7207052d 380
f8dc7e34
LP
381 return r;
382}
4261ab65
LP
383
384int resolv_conf_mode(void) {
385 static const char * const table[_RESOLV_CONF_MODE_MAX] = {
386 [RESOLV_CONF_UPLINK] = PRIVATE_UPLINK_RESOLV_CONF,
387 [RESOLV_CONF_STUB] = PRIVATE_STUB_RESOLV_CONF,
388 [RESOLV_CONF_STATIC] = PRIVATE_STATIC_RESOLV_CONF,
389 };
390
391 struct stat system_st;
392
393 if (stat("/etc/resolv.conf", &system_st) < 0) {
394 if (errno == ENOENT)
395 return RESOLV_CONF_MISSING;
396
397 return -errno;
398 }
399
400 for (ResolvConfMode m = 0; m < _RESOLV_CONF_MODE_MAX; m++) {
401 struct stat our_st;
402
403 if (!table[m])
404 continue;
405
406 if (stat(table[m], &our_st) < 0) {
407 if (errno != ENOENT)
408 log_debug_errno(errno, "Failed to stat() %s, ignoring: %m", table[m]);
409
410 continue;
411 }
412
413 if (system_st.st_dev == our_st.st_dev &&
414 system_st.st_ino == our_st.st_ino)
415 return m;
416 }
417
418 return RESOLV_CONF_FOREIGN;
419}
420
421static const char* const resolv_conf_mode_table[_RESOLV_CONF_MODE_MAX] = {
422 [RESOLV_CONF_UPLINK] = "uplink",
423 [RESOLV_CONF_STUB] = "stub",
424 [RESOLV_CONF_STATIC] = "static",
425 [RESOLV_CONF_MISSING] = "missing",
426 [RESOLV_CONF_FOREIGN] = "foreign",
427};
428DEFINE_STRING_TABLE_LOOKUP(resolv_conf_mode, ResolvConfMode);