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