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