1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Tom Gundersen <teg@jklm.no>
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.
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.
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/>.
23 #include "alloc-util.h"
24 #include "dns-domain.h"
26 #include "fileio-label.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"
35 static bool file_is_our_own(const struct stat
*st
) {
36 struct stat own1
, own2
;
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
)
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
)
55 int manager_read_resolv_conf(Manager
*m
) {
56 _cleanup_fclose_
FILE *f
= NULL
;
63 /* Reads the system /etc/resolv.conf, if it exists and is not
64 * symlinked to our own resolv.conf instance */
66 if (!m
->read_resolv_conf
)
69 r
= stat("/etc/resolv.conf", &st
);
74 r
= log_warning_errno(errno
, "Failed to stat /etc/resolv.conf: %m");
78 /* Have we already seen the file? */
79 if (timespec_load(&st
.st_mtim
) == m
->resolv_conf_mtime
)
82 if (file_is_our_own(&st
))
85 f
= fopen("/etc/resolv.conf", "re");
90 r
= log_warning_errno(errno
, "Failed to open /etc/resolv.conf: %m");
94 if (fstat(fileno(f
), &st
) < 0) {
95 r
= log_error_errno(errno
, "Failed to stat open file: %m");
99 if (file_is_our_own(&st
))
102 dns_server_mark_all(m
->dns_servers
);
103 dns_search_domain_mark_all(m
->search_domains
);
105 FOREACH_LINE(line
, f
, r
= -errno
; goto clear
) {
110 if (IN_SET(*l
, '#', ';'))
113 a
= first_word(l
, "nameserver");
115 r
= manager_parse_dns_server_string_and_warn(m
, DNS_SERVER_SYSTEM
, a
);
117 log_warning_errno(r
, "Failed to parse DNS server address '%s', ignoring.", a
);
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");
126 r
= manager_parse_search_domains_and_warn(m
, a
);
128 log_warning_errno(r
, "Failed to parse search domain string '%s', ignoring.", a
);
132 m
->resolv_conf_mtime
= timespec_load(&st
.st_mtim
);
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 */
137 dns_server_unlink_marked(m
->dns_servers
);
138 dns_search_domain_unlink_marked(m
->search_domains
);
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
);
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
);
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
);
167 dns_server_unlink_all(m
->dns_servers
);
168 dns_search_domain_unlink_all(m
->search_domains
);
172 static void write_resolv_conf_server(DnsServer
*s
, FILE *f
, unsigned *count
) {
177 if (!dns_server_string(s
)) {
178 log_warning("Our of memory, or invalid DNS address. Ignoring server.");
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
));
193 fputs_unlocked("# Too many DNS servers configured, the following entries may be ignored.\n", f
);
196 fprintf(f
, "nameserver %s\n", dns_server_string(s
));
199 static void write_resolv_conf_search(
202 unsigned length
= 0, count
= 0;
209 fputs_unlocked("search", f
);
211 ORDERED_SET_FOREACH(domain
, domains
, i
) {
212 if (++count
> MAXDNSRCH
) {
213 fputs_unlocked("\n# Too many search domains configured, remaining ones ignored.", f
);
216 length
+= strlen(domain
) + 1;
218 fputs_unlocked("\n# Total length of all search domains is too long, remaining ones ignored.", f
);
221 fputc_unlocked(' ', f
);
222 fputs_unlocked(domain
, f
);
225 fputs_unlocked("\n", f
);
228 static int write_uplink_resolv_conf_contents(FILE *f
, OrderedSet
*dns
, OrderedSet
*domains
) {
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
);
240 if (ordered_set_isempty(dns
))
241 fputs_unlocked("# No DNS servers known.\n", f
);
246 ORDERED_SET_FOREACH(s
, dns
, i
)
247 write_resolv_conf_server(s
, f
, &count
);
250 if (!ordered_set_isempty(domains
))
251 write_resolv_conf_search(domains
, f
);
253 return fflush_and_check(f
);
256 static 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"
259 "# run \"systemd-resolve --status\" to see details about the actual nameservers.\n\n"
260 "nameserver 127.0.0.53\n", f
);
262 if (!ordered_set_isempty(domains
))
263 write_resolv_conf_search(domains
, f
);
265 return fflush_and_check(f
);
268 int manager_write_resolv_conf(Manager
*m
) {
270 _cleanup_ordered_set_free_ OrderedSet
*dns
= NULL
, *domains
= NULL
;
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
;
279 /* Read the system /etc/resolv.conf first */
280 (void) manager_read_resolv_conf(m
);
282 /* Add the full list to a set, to filter out duplicates */
283 r
= manager_compile_dns_servers(m
, &dns
);
285 return log_warning_errno(r
, "Failed to compile list of DNS servers: %m");
287 r
= manager_compile_search_domains(m
, &domains
, false);
289 return log_warning_errno(r
, "Failed to compile list of search domains: %m");
291 r
= fopen_temporary_label(PRIVATE_UPLINK_RESOLV_CONF
, PRIVATE_UPLINK_RESOLV_CONF
, &f_uplink
, &temp_path_uplink
);
293 return log_warning_errno(r
, "Failed to open private resolv.conf file for writing: %m");
294 r
= fopen_temporary_label(PRIVATE_STUB_RESOLV_CONF
, PRIVATE_STUB_RESOLV_CONF
, &f_stub
, &temp_path_stub
);
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);
300 r
= write_uplink_resolv_conf_contents(f_uplink
, dns
, domains
);
302 log_error_errno(r
, "Failed to write private resolv.conf contents: %m");
306 if (rename(temp_path_uplink
, PRIVATE_UPLINK_RESOLV_CONF
) < 0) {
307 r
= log_error_errno(errno
, "Failed to move private resolv.conf file into place: %m");
311 r
= write_stub_resolv_conf_contents(f_stub
, dns
, domains
);
313 log_error_errno(r
, "Failed to write private stub-resolv.conf contents: %m");
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");
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
);