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