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