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