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