Support multiple virtual IPs on peer_cfg and ike_sa classes
[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 iface = hydra->kernel_interface->get_interface(
271 hydra->kernel_interface, me);
272 if (iface)
273 {
274 cache_iface(this, child_sa->get_reqid(child_sa), iface);
275 }
276 }
277 else
278 {
279 iface = uncache_iface(this, child_sa->get_reqid(child_sa));
280 }
281
282 dns = make_dns_vars(this, ike_sa);
283
284 /* determine IPv4/IPv6 and client/host situation */
285 is_host = my_ts->is_host(my_ts, me);
286 is_ipv6 = is_host ? (me->get_family(me) == AF_INET6) :
287 (my_ts->get_type(my_ts) == TS_IPV6_ADDR_RANGE);
288
289 /* build the command with all env variables.
290 * TODO: PLUTO_PEER_CA and PLUTO_NEXT_HOP are currently missing
291 */
292 snprintf(command, sizeof(command),
293 "2>&1 "
294 "PLUTO_VERSION='1.1' "
295 "PLUTO_VERB='%s%s%s' "
296 "PLUTO_CONNECTION='%s' "
297 "PLUTO_INTERFACE='%s' "
298 "PLUTO_REQID='%u' "
299 "PLUTO_ME='%H' "
300 "PLUTO_MY_ID='%Y' "
301 "PLUTO_MY_CLIENT='%H/%u' "
302 "PLUTO_MY_PORT='%u' "
303 "PLUTO_MY_PROTOCOL='%u' "
304 "PLUTO_PEER='%H' "
305 "PLUTO_PEER_ID='%Y' "
306 "PLUTO_PEER_CLIENT='%H/%u' "
307 "PLUTO_PEER_PORT='%u' "
308 "PLUTO_PEER_PROTOCOL='%u' "
309 "%s"
310 "%s"
311 "%s"
312 "%s"
313 "%s"
314 "%s"
315 "%s",
316 up ? "up" : "down",
317 is_host ? "-host" : "-client",
318 is_ipv6 ? "-v6" : "",
319 config->get_name(config),
320 iface ? iface : "unknown",
321 child_sa->get_reqid(child_sa),
322 me, ike_sa->get_my_id(ike_sa),
323 my_client, my_client_mask,
324 my_ts->get_from_port(my_ts),
325 my_ts->get_protocol(my_ts),
326 other, ike_sa->get_other_id(ike_sa),
327 other_client, other_client_mask,
328 other_ts->get_from_port(other_ts),
329 other_ts->get_protocol(other_ts),
330 virtual_ip,
331 mark_in,
332 mark_out,
333 udp_enc,
334 config->get_hostaccess(config) ? "PLUTO_HOST_ACCESS='1' " : "",
335 dns,
336 script);
337 my_client->destroy(my_client);
338 other_client->destroy(other_client);
339 free(virtual_ip);
340 free(mark_in);
341 free(mark_out);
342 free(udp_enc);
343 free(dns);
344 free(iface);
345
346 DBG3(DBG_CHD, "running updown script: %s", command);
347 shell = popen(command, "r");
348
349 if (shell == NULL)
350 {
351 DBG1(DBG_CHD, "could not execute updown script '%s'", script);
352 return TRUE;
353 }
354
355 while (TRUE)
356 {
357 char resp[128];
358
359 if (fgets(resp, sizeof(resp), shell) == NULL)
360 {
361 if (ferror(shell))
362 {
363 DBG1(DBG_CHD, "error reading output from updown script");
364 }
365 break;
366 }
367 else
368 {
369 char *e = resp + strlen(resp);
370 if (e > resp && e[-1] == '\n')
371 { /* trim trailing '\n' */
372 e[-1] = '\0';
373 }
374 DBG1(DBG_CHD, "updown: %s", resp);
375 }
376 }
377 pclose(shell);
378 }
379 enumerator->destroy(enumerator);
380 return TRUE;
381 }
382
383 METHOD(updown_listener_t, destroy, void,
384 private_updown_listener_t *this)
385 {
386 this->iface_cache->destroy(this->iface_cache);
387 free(this);
388 }
389
390 /**
391 * See header
392 */
393 updown_listener_t *updown_listener_create(updown_handler_t *handler)
394 {
395 private_updown_listener_t *this;
396
397 INIT(this,
398 .public = {
399 .listener = {
400 .child_updown = _child_updown,
401 },
402 .destroy = _destroy,
403 },
404 .iface_cache = linked_list_create(),
405 .handler = handler,
406 );
407
408 return &this->public;
409 }
410