]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-resolv-conf.c
Merge pull request #2409 from snakeroot/dropin-doc-2
[thirdparty/systemd.git] / src / resolve / resolved-resolv-conf.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2014 Tom Gundersen <teg@jklm.no>
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <resolv.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-resolv-conf.h"
32 #include "string-util.h"
33 #include "strv.h"
34
35 int manager_read_resolv_conf(Manager *m) {
36 _cleanup_fclose_ FILE *f = NULL;
37 struct stat st, own;
38 char line[LINE_MAX];
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)
53 return 0;
54
55 r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m");
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
64 /* Is it symlinked to our own file? */
65 if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 &&
66 st.st_dev == own.st_dev &&
67 st.st_ino == own.st_ino)
68 return 0;
69
70 f = fopen("/etc/resolv.conf", "re");
71 if (!f) {
72 if (errno == ENOENT)
73 return 0;
74
75 r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m");
76 goto clear;
77 }
78
79 if (fstat(fileno(f), &st) < 0) {
80 r = log_error_errno(errno, "Failed to stat open file: %m");
81 goto clear;
82 }
83
84 dns_server_mark_all(m->dns_servers);
85 dns_search_domain_mark_all(m->search_domains);
86
87 FOREACH_LINE(line, f, r = -errno; goto clear) {
88 const char *a;
89 char *l;
90
91 l = strstrip(line);
92 if (*l == '#' || *l == ';')
93 continue;
94
95 a = first_word(l, "nameserver");
96 if (a) {
97 r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a);
98 if (r < 0)
99 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
100
101 continue;
102 }
103
104 a = first_word(l, "domain");
105 if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */
106 a = first_word(l, "search");
107 if (a) {
108 r = manager_parse_search_domains_and_warn(m, a);
109 if (r < 0)
110 log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a);
111 }
112 }
113
114 m->resolv_conf_mtime = t;
115
116 /* Flush out all servers and search domains that are still
117 * marked. Those are then ones that didn't appear in the new
118 * /etc/resolv.conf */
119 dns_server_unlink_marked(m->dns_servers);
120 dns_search_domain_unlink_marked(m->search_domains);
121
122 /* Whenever /etc/resolv.conf changes, start using the first
123 * DNS server of it. This is useful to deal with broken
124 * network managing implementations (like NetworkManager),
125 * that when connecting to a VPN place both the VPN DNS
126 * servers and the local ones in /etc/resolv.conf. Without
127 * resetting the DNS server to use back to the first entry we
128 * will continue to use the local one thus being unable to
129 * resolve VPN domains. */
130 manager_set_dns_server(m, m->dns_servers);
131
132 /* Unconditionally flush the cache when /etc/resolv.conf is
133 * modified, even if the data it contained was completely
134 * identical to the previous version we used. We do this
135 * because altering /etc/resolv.conf is typically done when
136 * the network configuration changes, and that should be
137 * enough to flush the global unicast DNS cache. */
138 if (m->unicast_scope)
139 dns_cache_flush(&m->unicast_scope->cache);
140
141 return 0;
142
143 clear:
144 dns_server_unlink_all(m->dns_servers);
145 dns_search_domain_unlink_all(m->search_domains);
146 return r;
147 }
148
149 static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
150 assert(s);
151 assert(f);
152 assert(count);
153
154 (void) dns_server_string(s);
155
156 if (!s->server_string) {
157 log_warning("Our of memory, or invalid DNS address. Ignoring server.");
158 return;
159 }
160
161 if (*count == MAXNS)
162 fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f);
163 (*count) ++;
164
165 fprintf(f, "nameserver %s\n", s->server_string);
166 }
167
168 static void write_resolv_conf_search(
169 const char *domain,
170 FILE *f,
171 unsigned *count,
172 unsigned *length) {
173
174 assert(domain);
175 assert(f);
176 assert(length);
177
178 if (*count >= MAXDNSRCH ||
179 *length + strlen(domain) > 256) {
180 if (*count == MAXDNSRCH)
181 fputs(" # Too many search domains configured, remaining ones ignored.", f);
182 if (*length <= 256)
183 fputs(" # Total length of all search domains is too long, remaining ones ignored.", f);
184
185 return;
186 }
187
188 (*length) += strlen(domain);
189 (*count) ++;
190
191 fputc(' ', f);
192 fputs(domain, f);
193 }
194
195 static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) {
196 Iterator i;
197
198 fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n"
199 "# Third party programs must not access this file directly, but\n"
200 "# only through the symlink at /etc/resolv.conf. To manage\n"
201 "# resolv.conf(5) in a different way, replace the symlink by a\n"
202 "# static file or a different symlink.\n\n", f);
203
204 if (ordered_set_isempty(dns))
205 fputs("# No DNS servers known.\n", f);
206 else {
207 unsigned count = 0;
208 DnsServer *s;
209
210 ORDERED_SET_FOREACH(s, dns, i)
211 write_resolv_conf_server(s, f, &count);
212 }
213
214 if (!ordered_set_isempty(domains)) {
215 unsigned length = 0, count = 0;
216 char *domain;
217
218 fputs("search", f);
219 ORDERED_SET_FOREACH(domain, domains, i)
220 write_resolv_conf_search(domain, f, &count, &length);
221 fputs("\n", f);
222 }
223
224 return fflush_and_check(f);
225 }
226
227 int manager_write_resolv_conf(Manager *m) {
228
229 #define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf"
230
231 _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL;
232 _cleanup_free_ char *temp_path = NULL;
233 _cleanup_fclose_ FILE *f = NULL;
234 int r;
235
236 assert(m);
237
238 /* Read the system /etc/resolv.conf first */
239 manager_read_resolv_conf(m);
240
241 /* Add the full list to a set, to filter out duplicates */
242 r = manager_compile_dns_servers(m, &dns);
243 if (r < 0)
244 return r;
245
246 r = manager_compile_search_domains(m, &domains);
247 if (r < 0)
248 return r;
249
250 r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path);
251 if (r < 0)
252 return r;
253
254 fchmod(fileno(f), 0644);
255
256 r = write_resolv_conf_contents(f, dns, domains);
257 if (r < 0)
258 goto fail;
259
260 if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) {
261 r = -errno;
262 goto fail;
263 }
264
265 return 0;
266
267 fail:
268 (void) unlink(PRIVATE_RESOLV_CONF);
269 (void) unlink(temp_path);
270 return r;
271 }