]> git.ipfire.org Git - thirdparty/strongswan.git/blame - src/libcharon/plugins/updown/updown_listener.c
updown: Pass interface ID to updown script
[thirdparty/strongswan.git] / src / libcharon / plugins / updown / updown_listener.c
CommitLineData
49653b6b 1/*
cf4a7395 2 * Copyright (C) 2013 Tobias Brunner
49653b6b 3 * Copyright (C) 2008 Martin Willi
0d7202c7
AS
4 * Copyright (C) 2016 Andreas Steffen
5 * HSR Hochschule fuer Technik Rapperswil
49653b6b
MW
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.
49653b6b
MW
16 */
17
18#define _GNU_SOURCE
19#include <stdio.h>
6890bdc7 20#include <unistd.h>
49653b6b
MW
21
22#include "updown_listener.h"
23
6890bdc7 24#include <utils/process.h>
49653b6b
MW
25#include <daemon.h>
26#include <config/child_cfg.h>
27
28typedef struct private_updown_listener_t private_updown_listener_t;
29
30/**
31 * Private data of an updown_listener_t object.
32 */
33struct private_updown_listener_t {
7daf5226 34
49653b6b
MW
35 /**
36 * Public updown_listener_t interface.
37 */
38 updown_listener_t public;
7daf5226 39
49653b6b
MW
40 /**
41 * List of cached interface names
42 */
43 linked_list_t *iface_cache;
e0d3014a
MW
44
45 /**
46 * DNS attribute handler
47 */
48 updown_handler_t *handler;
49653b6b
MW
49};
50
51typedef struct cache_entry_t cache_entry_t;
52
53/**
54 * Cache line in the interface name cache.
55 */
56struct cache_entry_t {
57 /** requid of the CHILD_SA */
b12c53ce 58 uint32_t reqid;
49653b6b
MW
59 /** cached interface name */
60 char *iface;
61};
62
63/**
64 * Insert an interface name to the cache
65 */
b12c53ce 66static void cache_iface(private_updown_listener_t *this, uint32_t reqid,
49653b6b
MW
67 char *iface)
68{
69 cache_entry_t *entry = malloc_thing(cache_entry_t);
7daf5226 70
49653b6b
MW
71 entry->reqid = reqid;
72 entry->iface = strdup(iface);
7daf5226 73
49653b6b
MW
74 this->iface_cache->insert_first(this->iface_cache, entry);
75}
76
77/**
78 * Remove a cached interface name and return it.
79 */
b12c53ce 80static char* uncache_iface(private_updown_listener_t *this, uint32_t reqid)
49653b6b
MW
81{
82 enumerator_t *enumerator;
83 cache_entry_t *entry;
84 char *iface = NULL;
7daf5226 85
49653b6b
MW
86 enumerator = this->iface_cache->create_enumerator(this->iface_cache);
87 while (enumerator->enumerate(enumerator, &entry))
88 {
89 if (entry->reqid == reqid)
90 {
91 this->iface_cache->remove_at(this->iface_cache, enumerator);
92 iface = entry->iface;
93 free(entry);
94 break;
95 }
96 }
97 enumerator->destroy(enumerator);
98 return iface;
99}
100
e0d3014a 101/**
6890bdc7 102 * Allocate and push a format string to the environment
e0d3014a 103 */
6890bdc7 104static bool push_env(char *envp[], u_int count, char *fmt, ...)
e0d3014a 105{
6890bdc7
MW
106 int i = 0;
107 char *str;
108 va_list args;
e0d3014a 109
6890bdc7 110 while (envp[i])
e0d3014a 111 {
6890bdc7
MW
112 if (++i + 1 >= count)
113 {
114 return FALSE;
115 }
e0d3014a 116 }
6890bdc7
MW
117 va_start(args, fmt);
118 if (vasprintf(&str, fmt, args) >= 0)
119 {
120 envp[i] = str;
121 }
122 va_end(args);
123 return envp[i] != NULL;
124}
e0d3014a 125
6890bdc7
MW
126/**
127 * Free all allocated environment strings
128 */
129static void free_env(char *envp[])
130{
131 int i;
132
133 for (i = 0; envp[i]; i++)
e0d3014a 134 {
6890bdc7
MW
135 free(envp[i]);
136 }
137}
138
139/**
140 * Push variables for handled DNS attributes
141 */
142static void push_dns_env(private_updown_listener_t *this, ike_sa_t *ike_sa,
143 char *envp[], u_int count)
144{
145 enumerator_t *enumerator;
146 host_t *host;
147 int v4 = 0, v6 = 0;
148
149 if (this->handler)
150 {
151 enumerator = this->handler->create_dns_enumerator(this->handler,
152 ike_sa->get_unique_id(ike_sa));
153 while (enumerator->enumerate(enumerator, &host))
e0d3014a 154 {
6890bdc7
MW
155 switch (host->get_family(host))
156 {
157 case AF_INET:
158 push_env(envp, count, "PLUTO_DNS4_%d=%H", ++v4, host);
159 break;
160 case AF_INET6:
161 push_env(envp, count, "PLUTO_DNS6_%d=%H", ++v6, host);
162 break;
163 default:
164 continue;
165 }
e0d3014a 166 }
6890bdc7 167 enumerator->destroy(enumerator);
e0d3014a 168 }
e0d3014a
MW
169}
170
101d26ba 171/**
1de31bcc 172 * Push variables for local/remote virtual IPs
101d26ba 173 */
6890bdc7 174static void push_vip_env(private_updown_listener_t *this, ike_sa_t *ike_sa,
1de31bcc 175 char *envp[], u_int count, bool local)
101d26ba
MW
176{
177 enumerator_t *enumerator;
178 host_t *host;
179 int v4 = 0, v6 = 0;
101d26ba
MW
180 bool first = TRUE;
181
1de31bcc 182 enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, local);
101d26ba
MW
183 while (enumerator->enumerate(enumerator, &host))
184 {
185 if (first)
186 { /* legacy variable for first VIP */
6890bdc7 187 first = FALSE;
1de31bcc
TB
188 push_env(envp, count, "PLUTO_%s_SOURCEIP=%H",
189 local ? "MY" : "PEER", host);
101d26ba
MW
190 }
191 switch (host->get_family(host))
192 {
193 case AF_INET:
1de31bcc
TB
194 push_env(envp, count, "PLUTO_%s_SOURCEIP4_%d=%H",
195 local ? "MY" : "PEER", ++v4, host);
101d26ba
MW
196 break;
197 case AF_INET6:
1de31bcc
TB
198 push_env(envp, count, "PLUTO_%s_SOURCEIP6_%d=%H",
199 local ? "MY" : "PEER", ++v6, host);
101d26ba
MW
200 break;
201 default:
202 continue;
203 }
101d26ba
MW
204 }
205 enumerator->destroy(enumerator);
101d26ba
MW
206}
207
0d7202c7
AS
208#define PORT_BUF_LEN 12
209
9739a0bf
TB
210/**
211 * Determine proper values for port env variable
212 */
0d7202c7
AS
213static char* get_port(traffic_selector_t *me, traffic_selector_t *other,
214 char *port_buf, bool local)
9739a0bf 215{
0d7202c7
AS
216 uint16_t port, to, from;
217
9739a0bf
TB
218 switch (max(me->get_protocol(me), other->get_protocol(other)))
219 {
220 case IPPROTO_ICMP:
221 case IPPROTO_ICMPV6:
222 {
0d7202c7
AS
223 port = max(me->get_from_port(me), other->get_from_port(other));
224 snprintf(port_buf, PORT_BUF_LEN, "%u",
225 local ? traffic_selector_icmp_type(port)
226 : traffic_selector_icmp_code(port));
227 return port_buf;
9739a0bf
TB
228 }
229 }
0d7202c7
AS
230 if (local)
231 {
232 from = me->get_from_port(me);
233 to = me->get_to_port(me);
234 }
235 else
236 {
237 from = other->get_from_port(other);
238 to = other->get_to_port(other);
239 }
ad82c95f 240 if (from == to || (from == 0 && to == 65535))
0d7202c7
AS
241 {
242 snprintf(port_buf, PORT_BUF_LEN, "%u", from);
243 }
244 else
245 {
246 snprintf(port_buf, PORT_BUF_LEN, "%u:%u", from, to);
247 }
248 return port_buf;
9739a0bf
TB
249}
250
6890bdc7
MW
251/**
252 * Invoke the updown script once for given traffic selectors
253 */
254static void invoke_once(private_updown_listener_t *this, ike_sa_t *ike_sa,
255 child_sa_t *child_sa, child_cfg_t *config, bool up,
256 traffic_selector_t *my_ts, traffic_selector_t *other_ts)
49653b6b 257{
6890bdc7
MW
258 host_t *me, *other, *host;
259 char *iface;
b12c53ce 260 uint8_t mask;
3fa8c3e5 261 uint32_t if_id;
6890bdc7
MW
262 mark_t mark;
263 bool is_host, is_ipv6;
264 int out;
265 FILE *shell;
266 process_t *process;
0d7202c7 267 char port_buf[PORT_BUF_LEN];
6890bdc7 268 char *envp[128] = {};
7daf5226 269
49653b6b
MW
270 me = ike_sa->get_my_host(ike_sa);
271 other = ike_sa->get_other_host(ike_sa);
7daf5226 272
4736ba06 273 push_env(envp, countof(envp), "PATH=%s", getenv("PATH"));
6890bdc7
MW
274 push_env(envp, countof(envp), "PLUTO_VERSION=1.1");
275 is_host = my_ts->is_host(my_ts, me);
276 if (is_host)
49653b6b 277 {
6890bdc7 278 is_ipv6 = me->get_family(me) == AF_INET6;
49653b6b 279 }
6890bdc7 280 else
49653b6b 281 {
6890bdc7
MW
282 is_ipv6 = my_ts->get_type(my_ts) == TS_IPV6_ADDR_RANGE;
283 }
284 push_env(envp, countof(envp), "PLUTO_VERB=%s%s%s",
285 up ? "up" : "down",
286 is_host ? "-host" : "-client",
287 is_ipv6 ? "-v6" : "");
288 push_env(envp, countof(envp), "PLUTO_CONNECTION=%s",
289 config->get_name(config));
290 if (up)
291 {
8394ea2a 292 if (charon->kernel->get_interface(charon->kernel, me, &iface))
ae0e3b03 293 {
6890bdc7 294 cache_iface(this, child_sa->get_reqid(child_sa), iface);
ae0e3b03
AS
295 }
296 else
297 {
6890bdc7 298 iface = NULL;
ae0e3b03 299 }
6890bdc7
MW
300 }
301 else
302 {
303 iface = uncache_iface(this, child_sa->get_reqid(child_sa));
304 }
305 push_env(envp, countof(envp), "PLUTO_INTERFACE=%s",
306 iface ? iface : "unknown");
307 push_env(envp, countof(envp), "PLUTO_REQID=%u",
308 child_sa->get_reqid(child_sa));
309 push_env(envp, countof(envp), "PLUTO_PROTO=%s",
310 child_sa->get_protocol(child_sa) == PROTO_ESP ? "esp" : "ah");
311 push_env(envp, countof(envp), "PLUTO_UNIQUEID=%u",
312 ike_sa->get_unique_id(ike_sa));
313 push_env(envp, countof(envp), "PLUTO_ME=%H", me);
314 push_env(envp, countof(envp), "PLUTO_MY_ID=%Y", ike_sa->get_my_id(ike_sa));
3f1de986 315 if (!my_ts->to_subnet(my_ts, &host, &mask))
6890bdc7 316 {
3f1de986
AS
317 DBG1(DBG_CHD, "updown approximates local TS %R "
318 "by next larger subnet", my_ts);
6890bdc7 319 }
3f1de986
AS
320 push_env(envp, countof(envp), "PLUTO_MY_CLIENT=%+H/%u", host, mask);
321 host->destroy(host);
0d7202c7
AS
322 push_env(envp, countof(envp), "PLUTO_MY_PORT=%s",
323 get_port(my_ts, other_ts, port_buf, TRUE));
6890bdc7
MW
324 push_env(envp, countof(envp), "PLUTO_MY_PROTOCOL=%u",
325 my_ts->get_protocol(my_ts));
326 push_env(envp, countof(envp), "PLUTO_PEER=%H", other);
327 push_env(envp, countof(envp), "PLUTO_PEER_ID=%Y",
328 ike_sa->get_other_id(ike_sa));
3f1de986 329 if (!other_ts->to_subnet(other_ts, &host, &mask))
6890bdc7 330 {
3f1de986
AS
331 DBG1(DBG_CHD, "updown approximates remote TS %R "
332 "by next larger subnet", other_ts);
6890bdc7 333 }
3f1de986
AS
334 push_env(envp, countof(envp), "PLUTO_PEER_CLIENT=%+H/%u", host, mask);
335 host->destroy(host);
0d7202c7
AS
336 push_env(envp, countof(envp), "PLUTO_PEER_PORT=%s",
337 get_port(my_ts, other_ts, port_buf, FALSE));
6890bdc7
MW
338 push_env(envp, countof(envp), "PLUTO_PEER_PROTOCOL=%u",
339 other_ts->get_protocol(other_ts));
340 if (ike_sa->has_condition(ike_sa, COND_EAP_AUTHENTICATED) ||
341 ike_sa->has_condition(ike_sa, COND_XAUTH_AUTHENTICATED))
342 {
343 push_env(envp, countof(envp), "PLUTO_XAUTH_ID=%Y",
344 ike_sa->get_other_eap_id(ike_sa));
345 }
1de31bcc
TB
346 push_vip_env(this, ike_sa, envp, countof(envp), TRUE);
347 push_vip_env(this, ike_sa, envp, countof(envp), FALSE);
b2103693 348 mark = child_sa->get_mark(child_sa, TRUE);
6890bdc7
MW
349 if (mark.value)
350 {
351 push_env(envp, countof(envp), "PLUTO_MARK_IN=%u/0x%08x",
352 mark.value, mark.mask);
353 }
b2103693 354 mark = child_sa->get_mark(child_sa, FALSE);
6890bdc7
MW
355 if (mark.value)
356 {
357 push_env(envp, countof(envp), "PLUTO_MARK_OUT=%u/0x%08x",
358 mark.value, mark.mask);
359 }
3fa8c3e5
TB
360 if_id = child_sa->get_if_id(child_sa, TRUE);
361 if (if_id)
362 {
363 push_env(envp, countof(envp), "PLUTO_IF_ID_IN=%u", if_id);
364 }
365 if_id = child_sa->get_if_id(child_sa, FALSE);
366 if (if_id)
367 {
368 push_env(envp, countof(envp), "PLUTO_IF_ID_OUT=%u", if_id);
369 }
6890bdc7
MW
370 if (ike_sa->has_condition(ike_sa, COND_NAT_ANY))
371 {
372 push_env(envp, countof(envp), "PLUTO_UDP_ENC=%u",
373 other->get_port(other));
374 }
375 if (child_sa->get_ipcomp(child_sa) != IPCOMP_NONE)
376 {
377 push_env(envp, countof(envp), "PLUTO_IPCOMP=1");
378 }
379 push_dns_env(this, ike_sa, envp, countof(envp));
749ac175 380 if (config->has_option(config, OPT_HOSTACCESS))
6890bdc7
MW
381 {
382 push_env(envp, countof(envp), "PLUTO_HOST_ACCESS=1");
383 }
ae0e3b03 384
6890bdc7
MW
385 process = process_start_shell(envp, NULL, &out, NULL, "2>&1 %s",
386 config->get_updown(config));
387 if (process)
388 {
389 shell = fdopen(out, "r");
390 if (shell)
5b89e3b0 391 {
6890bdc7 392 while (TRUE)
5b89e3b0 393 {
6890bdc7 394 char resp[128];
5b89e3b0 395
6890bdc7
MW
396 if (fgets(resp, sizeof(resp), shell) == NULL)
397 {
398 if (ferror(shell))
399 {
400 DBG1(DBG_CHD, "error reading from updown script");
401 }
402 break;
403 }
404 else
405 {
406 char *e = resp + strlen(resp);
407 if (e > resp && e[-1] == '\n')
408 {
409 e[-1] = '\0';
410 }
411 DBG1(DBG_CHD, "updown: %s", resp);
412 }
43347356 413 }
6890bdc7 414 fclose(shell);
49653b6b
MW
415 }
416 else
417 {
6890bdc7 418 close(out);
49653b6b 419 }
6890bdc7
MW
420 process->wait(process, NULL);
421 }
422 free(iface);
423 free_env(envp);
424}
7daf5226 425
6890bdc7
MW
426METHOD(listener_t, child_updown, bool,
427 private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
428 bool up)
429{
430 traffic_selector_t *my_ts, *other_ts;
431 enumerator_t *enumerator;
432 child_cfg_t *config;
7daf5226 433
6890bdc7
MW
434 config = child_sa->get_config(child_sa);
435 if (config->get_updown(config))
436 {
437 enumerator = child_sa->create_policy_enumerator(child_sa);
438 while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
49653b6b 439 {
6890bdc7 440 invoke_once(this, ike_sa, child_sa, config, up, my_ts, other_ts);
49653b6b 441 }
6890bdc7 442 enumerator->destroy(enumerator);
49653b6b 443 }
49653b6b
MW
444 return TRUE;
445}
446
7481f964
MW
447METHOD(updown_listener_t, destroy, void,
448 private_updown_listener_t *this)
49653b6b
MW
449{
450 this->iface_cache->destroy(this->iface_cache);
451 free(this);
452}
453
454/**
455 * See header
456 */
e0d3014a 457updown_listener_t *updown_listener_create(updown_handler_t *handler)
49653b6b 458{
7481f964
MW
459 private_updown_listener_t *this;
460
461 INIT(this,
462 .public = {
6f52d3b0
TB
463 .listener = {
464 .child_updown = _child_updown,
465 },
7481f964
MW
466 .destroy = _destroy,
467 },
468 .iface_cache = linked_list_create(),
e0d3014a 469 .handler = handler,
7481f964 470 );
7daf5226 471
49653b6b
MW
472 return &this->public;
473}