Make it easy to check if an address is locally usable via changed get_interface(...
[strongswan.git] / src / libcharon / plugins / updown / updown_listener.c
1 /*
2 * Copyright (C) 2008 Martin Willi
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #define _GNU_SOURCE
17 #include <stdio.h>
18
19 #include "updown_listener.h"
20
21 #include <hydra.h>
22 #include <daemon.h>
23 #include <config/child_cfg.h>
24
25 typedef struct private_updown_listener_t private_updown_listener_t;
26
27 /**
28 * Private data of an updown_listener_t object.
29 */
30 struct private_updown_listener_t {
31
32 /**
33 * Public updown_listener_t interface.
34 */
35 updown_listener_t public;
36
37 /**
38 * List of cached interface names
39 */
40 linked_list_t *iface_cache;
41
42 /**
43 * DNS attribute handler
44 */
45 updown_handler_t *handler;
46 };
47
48 typedef struct cache_entry_t cache_entry_t;
49
50 /**
51 * Cache line in the interface name cache.
52 */
53 struct cache_entry_t {
54 /** requid of the CHILD_SA */
55 u_int32_t reqid;
56 /** cached interface name */
57 char *iface;
58 };
59
60 /**
61 * Insert an interface name to the cache
62 */
63 static void cache_iface(private_updown_listener_t *this, u_int32_t reqid,
64 char *iface)
65 {
66 cache_entry_t *entry = malloc_thing(cache_entry_t);
67
68 entry->reqid = reqid;
69 entry->iface = strdup(iface);
70
71 this->iface_cache->insert_first(this->iface_cache, entry);
72 }
73
74 /**
75 * Remove a cached interface name and return it.
76 */
77 static char* uncache_iface(private_updown_listener_t *this, u_int32_t reqid)
78 {
79 enumerator_t *enumerator;
80 cache_entry_t *entry;
81 char *iface = NULL;
82
83 enumerator = this->iface_cache->create_enumerator(this->iface_cache);
84 while (enumerator->enumerate(enumerator, &entry))
85 {
86 if (entry->reqid == reqid)
87 {
88 this->iface_cache->remove_at(this->iface_cache, enumerator);
89 iface = entry->iface;
90 free(entry);
91 break;
92 }
93 }
94 enumerator->destroy(enumerator);
95 return iface;
96 }
97
98 /**
99 * Create variables for handled DNS attributes
100 */
101 static char *make_dns_vars(private_updown_listener_t *this, ike_sa_t *ike_sa)
102 {
103 enumerator_t *enumerator;
104 host_t *host;
105 int v4 = 0, v6 = 0;
106 char total[512] = "", current[64];
107
108 if (!this->handler)
109 {
110 return strdup("");
111 }
112
113 enumerator = this->handler->create_dns_enumerator(this->handler,
114 ike_sa->get_unique_id(ike_sa));
115 while (enumerator->enumerate(enumerator, &host))
116 {
117 switch (host->get_family(host))
118 {
119 case AF_INET:
120 snprintf(current, sizeof(current),
121 "PLUTO_DNS4_%d='%H' ", ++v4, host);
122 break;
123 case AF_INET6:
124 snprintf(current, sizeof(current),
125 "PLUTO_DNS6_%d='%H' ", ++v6, host);
126 break;
127 default:
128 continue;
129 }
130 strncat(total, current, sizeof(total) - strlen(total) - 1);
131 }
132 enumerator->destroy(enumerator);
133
134 return strdup(total);
135 }
136
137 /**
138 * Create variables for local virtual IPs
139 */
140 static char *make_vip_vars(private_updown_listener_t *this, ike_sa_t *ike_sa)
141 {
142 enumerator_t *enumerator;
143 host_t *host;
144 int v4 = 0, v6 = 0;
145 char total[512] = "", current[64];
146 bool first = TRUE;
147
148 enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE);
149 while (enumerator->enumerate(enumerator, &host))
150 {
151 if (first)
152 { /* legacy variable for first VIP */
153 snprintf(current, sizeof(current),
154 "PLUTO_MY_SOURCEIP='%H' ", host);
155 strncat(total, current, sizeof(total) - strlen(total) - 1);
156 }
157 switch (host->get_family(host))
158 {
159 case AF_INET:
160 snprintf(current, sizeof(current),
161 "PLUTO_MY_SOURCEIP4_%d='%H' ", ++v4, host);
162 break;
163 case AF_INET6:
164 snprintf(current, sizeof(current),
165 "PLUTO_MY_SOURCEIP6_%d='%H' ", ++v6, host);
166 break;
167 default:
168 continue;
169 }
170 strncat(total, current, sizeof(total) - strlen(total) - 1);
171 }
172 enumerator->destroy(enumerator);
173
174 return strdup(total);
175 }
176
177 METHOD(listener_t, child_updown, bool,
178 private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
179 bool up)
180 {
181 traffic_selector_t *my_ts, *other_ts;
182 enumerator_t *enumerator;
183 child_cfg_t *config;
184 host_t *me, *other;
185 char *script;
186
187 config = child_sa->get_config(child_sa);
188 script = config->get_updown(config);
189 me = ike_sa->get_my_host(ike_sa);
190 other = ike_sa->get_other_host(ike_sa);
191
192 if (script == NULL)
193 {
194 return TRUE;
195 }
196
197 enumerator = child_sa->create_policy_enumerator(child_sa);
198 while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
199 {
200 char command[1024];
201 host_t *my_client, *other_client;
202 u_int8_t my_client_mask, other_client_mask;
203 char *virtual_ip, *iface, *mark_in, *mark_out, *udp_enc, *dns;
204 mark_t mark;
205 bool is_host, is_ipv6;
206 FILE *shell;
207
208 my_ts->to_subnet(my_ts, &my_client, &my_client_mask);
209 other_ts->to_subnet(other_ts, &other_client, &other_client_mask);
210
211 virtual_ip = make_vip_vars(this, ike_sa);
212
213 /* check for the presence of an inbound mark */
214 mark = config->get_mark(config, TRUE);
215 if (mark.value)
216 {
217 if (asprintf(&mark_in, "PLUTO_MARK_IN='%u/0x%08x' ",
218 mark.value, mark.mask ) < 0)
219 {
220 mark_in = NULL;
221 }
222 }
223 else
224 {
225 if (asprintf(&mark_in, "") < 0)
226 {
227 mark_in = NULL;
228 }
229 }
230
231 /* check for the presence of an outbound mark */
232 mark = config->get_mark(config, FALSE);
233 if (mark.value)
234 {
235 if (asprintf(&mark_out, "PLUTO_MARK_OUT='%u/0x%08x' ",
236 mark.value, mark.mask ) < 0)
237 {
238 mark_out = NULL;
239 }
240 }
241 else
242 {
243 if (asprintf(&mark_out, "") < 0)
244 {
245 mark_out = NULL;
246 }
247 }
248
249 /* check for a NAT condition causing ESP_IN_UDP encapsulation */
250 if (ike_sa->has_condition(ike_sa, COND_NAT_ANY))
251 {
252 if (asprintf(&udp_enc, "PLUTO_UDP_ENC='%u' ",
253 other->get_port(other)) < 0)
254 {
255 udp_enc = NULL;
256 }
257
258 }
259 else
260 {
261 if (asprintf(&udp_enc, "") < 0)
262 {
263 udp_enc = NULL;
264 }
265
266 }
267
268 if (up)
269 {
270 if (hydra->kernel_interface->get_interface(hydra->kernel_interface,
271 me, &iface))
272 {
273 cache_iface(this, child_sa->get_reqid(child_sa), iface);
274 }
275 }
276 else
277 {
278 iface = uncache_iface(this, child_sa->get_reqid(child_sa));
279 }
280
281 dns = make_dns_vars(this, ike_sa);
282
283 /* determine IPv4/IPv6 and client/host situation */
284 is_host = my_ts->is_host(my_ts, me);
285 is_ipv6 = is_host ? (me->get_family(me) == AF_INET6) :
286 (my_ts->get_type(my_ts) == TS_IPV6_ADDR_RANGE);
287
288 /* build the command with all env variables.
289 * TODO: PLUTO_PEER_CA and PLUTO_NEXT_HOP are currently missing
290 */
291 snprintf(command, sizeof(command),
292 "2>&1 "
293 "PLUTO_VERSION='1.1' "
294 "PLUTO_VERB='%s%s%s' "
295 "PLUTO_CONNECTION='%s' "
296 "PLUTO_INTERFACE='%s' "
297 "PLUTO_REQID='%u' "
298 "PLUTO_ME='%H' "
299 "PLUTO_MY_ID='%Y' "
300 "PLUTO_MY_CLIENT='%H/%u' "
301 "PLUTO_MY_PORT='%u' "
302 "PLUTO_MY_PROTOCOL='%u' "
303 "PLUTO_PEER='%H' "
304 "PLUTO_PEER_ID='%Y' "
305 "PLUTO_PEER_CLIENT='%H/%u' "
306 "PLUTO_PEER_PORT='%u' "
307 "PLUTO_PEER_PROTOCOL='%u' "
308 "%s"
309 "%s"
310 "%s"
311 "%s"
312 "%s"
313 "%s"
314 "%s",
315 up ? "up" : "down",
316 is_host ? "-host" : "-client",
317 is_ipv6 ? "-v6" : "",
318 config->get_name(config),
319 iface ? iface : "unknown",
320 child_sa->get_reqid(child_sa),
321 me, ike_sa->get_my_id(ike_sa),
322 my_client, my_client_mask,
323 my_ts->get_from_port(my_ts),
324 my_ts->get_protocol(my_ts),
325 other, ike_sa->get_other_id(ike_sa),
326 other_client, other_client_mask,
327 other_ts->get_from_port(other_ts),
328 other_ts->get_protocol(other_ts),
329 virtual_ip,
330 mark_in,
331 mark_out,
332 udp_enc,
333 config->get_hostaccess(config) ? "PLUTO_HOST_ACCESS='1' " : "",
334 dns,
335 script);
336 my_client->destroy(my_client);
337 other_client->destroy(other_client);
338 free(virtual_ip);
339 free(mark_in);
340 free(mark_out);
341 free(udp_enc);
342 free(dns);
343 free(iface);
344
345 DBG3(DBG_CHD, "running updown script: %s", command);
346 shell = popen(command, "r");
347
348 if (shell == NULL)
349 {
350 DBG1(DBG_CHD, "could not execute updown script '%s'", script);
351 return TRUE;
352 }
353
354 while (TRUE)
355 {
356 char resp[128];
357
358 if (fgets(resp, sizeof(resp), shell) == NULL)
359 {
360 if (ferror(shell))
361 {
362 DBG1(DBG_CHD, "error reading output from updown script");
363 }
364 break;
365 }
366 else
367 {
368 char *e = resp + strlen(resp);
369 if (e > resp && e[-1] == '\n')
370 { /* trim trailing '\n' */
371 e[-1] = '\0';
372 }
373 DBG1(DBG_CHD, "updown: %s", resp);
374 }
375 }
376 pclose(shell);
377 }
378 enumerator->destroy(enumerator);
379 return TRUE;
380 }
381
382 METHOD(updown_listener_t, destroy, void,
383 private_updown_listener_t *this)
384 {
385 this->iface_cache->destroy(this->iface_cache);
386 free(this);
387 }
388
389 /**
390 * See header
391 */
392 updown_listener_t *updown_listener_create(updown_handler_t *handler)
393 {
394 private_updown_listener_t *this;
395
396 INIT(this,
397 .public = {
398 .listener = {
399 .child_updown = _child_updown,
400 },
401 .destroy = _destroy,
402 },
403 .iface_cache = linked_list_create(),
404 .handler = handler,
405 );
406
407 return &this->public;
408 }
409