]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-resolv-conf.c
resolved: fix a minimal race, when reading /etc/resolv.conf
[thirdparty/systemd.git] / src / resolve / resolved-resolv-conf.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
f8dc7e34
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2014 Tom Gundersen <teg@jklm.no>
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21#include <resolv.h>
22
23#include "alloc-util.h"
24#include "dns-domain.h"
25#include "fd-util.h"
26#include "fileio-label.h"
27#include "fileio.h"
28#include "ordered-set.h"
29#include "resolved-conf.h"
59c0fd0e 30#include "resolved-dns-server.h"
f8dc7e34
LP
31#include "resolved-resolv-conf.h"
32#include "string-util.h"
33#include "strv.h"
34
043d3928
LP
35static bool file_is_our_own(const struct stat *st) {
36 struct stat own1, own2;
37
38 assert(st);
39
40 /* Is it symlinked to our own file? */
41 if (stat(PRIVATE_UPLINK_RESOLV_CONF, &own1) >= 0 &&
42 st->st_dev == own1.st_dev &&
43 st->st_ino == own1.st_ino)
44 return true;
45
46 /* Is it symlinked to our own stub file? */
47 if (stat(PRIVATE_STUB_RESOLV_CONF, &own2) >= 0 &&
48 st->st_dev == own2.st_dev &&
49 st->st_ino == own2.st_ino)
50 return true;
51
52 return false;
53}
54
f8dc7e34
LP
55int manager_read_resolv_conf(Manager *m) {
56 _cleanup_fclose_ FILE *f = NULL;
043d3928 57 struct stat st;
f8dc7e34 58 char line[LINE_MAX];
f8dc7e34
LP
59 usec_t t;
60 int r;
61
62 assert(m);
63
64 /* Reads the system /etc/resolv.conf, if it exists and is not
65 * symlinked to our own resolv.conf instance */
66
67 if (!m->read_resolv_conf)
68 return 0;
69
70 r = stat("/etc/resolv.conf", &st);
71 if (r < 0) {
72 if (errno == ENOENT)
bf7fabd6
LP
73 return 0;
74
75 r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m");
f8dc7e34
LP
76 goto clear;
77 }
78
79 /* Have we already seen the file? */
80 t = timespec_load(&st.st_mtim);
81 if (t == m->resolv_conf_mtime)
82 return 0;
83
043d3928 84 if (file_is_our_own(&st))
bf7fabd6 85 return 0;
f8dc7e34
LP
86
87 f = fopen("/etc/resolv.conf", "re");
88 if (!f) {
89 if (errno == ENOENT)
bf7fabd6
LP
90 return 0;
91
92 r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m");
f8dc7e34
LP
93 goto clear;
94 }
95
96 if (fstat(fileno(f), &st) < 0) {
97 r = log_error_errno(errno, "Failed to stat open file: %m");
98 goto clear;
99 }
100
043d3928
LP
101 if (file_is_our_own(&st))
102 return 0;
103
4b95f179 104 dns_server_mark_all(m->dns_servers);
a51c1048 105 dns_search_domain_mark_all(m->search_domains);
f8dc7e34
LP
106
107 FOREACH_LINE(line, f, r = -errno; goto clear) {
f8dc7e34
LP
108 const char *a;
109 char *l;
110
f8dc7e34 111 l = strstrip(line);
4c701096 112 if (IN_SET(*l, '#', ';'))
f8dc7e34
LP
113 continue;
114
115 a = first_word(l, "nameserver");
116 if (a) {
2817157b 117 r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_SYSTEM, a);
f8dc7e34
LP
118 if (r < 0)
119 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
120
121 continue;
122 }
a51c1048
LP
123
124 a = first_word(l, "domain");
125 if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */
126 a = first_word(l, "search");
127 if (a) {
128 r = manager_parse_search_domains_and_warn(m, a);
129 if (r < 0)
130 log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a);
131 }
f8dc7e34
LP
132 }
133
bf7fabd6
LP
134 m->resolv_conf_mtime = t;
135
a51c1048
LP
136 /* Flush out all servers and search domains that are still
137 * marked. Those are then ones that didn't appear in the new
138 * /etc/resolv.conf */
4b95f179 139 dns_server_unlink_marked(m->dns_servers);
a51c1048 140 dns_search_domain_unlink_marked(m->search_domains);
f8dc7e34
LP
141
142 /* Whenever /etc/resolv.conf changes, start using the first
143 * DNS server of it. This is useful to deal with broken
144 * network managing implementations (like NetworkManager),
145 * that when connecting to a VPN place both the VPN DNS
146 * servers and the local ones in /etc/resolv.conf. Without
147 * resetting the DNS server to use back to the first entry we
148 * will continue to use the local one thus being unable to
149 * resolve VPN domains. */
150 manager_set_dns_server(m, m->dns_servers);
151
452b4e32
LP
152 /* Unconditionally flush the cache when /etc/resolv.conf is
153 * modified, even if the data it contained was completely
154 * identical to the previous version we used. We do this
155 * because altering /etc/resolv.conf is typically done when
156 * the network configuration changes, and that should be
157 * enough to flush the global unicast DNS cache. */
158 if (m->unicast_scope)
159 dns_cache_flush(&m->unicast_scope->cache);
160
59c0fd0e
LP
161 /* If /etc/resolv.conf changed, make sure to forget everything we learned about the DNS servers. After all we
162 * might now talk to a very different DNS server that just happens to have the same IP address as an old one
163 * (think 192.168.1.1). */
164 dns_server_reset_features_all(m->dns_servers);
165
f8dc7e34
LP
166 return 0;
167
168clear:
4b95f179 169 dns_server_unlink_all(m->dns_servers);
a51c1048 170 dns_search_domain_unlink_all(m->search_domains);
f8dc7e34
LP
171 return r;
172}
173
174static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
f8dc7e34
LP
175 assert(s);
176 assert(f);
177 assert(count);
178
2817157b 179 if (!dns_server_string(s)) {
6cb08a89 180 log_warning("Our of memory, or invalid DNS address. Ignoring server.");
f8dc7e34
LP
181 return;
182 }
183
b9fe94ca
MP
184 /* Check if the DNS server is limited to particular domains;
185 * resolv.conf does not have a syntax to express that, so it must not
186 * appear as a global name server to avoid routing unrelated domains to
187 * it (which is a privacy violation, will most probably fail anyway,
188 * and adds unnecessary load) */
189 if (dns_server_limited_domains(s)) {
190 log_debug("DNS server %s has route-only domains, not using as global name server", dns_server_string(s));
191 return;
192 }
193
f8dc7e34 194 if (*count == MAXNS)
4b61c875 195 fputs_unlocked("# Too many DNS servers configured, the following entries may be ignored.\n", f);
313cefa1 196 (*count)++;
f8dc7e34 197
2817157b 198 fprintf(f, "nameserver %s\n", dns_server_string(s));
f8dc7e34
LP
199}
200
201static void write_resolv_conf_search(
d2bc1251
MP
202 OrderedSet *domains,
203 FILE *f) {
204 unsigned length = 0, count = 0;
205 Iterator i;
206 char *domain;
f8dc7e34 207
d2bc1251 208 assert(domains);
f8dc7e34 209 assert(f);
f8dc7e34 210
4b61c875 211 fputs_unlocked("search", f);
f8dc7e34 212
d2bc1251
MP
213 ORDERED_SET_FOREACH(domain, domains, i) {
214 if (++count > MAXDNSRCH) {
4b61c875 215 fputs_unlocked("\n# Too many search domains configured, remaining ones ignored.", f);
d2bc1251
MP
216 break;
217 }
218 length += strlen(domain) + 1;
219 if (length > 256) {
4b61c875 220 fputs_unlocked("\n# Total length of all search domains is too long, remaining ones ignored.", f);
d2bc1251
MP
221 break;
222 }
4b61c875
LP
223 fputc_unlocked(' ', f);
224 fputs_unlocked(domain, f);
f8dc7e34
LP
225 }
226
4b61c875 227 fputs_unlocked("\n", f);
f8dc7e34
LP
228}
229
e6b2d948 230static int write_uplink_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) {
f8dc7e34
LP
231 Iterator i;
232
4b61c875
LP
233 fputs_unlocked("# This file is managed by man:systemd-resolved(8). Do not edit.\n#\n"
234 "# This is a dynamic resolv.conf file for connecting local clients directly to\n"
235 "# all known DNS servers.\n#\n"
236 "# Third party programs must not access this file directly, but only through the\n"
237 "# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,\n"
238 "# replace this symlink by a static file or a different symlink.\n#\n"
239 "# See man:systemd-resolved.service(8) for details about the supported modes of\n"
240 "# operation for /etc/resolv.conf.\n\n", f);
f8dc7e34
LP
241
242 if (ordered_set_isempty(dns))
4b61c875 243 fputs_unlocked("# No DNS servers known.\n", f);
f8dc7e34
LP
244 else {
245 unsigned count = 0;
246 DnsServer *s;
247
248 ORDERED_SET_FOREACH(s, dns, i)
249 write_resolv_conf_server(s, f, &count);
250 }
251
d2bc1251
MP
252 if (!ordered_set_isempty(domains))
253 write_resolv_conf_search(domains, f);
f8dc7e34
LP
254
255 return fflush_and_check(f);
256}
257
e6b2d948
DJL
258static int write_stub_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) {
259 fputs("# This file is managed by man:systemd-resolved(8). Do not edit.\n#\n"
260 "# 127.0.0.53 is the systemd-resolved stub resolver.\n"
21df96c0
YW
261 "# run \"systemd-resolve --status\" to see details about the actual nameservers.\n\n"
262 "nameserver 127.0.0.53\n", f);
e6b2d948
DJL
263
264 if (!ordered_set_isempty(domains))
265 write_resolv_conf_search(domains, f);
266
267 return fflush_and_check(f);
268}
269
f8dc7e34 270int manager_write_resolv_conf(Manager *m) {
9176a57c 271
9176a57c 272 _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL;
e6b2d948
DJL
273 _cleanup_free_ char *temp_path_uplink = NULL;
274 _cleanup_free_ char *temp_path_stub = NULL;
275 _cleanup_fclose_ FILE *f_uplink = NULL;
276 _cleanup_fclose_ FILE *f_stub = NULL;
f8dc7e34
LP
277 int r;
278
279 assert(m);
280
281 /* Read the system /etc/resolv.conf first */
7207052d 282 (void) manager_read_resolv_conf(m);
f8dc7e34
LP
283
284 /* Add the full list to a set, to filter out duplicates */
9176a57c
LP
285 r = manager_compile_dns_servers(m, &dns);
286 if (r < 0)
7207052d 287 return log_warning_errno(r, "Failed to compile list of DNS servers: %m");
f8dc7e34 288
6f7da49d 289 r = manager_compile_search_domains(m, &domains, false);
9176a57c 290 if (r < 0)
7207052d 291 return log_warning_errno(r, "Failed to compile list of search domains: %m");
f8dc7e34 292
e6b2d948 293 r = fopen_temporary_label(PRIVATE_UPLINK_RESOLV_CONF, PRIVATE_UPLINK_RESOLV_CONF, &f_uplink, &temp_path_uplink);
f8dc7e34 294 if (r < 0)
7207052d 295 return log_warning_errno(r, "Failed to open private resolv.conf file for writing: %m");
e6b2d948
DJL
296 r = fopen_temporary_label(PRIVATE_STUB_RESOLV_CONF, PRIVATE_STUB_RESOLV_CONF, &f_stub, &temp_path_stub);
297 if (r < 0)
298 return log_warning_errno(r, "Failed to open private stub-resolv.conf file for writing: %m");
299 (void) fchmod(fileno(f_uplink), 0644);
300 (void) fchmod(fileno(f_stub), 0644);
f8dc7e34 301
e6b2d948 302 r = write_uplink_resolv_conf_contents(f_uplink, dns, domains);
7207052d
LP
303 if (r < 0) {
304 log_error_errno(r, "Failed to write private resolv.conf contents: %m");
f8dc7e34 305 goto fail;
7207052d 306 }
f8dc7e34 307
e6b2d948 308 if (rename(temp_path_uplink, PRIVATE_UPLINK_RESOLV_CONF) < 0) {
7207052d 309 r = log_error_errno(errno, "Failed to move private resolv.conf file into place: %m");
f8dc7e34
LP
310 goto fail;
311 }
312
e6b2d948
DJL
313 r = write_stub_resolv_conf_contents(f_stub, dns, domains);
314 if (r < 0) {
315 log_error_errno(r, "Failed to write private stub-resolv.conf contents: %m");
316 goto fail;
317 }
318
319 if (rename(temp_path_stub, PRIVATE_STUB_RESOLV_CONF) < 0) {
320 r = log_error_errno(errno, "Failed to move private stub-resolv.conf file into place: %m");
321 goto fail;
322 }
323
f8dc7e34
LP
324 return 0;
325
326fail:
e6b2d948
DJL
327 (void) unlink(PRIVATE_UPLINK_RESOLV_CONF);
328 (void) unlink(temp_path_uplink);
329 (void) unlink(PRIVATE_STUB_RESOLV_CONF);
330 (void) unlink(temp_path_stub);
7207052d 331
f8dc7e34
LP
332 return r;
333}