resolve: Use process abstraction when calling resolvconf
[strongswan.git] / src / libcharon / plugins / resolve / resolve_handler.c
1 /*
2 * Copyright (C) 2012-2016 Tobias Brunner
3 * Copyright (C) 2009 Martin Willi
4 * HSR Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #include "resolve_handler.h"
18
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22
23 #include <utils/debug.h>
24 #include <utils/process.h>
25 #include <threading/mutex.h>
26
27 /* path to resolvconf executable */
28 #define RESOLVCONF_EXEC "/sbin/resolvconf"
29
30 /* default prefix used for resolvconf interfaces (should have high prio) */
31 #define RESOLVCONF_PREFIX "lo.inet.ipsec."
32
33 typedef struct private_resolve_handler_t private_resolve_handler_t;
34
35 /**
36 * Private data of an resolve_handler_t object.
37 */
38 struct private_resolve_handler_t {
39
40 /**
41 * Public resolve_handler_t interface.
42 */
43 resolve_handler_t public;
44
45 /**
46 * resolv.conf file to use
47 */
48 char *file;
49
50 /**
51 * use resolvconf instead of writing directly to resolv.conf
52 */
53 bool use_resolvconf;
54
55 /**
56 * prefix to be used for interface names sent to resolvconf
57 */
58 char *iface_prefix;
59
60 /**
61 * Mutex to access file exclusively
62 */
63 mutex_t *mutex;
64 };
65
66 /**
67 * Writes the given nameserver to resolv.conf
68 */
69 static bool write_nameserver(private_resolve_handler_t *this,
70 identification_t *server, host_t *addr)
71 {
72 FILE *in, *out;
73 char buf[1024];
74 size_t len;
75 bool handled = FALSE;
76
77 in = fopen(this->file, "r");
78 /* allows us to stream from in to out */
79 unlink(this->file);
80 out = fopen(this->file, "w");
81 if (out)
82 {
83 fprintf(out, "nameserver %H # by strongSwan, from %Y\n", addr,
84 server);
85 DBG1(DBG_IKE, "installing DNS server %H to %s", addr, this->file);
86 handled = TRUE;
87
88 /* copy rest of the file */
89 if (in)
90 {
91 while ((len = fread(buf, 1, sizeof(buf), in)))
92 {
93 ignore_result(fwrite(buf, 1, len, out));
94 }
95 }
96 fclose(out);
97 }
98 if (in)
99 {
100 fclose(in);
101 }
102 return handled;
103 }
104
105 /**
106 * Removes the given nameserver from resolv.conf
107 */
108 static void remove_nameserver(private_resolve_handler_t *this,
109 identification_t *server, host_t *addr)
110 {
111 FILE *in, *out;
112 char line[1024], matcher[512];
113
114 in = fopen(this->file, "r");
115 if (in)
116 {
117 /* allows us to stream from in to out */
118 unlink(this->file);
119 out = fopen(this->file, "w");
120 if (out)
121 {
122 snprintf(matcher, sizeof(matcher),
123 "nameserver %H # by strongSwan, from %Y\n",
124 addr, server);
125
126 /* copy all, but matching line */
127 while (fgets(line, sizeof(line), in))
128 {
129 if (strpfx(line, matcher))
130 {
131 DBG1(DBG_IKE, "removing DNS server %H from %s",
132 addr, this->file);
133 }
134 else
135 {
136 fputs(line, out);
137 }
138 }
139 fclose(out);
140 }
141 fclose(in);
142 }
143 }
144
145 /**
146 * Add or remove the given nameserver by invoking resolvconf.
147 */
148 static bool invoke_resolvconf(private_resolve_handler_t *this,
149 identification_t *server, host_t *addr,
150 bool install)
151 {
152 process_t *process;
153 FILE *shell;
154 int in, out, retval;
155
156 /* we use the nameserver's IP address as part of the interface name to
157 * make them unique */
158 process = process_start_shell(NULL, install ? &in : NULL, &out, NULL,
159 "2>&1 %s %s %s%H", RESOLVCONF_EXEC,
160 install ? "-a" : "-d", this->iface_prefix, addr);
161
162 if (!process)
163 {
164 return FALSE;
165 }
166 if (install)
167 {
168 shell = fdopen(in, "w");
169 if (shell)
170 {
171 DBG1(DBG_IKE, "installing DNS server %H via resolvconf", addr);
172 fprintf(shell, "nameserver %H\n", addr);
173 fclose(shell);
174 }
175 else
176 {
177 close(in);
178 close(out);
179 process->wait(process, NULL);
180 return FALSE;
181 }
182 }
183 else
184 {
185 DBG1(DBG_IKE, "removing DNS server %H via resolvconf", addr);
186 }
187 shell = fdopen(out, "r");
188 if (shell)
189 {
190 while (TRUE)
191 {
192 char resp[128], *e;
193
194 if (fgets(resp, sizeof(resp), shell) == NULL)
195 {
196 if (ferror(shell))
197 {
198 DBG1(DBG_IKE, "error reading from resolvconf");
199 }
200 break;
201 }
202 else
203 {
204 e = resp + strlen(resp);
205 if (e > resp && e[-1] == '\n')
206 {
207 e[-1] = '\0';
208 }
209 DBG1(DBG_IKE, "resolvconf: %s", resp);
210 }
211 }
212 fclose(shell);
213 }
214 else
215 {
216 close(out);
217 }
218 if (!process->wait(process, &retval) || retval != EXIT_SUCCESS)
219 {
220 if (install)
221 { /* revert changes when installing fails */
222 invoke_resolvconf(this, server, addr, FALSE);
223 return FALSE;
224 }
225 }
226 return TRUE;
227 }
228
229 METHOD(attribute_handler_t, handle, bool,
230 private_resolve_handler_t *this, ike_sa_t *ike_sa,
231 configuration_attribute_type_t type, chunk_t data)
232 {
233 identification_t *server;
234 host_t *addr;
235 bool handled;
236
237 switch (type)
238 {
239 case INTERNAL_IP4_DNS:
240 addr = host_create_from_chunk(AF_INET, data, 0);
241 break;
242 case INTERNAL_IP6_DNS:
243 addr = host_create_from_chunk(AF_INET6, data, 0);
244 break;
245 default:
246 return FALSE;
247 }
248
249 if (!addr || addr->is_anyaddr(addr))
250 {
251 DESTROY_IF(addr);
252 return FALSE;
253 }
254 server = ike_sa->get_other_id(ike_sa);
255
256 this->mutex->lock(this->mutex);
257 if (this->use_resolvconf)
258 {
259 handled = invoke_resolvconf(this, server, addr, TRUE);
260 }
261 else
262 {
263 handled = write_nameserver(this, server, addr);
264 }
265 this->mutex->unlock(this->mutex);
266 addr->destroy(addr);
267
268 if (!handled)
269 {
270 DBG1(DBG_IKE, "adding DNS server failed");
271 }
272 return handled;
273 }
274
275 METHOD(attribute_handler_t, release, void,
276 private_resolve_handler_t *this, ike_sa_t *ike_sa,
277 configuration_attribute_type_t type, chunk_t data)
278 {
279 identification_t *server;
280 host_t *addr;
281 int family;
282
283 switch (type)
284 {
285 case INTERNAL_IP4_DNS:
286 family = AF_INET;
287 break;
288 case INTERNAL_IP6_DNS:
289 family = AF_INET6;
290 break;
291 default:
292 return;
293 }
294 addr = host_create_from_chunk(family, data, 0);
295 server = ike_sa->get_other_id(ike_sa);
296
297 this->mutex->lock(this->mutex);
298 if (this->use_resolvconf)
299 {
300 invoke_resolvconf(this, server, addr, FALSE);
301 }
302 else
303 {
304 remove_nameserver(this, server, addr);
305 }
306 this->mutex->unlock(this->mutex);
307
308 addr->destroy(addr);
309 }
310
311 /**
312 * Attribute enumerator implementation
313 */
314 typedef struct {
315 /** implements enumerator_t interface */
316 enumerator_t public;
317 /** request IPv4 DNS? */
318 bool v4;
319 /** request IPv6 DNS? */
320 bool v6;
321 } attribute_enumerator_t;
322
323 static bool attribute_enumerate(attribute_enumerator_t *this,
324 configuration_attribute_type_t *type,
325 chunk_t *data)
326 {
327 if (this->v4)
328 {
329 *type = INTERNAL_IP4_DNS;
330 *data = chunk_empty;
331 this->v4 = FALSE;
332 return TRUE;
333 }
334 if (this->v6)
335 {
336 *type = INTERNAL_IP6_DNS;
337 *data = chunk_empty;
338 this->v6 = FALSE;
339 return TRUE;
340 }
341 return FALSE;
342 }
343
344 /**
345 * Check if a list has a host of given family
346 */
347 static bool has_host_family(linked_list_t *list, int family)
348 {
349 enumerator_t *enumerator;
350 host_t *host;
351 bool found = FALSE;
352
353 enumerator = list->create_enumerator(list);
354 while (enumerator->enumerate(enumerator, &host))
355 {
356 if (host->get_family(host) == family)
357 {
358 found = TRUE;
359 break;
360 }
361 }
362 enumerator->destroy(enumerator);
363
364 return found;
365 }
366
367 METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t*,
368 private_resolve_handler_t *this, ike_sa_t *ike_sa,
369 linked_list_t *vips)
370 {
371 attribute_enumerator_t *enumerator;
372
373 INIT(enumerator,
374 .public = {
375 .enumerate = (void*)attribute_enumerate,
376 .destroy = (void*)free,
377 },
378 .v4 = has_host_family(vips, AF_INET),
379 .v6 = has_host_family(vips, AF_INET6),
380 );
381 return &enumerator->public;
382 }
383
384 METHOD(resolve_handler_t, destroy, void,
385 private_resolve_handler_t *this)
386 {
387 this->mutex->destroy(this->mutex);
388 free(this);
389 }
390
391 /**
392 * See header
393 */
394 resolve_handler_t *resolve_handler_create()
395 {
396 private_resolve_handler_t *this;
397 struct stat st;
398
399 INIT(this,
400 .public = {
401 .handler = {
402 .handle = _handle,
403 .release = _release,
404 .create_attribute_enumerator = _create_attribute_enumerator,
405 },
406 .destroy = _destroy,
407 },
408 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
409 .file = lib->settings->get_str(lib->settings, "%s.plugins.resolve.file",
410 RESOLV_CONF, lib->ns),
411 );
412
413 if (stat(RESOLVCONF_EXEC, &st) == 0)
414 {
415 this->use_resolvconf = TRUE;
416 this->iface_prefix = lib->settings->get_str(lib->settings,
417 "%s.plugins.resolve.resolvconf.iface_prefix",
418 RESOLVCONF_PREFIX, lib->ns);
419 }
420
421 return &this->public;
422 }