]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-resolv-conf.c
resolved: split out calls to compile full list of dns servers and search domains
[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
224 #define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf"
225
226 _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL;
227 _cleanup_free_ char *temp_path = NULL;
228 _cleanup_fclose_ FILE *f = NULL;
229 int r;
230
231 assert(m);
232
233 /* Read the system /etc/resolv.conf first */
234 manager_read_resolv_conf(m);
235
236 /* Add the full list to a set, to filter out duplicates */
237 r = manager_compile_dns_servers(m, &dns);
238 if (r < 0)
239 return r;
240
241 r = manager_compile_search_domains(m, &domains);
242 if (r < 0)
243 return r;
244
245 r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path);
246 if (r < 0)
247 return r;
248
249 fchmod(fileno(f), 0644);
250
251 r = write_resolv_conf_contents(f, dns, domains);
252 if (r < 0)
253 goto fail;
254
255 if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) {
256 r = -errno;
257 goto fail;
258 }
259
260 return 0;
261
262 fail:
263 (void) unlink(PRIVATE_RESOLV_CONF);
264 (void) unlink(temp_path);
265 return r;
266 }