Version bump to 5.8.2rc1
[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 <collections/array.h>
26 #include <threading/mutex.h>
27
28 /* path to resolvconf executable */
29 #define RESOLVCONF_EXEC "/sbin/resolvconf"
30
31 /* default prefix used for resolvconf interfaces (should have high prio) */
32 #define RESOLVCONF_PREFIX "lo.inet.ipsec."
33
34 typedef struct private_resolve_handler_t private_resolve_handler_t;
35
36 /**
37 * Private data of an resolve_handler_t object.
38 */
39 struct private_resolve_handler_t {
40
41 /**
42 * Public resolve_handler_t interface.
43 */
44 resolve_handler_t public;
45
46 /**
47 * resolv.conf file to use
48 */
49 char *file;
50
51 /**
52 * Use resolvconf instead of writing directly to resolv.conf
53 */
54 bool use_resolvconf;
55
56 /**
57 * Prefix to be used for interface names sent to resolvconf
58 */
59 char *iface_prefix;
60
61 /**
62 * Mutex to access file exclusively
63 */
64 mutex_t *mutex;
65
66 /**
67 * Reference counting for DNS servers dns_server_t
68 */
69 array_t *servers;
70 };
71
72 /**
73 * Reference counting for DNS servers
74 */
75 typedef struct {
76
77 /**
78 * DNS server address
79 */
80 host_t *server;
81
82 /**
83 * Reference count
84 */
85 u_int refcount;
86
87 } dns_server_t;
88
89 /**
90 * Compare a server and a stored reference
91 */
92 static int dns_server_find(const void *a, const void *b)
93 {
94 host_t *server = (host_t*)a;
95 dns_server_t *item = (dns_server_t*)b;
96 return chunk_compare(server->get_address(server),
97 item->server->get_address(item->server));
98 }
99
100 /**
101 * Sort references by DNS server
102 */
103 static int dns_server_sort(const void *a, const void *b, void *user)
104 {
105 const dns_server_t *da = a, *db = b;
106 return chunk_compare(da->server->get_address(da->server),
107 db->server->get_address(db->server));
108 }
109
110 /**
111 * Writes the given nameserver to resolv.conf
112 */
113 static bool write_nameserver(private_resolve_handler_t *this, host_t *addr)
114 {
115 FILE *in, *out;
116 char buf[1024];
117 size_t len;
118 bool handled = FALSE;
119
120 in = fopen(this->file, "r");
121 /* allows us to stream from in to out */
122 unlink(this->file);
123 out = fopen(this->file, "w");
124 if (out)
125 {
126 fprintf(out, "nameserver %H # by strongSwan\n", addr);
127 DBG1(DBG_IKE, "installing DNS server %H to %s", addr, this->file);
128 handled = TRUE;
129
130 /* copy rest of the file */
131 if (in)
132 {
133 while ((len = fread(buf, 1, sizeof(buf), in)))
134 {
135 ignore_result(fwrite(buf, 1, len, out));
136 }
137 }
138 fclose(out);
139 }
140 if (in)
141 {
142 fclose(in);
143 }
144 return handled;
145 }
146
147 /**
148 * Removes the given nameserver from resolv.conf
149 */
150 static void remove_nameserver(private_resolve_handler_t *this, host_t *addr)
151 {
152 FILE *in, *out;
153 char line[1024], matcher[512];
154
155 in = fopen(this->file, "r");
156 if (in)
157 {
158 /* allows us to stream from in to out */
159 unlink(this->file);
160 out = fopen(this->file, "w");
161 if (out)
162 {
163 snprintf(matcher, sizeof(matcher),
164 "nameserver %H # by strongSwan\n", addr);
165
166 /* copy all, but matching line */
167 while (fgets(line, sizeof(line), in))
168 {
169 if (strpfx(line, matcher))
170 {
171 DBG1(DBG_IKE, "removing DNS server %H from %s",
172 addr, this->file);
173 }
174 else
175 {
176 fputs(line, out);
177 }
178 }
179 fclose(out);
180 }
181 fclose(in);
182 }
183 }
184
185 /**
186 * Add or remove the given nameserver by invoking resolvconf.
187 */
188 static bool invoke_resolvconf(private_resolve_handler_t *this, host_t *addr,
189 bool install)
190 {
191 process_t *process;
192 FILE *shell;
193 int in, out, retval;
194
195 /* we use the nameserver's IP address as part of the interface name to
196 * make them unique */
197 process = process_start_shell(NULL, install ? &in : NULL, &out, NULL,
198 "2>&1 %s %s %s%H", RESOLVCONF_EXEC,
199 install ? "-a" : "-d", this->iface_prefix, addr);
200
201 if (!process)
202 {
203 return FALSE;
204 }
205 if (install)
206 {
207 shell = fdopen(in, "w");
208 if (shell)
209 {
210 DBG1(DBG_IKE, "installing DNS server %H via resolvconf", addr);
211 fprintf(shell, "nameserver %H\n", addr);
212 fclose(shell);
213 }
214 else
215 {
216 close(in);
217 close(out);
218 process->wait(process, NULL);
219 return FALSE;
220 }
221 }
222 else
223 {
224 DBG1(DBG_IKE, "removing DNS server %H via resolvconf", addr);
225 }
226 shell = fdopen(out, "r");
227 if (shell)
228 {
229 while (TRUE)
230 {
231 char resp[128], *e;
232
233 if (fgets(resp, sizeof(resp), shell) == NULL)
234 {
235 if (ferror(shell))
236 {
237 DBG1(DBG_IKE, "error reading from resolvconf");
238 }
239 break;
240 }
241 else
242 {
243 e = resp + strlen(resp);
244 if (e > resp && e[-1] == '\n')
245 {
246 e[-1] = '\0';
247 }
248 DBG1(DBG_IKE, "resolvconf: %s", resp);
249 }
250 }
251 fclose(shell);
252 }
253 else
254 {
255 close(out);
256 }
257 if (!process->wait(process, &retval) || retval != EXIT_SUCCESS)
258 {
259 if (install)
260 { /* revert changes when installing fails */
261 invoke_resolvconf(this, addr, FALSE);
262 return FALSE;
263 }
264 }
265 return TRUE;
266 }
267
268 METHOD(attribute_handler_t, handle, bool,
269 private_resolve_handler_t *this, ike_sa_t *ike_sa,
270 configuration_attribute_type_t type, chunk_t data)
271 {
272 dns_server_t *found = NULL;
273 host_t *addr;
274 bool handled;
275
276 switch (type)
277 {
278 case INTERNAL_IP4_DNS:
279 addr = host_create_from_chunk(AF_INET, data, 0);
280 break;
281 case INTERNAL_IP6_DNS:
282 addr = host_create_from_chunk(AF_INET6, data, 0);
283 break;
284 default:
285 return FALSE;
286 }
287
288 if (!addr || addr->is_anyaddr(addr))
289 {
290 DESTROY_IF(addr);
291 return FALSE;
292 }
293
294 this->mutex->lock(this->mutex);
295 if (array_bsearch(this->servers, addr, dns_server_find, &found) == -1)
296 {
297 if (this->use_resolvconf)
298 {
299 handled = invoke_resolvconf(this, addr, TRUE);
300 }
301 else
302 {
303 handled = write_nameserver(this, addr);
304 }
305 if (handled)
306 {
307 INIT(found,
308 .server = addr->clone(addr),
309 .refcount = 1,
310 );
311 array_insert_create(&this->servers, ARRAY_TAIL, found);
312 array_sort(this->servers, dns_server_sort, NULL);
313 }
314 }
315 else
316 {
317 DBG1(DBG_IKE, "DNS server %H already installed, increasing refcount",
318 addr);
319 found->refcount++;
320 handled = TRUE;
321 }
322 this->mutex->unlock(this->mutex);
323 addr->destroy(addr);
324
325 if (!handled)
326 {
327 DBG1(DBG_IKE, "adding DNS server failed");
328 }
329 return handled;
330 }
331
332 METHOD(attribute_handler_t, release, void,
333 private_resolve_handler_t *this, ike_sa_t *ike_sa,
334 configuration_attribute_type_t type, chunk_t data)
335 {
336 dns_server_t *found = NULL;
337 host_t *addr;
338 int family, idx;
339
340 switch (type)
341 {
342 case INTERNAL_IP4_DNS:
343 family = AF_INET;
344 break;
345 case INTERNAL_IP6_DNS:
346 family = AF_INET6;
347 break;
348 default:
349 return;
350 }
351 addr = host_create_from_chunk(family, data, 0);
352
353 this->mutex->lock(this->mutex);
354 idx = array_bsearch(this->servers, addr, dns_server_find, &found);
355 if (idx != -1)
356 {
357 if (--found->refcount > 0)
358 {
359 DBG1(DBG_IKE, "DNS server %H still used, decreasing refcount",
360 addr);
361 }
362 else
363 {
364 if (this->use_resolvconf)
365 {
366 invoke_resolvconf(this, addr, FALSE);
367 }
368 else
369 {
370 remove_nameserver(this, addr);
371 }
372 array_remove(this->servers, idx, NULL);
373 found->server->destroy(found->server);
374 free(found);
375 }
376 }
377 this->mutex->unlock(this->mutex);
378
379 addr->destroy(addr);
380 }
381
382 /**
383 * Attribute enumerator implementation
384 */
385 typedef struct {
386 /** implements enumerator_t interface */
387 enumerator_t public;
388 /** request IPv4 DNS? */
389 bool v4;
390 /** request IPv6 DNS? */
391 bool v6;
392 } attribute_enumerator_t;
393
394 METHOD(enumerator_t, attribute_enumerate, bool,
395 attribute_enumerator_t *this, va_list args)
396 {
397 configuration_attribute_type_t *type;
398 chunk_t *data;
399
400 VA_ARGS_VGET(args, type, data);
401 if (this->v4)
402 {
403 *type = INTERNAL_IP4_DNS;
404 *data = chunk_empty;
405 this->v4 = FALSE;
406 return TRUE;
407 }
408 if (this->v6)
409 {
410 *type = INTERNAL_IP6_DNS;
411 *data = chunk_empty;
412 this->v6 = FALSE;
413 return TRUE;
414 }
415 return FALSE;
416 }
417
418 /**
419 * Check if a list has a host of given family
420 */
421 static bool has_host_family(linked_list_t *list, int family)
422 {
423 enumerator_t *enumerator;
424 host_t *host;
425 bool found = FALSE;
426
427 enumerator = list->create_enumerator(list);
428 while (enumerator->enumerate(enumerator, &host))
429 {
430 if (host->get_family(host) == family)
431 {
432 found = TRUE;
433 break;
434 }
435 }
436 enumerator->destroy(enumerator);
437
438 return found;
439 }
440
441 METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t*,
442 private_resolve_handler_t *this, ike_sa_t *ike_sa,
443 linked_list_t *vips)
444 {
445 attribute_enumerator_t *enumerator;
446
447 INIT(enumerator,
448 .public = {
449 .enumerate = enumerator_enumerate_default,
450 .venumerate = _attribute_enumerate,
451 .destroy = (void*)free,
452 },
453 .v4 = has_host_family(vips, AF_INET),
454 .v6 = has_host_family(vips, AF_INET6),
455 );
456 return &enumerator->public;
457 }
458
459 METHOD(resolve_handler_t, destroy, void,
460 private_resolve_handler_t *this)
461 {
462 array_destroy(this->servers);
463 this->mutex->destroy(this->mutex);
464 free(this);
465 }
466
467 /**
468 * See header
469 */
470 resolve_handler_t *resolve_handler_create()
471 {
472 private_resolve_handler_t *this;
473 struct stat st;
474
475 INIT(this,
476 .public = {
477 .handler = {
478 .handle = _handle,
479 .release = _release,
480 .create_attribute_enumerator = _create_attribute_enumerator,
481 },
482 .destroy = _destroy,
483 },
484 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
485 .file = lib->settings->get_str(lib->settings, "%s.plugins.resolve.file",
486 RESOLV_CONF, lib->ns),
487 );
488
489 if (stat(RESOLVCONF_EXEC, &st) == 0)
490 {
491 this->use_resolvconf = TRUE;
492 this->iface_prefix = lib->settings->get_str(lib->settings,
493 "%s.plugins.resolve.resolvconf.iface_prefix",
494 RESOLVCONF_PREFIX, lib->ns);
495 }
496
497 return &this->public;
498 }