]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-resolv-conf.c
resolved: unify DnsServer handling code between Link and Manager
[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 r = 0;
54 else
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 m->resolv_conf_mtime = t;
65
66 /* Is it symlinked to our own file? */
67 if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 &&
68 st.st_dev == own.st_dev &&
69 st.st_ino == own.st_ino) {
70 r = 0;
71 goto clear;
72 }
73
74 f = fopen("/etc/resolv.conf", "re");
75 if (!f) {
76 if (errno == ENOENT)
77 r = 0;
78 else
79 r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m");
80 goto clear;
81 }
82
83 if (fstat(fileno(f), &st) < 0) {
84 r = log_error_errno(errno, "Failed to stat open file: %m");
85 goto clear;
86 }
87
88 dns_server_mark_all(m->dns_servers);
89 dns_search_domain_mark_all(m->search_domains);
90
91 FOREACH_LINE(line, f, r = -errno; goto clear) {
92 const char *a;
93 char *l;
94
95 l = strstrip(line);
96 if (*l == '#' || *l == ';')
97 continue;
98
99 a = first_word(l, "nameserver");
100 if (a) {
101 r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a);
102 if (r < 0)
103 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
104
105 continue;
106 }
107
108 a = first_word(l, "domain");
109 if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */
110 a = first_word(l, "search");
111 if (a) {
112 r = manager_parse_search_domains_and_warn(m, a);
113 if (r < 0)
114 log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a);
115 }
116 }
117
118 /* Flush out all servers and search domains that are still
119 * marked. Those are then ones that didn't appear in the new
120 * /etc/resolv.conf */
121 dns_server_unlink_marked(m->dns_servers);
122 dns_search_domain_unlink_marked(m->search_domains);
123
124 /* Whenever /etc/resolv.conf changes, start using the first
125 * DNS server of it. This is useful to deal with broken
126 * network managing implementations (like NetworkManager),
127 * that when connecting to a VPN place both the VPN DNS
128 * servers and the local ones in /etc/resolv.conf. Without
129 * resetting the DNS server to use back to the first entry we
130 * will continue to use the local one thus being unable to
131 * resolve VPN domains. */
132 manager_set_dns_server(m, m->dns_servers);
133
134 return 0;
135
136 clear:
137 dns_server_unlink_all(m->dns_servers);
138 dns_search_domain_unlink_all(m->search_domains);
139 return r;
140 }
141
142 static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
143 _cleanup_free_ char *t = NULL;
144 int r;
145
146 assert(s);
147 assert(f);
148 assert(count);
149
150 r = in_addr_to_string(s->family, &s->address, &t);
151 if (r < 0) {
152 log_warning_errno(r, "Invalid DNS address. Ignoring: %m");
153 return;
154 }
155
156 if (*count == MAXNS)
157 fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f);
158 (*count) ++;
159
160 fprintf(f, "nameserver %s\n", t);
161 }
162
163 static void write_resolv_conf_search(
164 const char *domain,
165 FILE *f,
166 unsigned *count,
167 unsigned *length) {
168
169 assert(domain);
170 assert(f);
171 assert(length);
172
173 if (*count >= MAXDNSRCH ||
174 *length + strlen(domain) > 256) {
175 if (*count == MAXDNSRCH)
176 fputs(" # Too many search domains configured, remaining ones ignored.", f);
177 if (*length <= 256)
178 fputs(" # Total length of all search domains is too long, remaining ones ignored.", f);
179
180 return;
181 }
182
183 (*length) += strlen(domain);
184 (*count) ++;
185
186 fputc(' ', f);
187 fputs(domain, f);
188 }
189
190 static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) {
191 Iterator i;
192
193 fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n"
194 "# Third party programs must not access this file directly, but\n"
195 "# only through the symlink at /etc/resolv.conf. To manage\n"
196 "# resolv.conf(5) in a different way, replace the symlink by a\n"
197 "# static file or a different symlink.\n\n", f);
198
199 if (ordered_set_isempty(dns))
200 fputs("# No DNS servers known.\n", f);
201 else {
202 unsigned count = 0;
203 DnsServer *s;
204
205 ORDERED_SET_FOREACH(s, dns, i)
206 write_resolv_conf_server(s, f, &count);
207 }
208
209 if (!ordered_set_isempty(domains)) {
210 unsigned length = 0, count = 0;
211 char *domain;
212
213 fputs("search", f);
214 ORDERED_SET_FOREACH(domain, domains, i)
215 write_resolv_conf_search(domain, f, &count, &length);
216 fputs("\n", f);
217 }
218
219 return fflush_and_check(f);
220 }
221
222 int manager_write_resolv_conf(Manager *m) {
223 static const char path[] = "/run/systemd/resolve/resolv.conf";
224 _cleanup_free_ char *temp_path = NULL;
225 _cleanup_fclose_ FILE *f = NULL;
226 _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL;
227 DnsSearchDomain *d;
228 DnsServer *s;
229 Iterator i;
230 Link *l;
231 int r;
232
233 assert(m);
234
235 /* Read the system /etc/resolv.conf first */
236 manager_read_resolv_conf(m);
237
238 /* Add the full list to a set, to filter out duplicates */
239 dns = ordered_set_new(&dns_server_hash_ops);
240 if (!dns)
241 return -ENOMEM;
242
243 domains = ordered_set_new(&dns_name_hash_ops);
244 if (!domains)
245 return -ENOMEM;
246
247 /* First add the system-wide servers and domains */
248 LIST_FOREACH(servers, s, m->dns_servers) {
249 r = ordered_set_put(dns, s);
250 if (r == -EEXIST)
251 continue;
252 if (r < 0)
253 return r;
254 }
255
256 LIST_FOREACH(domains, d, m->search_domains) {
257 r = ordered_set_put(domains, d->name);
258 if (r == -EEXIST)
259 continue;
260 if (r < 0)
261 return r;
262 }
263
264 /* Then, add the per-link servers and domains */
265 HASHMAP_FOREACH(l, m->links, i) {
266 LIST_FOREACH(servers, s, l->dns_servers) {
267 r = ordered_set_put(dns, s);
268 if (r == -EEXIST)
269 continue;
270 if (r < 0)
271 return r;
272 }
273
274 LIST_FOREACH(domains, d, l->search_domains) {
275 r = ordered_set_put(domains, d->name);
276 if (r == -EEXIST)
277 continue;
278 if (r < 0)
279 return r;
280 }
281 }
282
283 /* If we found nothing, add the fallback servers */
284 if (ordered_set_isempty(dns)) {
285 LIST_FOREACH(servers, s, m->fallback_dns_servers) {
286 r = ordered_set_put(dns, s);
287 if (r == -EEXIST)
288 continue;
289 if (r < 0)
290 return r;
291 }
292 }
293
294 r = fopen_temporary_label(path, path, &f, &temp_path);
295 if (r < 0)
296 return r;
297
298 fchmod(fileno(f), 0644);
299
300 r = write_resolv_conf_contents(f, dns, domains);
301 if (r < 0)
302 goto fail;
303
304 if (rename(temp_path, path) < 0) {
305 r = -errno;
306 goto fail;
307 }
308
309 return 0;
310
311 fail:
312 (void) unlink(path);
313 (void) unlink(temp_path);
314 return r;
315 }