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