]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-resolv-conf.c
resolved: add a generic DnsSearchDomain concept
[thirdparty/systemd.git] / src / resolve / resolved-resolv-conf.c
CommitLineData
f8dc7e34
LP
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
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)
53 r = 0;
54 else
0eac4623 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
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 manager_mark_dns_servers(m, DNS_SERVER_SYSTEM);
a51c1048 89 dns_search_domain_mark_all(m->search_domains);
f8dc7e34
LP
90
91 FOREACH_LINE(line, f, r = -errno; goto clear) {
f8dc7e34
LP
92 const char *a;
93 char *l;
94
f8dc7e34
LP
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 }
a51c1048
LP
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 }
f8dc7e34
LP
116 }
117
a51c1048
LP
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 */
f8dc7e34 121 manager_flush_marked_dns_servers(m, DNS_SERVER_SYSTEM);
a51c1048 122 dns_search_domain_unlink_marked(m->search_domains);
f8dc7e34
LP
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
136clear:
0eac4623 137 manager_flush_dns_servers(m, DNS_SERVER_SYSTEM);
a51c1048 138 dns_search_domain_unlink_all(m->search_domains);
f8dc7e34
LP
139 return r;
140}
141
142static 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
163static 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
190static 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
222int 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;
a51c1048 227 DnsSearchDomain *d;
f8dc7e34
LP
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
a51c1048
LP
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
f8dc7e34
LP
264 /* Then, add the per-link servers and domains */
265 HASHMAP_FOREACH(l, m->links, i) {
f8dc7e34
LP
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
a51c1048
LP
274 LIST_FOREACH(domains, d, l->search_domains) {
275 r = ordered_set_put(domains, d->name);
f8dc7e34
LP
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
311fail:
312 (void) unlink(path);
313 (void) unlink(temp_path);
314 return r;
315}