2bd757ec7042549bf1ef4996b66c985637ae645e
[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 typedef struct cache_entry_t cache_entry_t;
44
45 /**
46 * Cache line in the interface name cache.
47 */
48 struct cache_entry_t {
49 /** requid of the CHILD_SA */
50 u_int32_t reqid;
51 /** cached interface name */
52 char *iface;
53 };
54
55 /**
56 * Insert an interface name to the cache
57 */
58 static void cache_iface(private_updown_listener_t *this, u_int32_t reqid,
59 char *iface)
60 {
61 cache_entry_t *entry = malloc_thing(cache_entry_t);
62
63 entry->reqid = reqid;
64 entry->iface = strdup(iface);
65
66 this->iface_cache->insert_first(this->iface_cache, entry);
67 }
68
69 /**
70 * Remove a cached interface name and return it.
71 */
72 static char* uncache_iface(private_updown_listener_t *this, u_int32_t reqid)
73 {
74 enumerator_t *enumerator;
75 cache_entry_t *entry;
76 char *iface = NULL;
77
78 enumerator = this->iface_cache->create_enumerator(this->iface_cache);
79 while (enumerator->enumerate(enumerator, &entry))
80 {
81 if (entry->reqid == reqid)
82 {
83 this->iface_cache->remove_at(this->iface_cache, enumerator);
84 iface = entry->iface;
85 free(entry);
86 break;
87 }
88 }
89 enumerator->destroy(enumerator);
90 return iface;
91 }
92
93 METHOD(listener_t, child_updown, bool,
94 private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
95 bool up)
96 {
97 traffic_selector_t *my_ts, *other_ts;
98 enumerator_t *enumerator;
99 child_cfg_t *config;
100 host_t *vip, *me, *other;
101 char *script;
102
103 config = child_sa->get_config(child_sa);
104 vip = ike_sa->get_virtual_ip(ike_sa, TRUE);
105 script = config->get_updown(config);
106 me = ike_sa->get_my_host(ike_sa);
107 other = ike_sa->get_other_host(ike_sa);
108
109 if (script == NULL)
110 {
111 return TRUE;
112 }
113
114 enumerator = child_sa->create_policy_enumerator(child_sa);
115 while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
116 {
117 char command[1024];
118 host_t *my_client, *other_client;
119 u_int8_t my_client_mask, other_client_mask;
120 char *virtual_ip, *iface, *mark_in, *mark_out, *udp_enc;
121 mark_t mark;
122 bool is_host, is_ipv6;
123 FILE *shell;
124
125 my_ts->to_subnet(my_ts, &my_client, &my_client_mask);
126 other_ts->to_subnet(other_ts, &other_client, &other_client_mask);
127
128 if (vip)
129 {
130 if (asprintf(&virtual_ip, "PLUTO_MY_SOURCEIP='%H' ", vip) < 0)
131 {
132 virtual_ip = NULL;
133 }
134 }
135 else
136 {
137 if (asprintf(&virtual_ip, "") < 0)
138 {
139 virtual_ip = NULL;
140 }
141 }
142
143 /* check for the presence of an inbound mark */
144 mark = config->get_mark(config, TRUE);
145 if (mark.value)
146 {
147 if (asprintf(&mark_in, "PLUTO_MARK_IN='%u/0x%08x' ",
148 mark.value, mark.mask ) < 0)
149 {
150 mark_in = NULL;
151 }
152 }
153 else
154 {
155 if (asprintf(&mark_in, "") < 0)
156 {
157 mark_in = NULL;
158 }
159 }
160
161 /* check for the presence of an outbound mark */
162 mark = config->get_mark(config, FALSE);
163 if (mark.value)
164 {
165 if (asprintf(&mark_out, "PLUTO_MARK_OUT='%u/0x%08x' ",
166 mark.value, mark.mask ) < 0)
167 {
168 mark_out = NULL;
169 }
170 }
171 else
172 {
173 if (asprintf(&mark_out, "") < 0)
174 {
175 mark_out = NULL;
176 }
177 }
178
179 /* check for a NAT condition causing ESP_IN_UDP encapsulation */
180 if (ike_sa->has_condition(ike_sa, COND_NAT_ANY))
181 {
182 if (asprintf(&udp_enc, "PLUTO_UDP_ENC='%u' ",
183 other->get_port(other)) < 0)
184 {
185 udp_enc = NULL;
186 }
187
188 }
189 else
190 {
191 if (asprintf(&udp_enc, "") < 0)
192 {
193 udp_enc = NULL;
194 }
195
196 }
197
198 if (up)
199 {
200 iface = hydra->kernel_interface->get_interface(
201 hydra->kernel_interface, me);
202 if (iface)
203 {
204 cache_iface(this, child_sa->get_reqid(child_sa), iface);
205 }
206 }
207 else
208 {
209 iface = uncache_iface(this, child_sa->get_reqid(child_sa));
210 }
211
212 /* determine IPv4/IPv6 and client/host situation */
213 is_host = my_ts->is_host(my_ts, me);
214 is_ipv6 = is_host ? (me->get_family(me) == AF_INET6) :
215 (my_ts->get_type(my_ts) == TS_IPV6_ADDR_RANGE);
216
217 /* build the command with all env variables.
218 * TODO: PLUTO_PEER_CA and PLUTO_NEXT_HOP are currently missing
219 */
220 snprintf(command, sizeof(command),
221 "2>&1 "
222 "PLUTO_VERSION='1.1' "
223 "PLUTO_VERB='%s%s%s' "
224 "PLUTO_CONNECTION='%s' "
225 "PLUTO_INTERFACE='%s' "
226 "PLUTO_REQID='%u' "
227 "PLUTO_ME='%H' "
228 "PLUTO_MY_ID='%Y' "
229 "PLUTO_MY_CLIENT='%H/%u' "
230 "PLUTO_MY_PORT='%u' "
231 "PLUTO_MY_PROTOCOL='%u' "
232 "PLUTO_PEER='%H' "
233 "PLUTO_PEER_ID='%Y' "
234 "PLUTO_PEER_CLIENT='%H/%u' "
235 "PLUTO_PEER_PORT='%u' "
236 "PLUTO_PEER_PROTOCOL='%u' "
237 "%s"
238 "%s"
239 "%s"
240 "%s"
241 "%s"
242 "%s",
243 up ? "up" : "down",
244 is_host ? "-host" : "-client",
245 is_ipv6 ? "-v6" : "",
246 config->get_name(config),
247 iface ? iface : "unknown",
248 child_sa->get_reqid(child_sa),
249 me, ike_sa->get_my_id(ike_sa),
250 my_client, my_client_mask,
251 my_ts->get_from_port(my_ts),
252 my_ts->get_protocol(my_ts),
253 other, ike_sa->get_other_id(ike_sa),
254 other_client, other_client_mask,
255 other_ts->get_from_port(other_ts),
256 other_ts->get_protocol(other_ts),
257 virtual_ip,
258 mark_in,
259 mark_out,
260 udp_enc,
261 config->get_hostaccess(config) ? "PLUTO_HOST_ACCESS='1' " : "",
262 script);
263 my_client->destroy(my_client);
264 other_client->destroy(other_client);
265 free(virtual_ip);
266 free(mark_in);
267 free(mark_out);
268 free(udp_enc);
269 free(iface);
270
271 DBG3(DBG_CHD, "running updown script: %s", command);
272 shell = popen(command, "r");
273
274 if (shell == NULL)
275 {
276 DBG1(DBG_CHD, "could not execute updown script '%s'", script);
277 return TRUE;
278 }
279
280 while (TRUE)
281 {
282 char resp[128];
283
284 if (fgets(resp, sizeof(resp), shell) == NULL)
285 {
286 if (ferror(shell))
287 {
288 DBG1(DBG_CHD, "error reading output from updown script");
289 }
290 break;
291 }
292 else
293 {
294 char *e = resp + strlen(resp);
295 if (e > resp && e[-1] == '\n')
296 { /* trim trailing '\n' */
297 e[-1] = '\0';
298 }
299 DBG1(DBG_CHD, "updown: %s", resp);
300 }
301 }
302 pclose(shell);
303 }
304 enumerator->destroy(enumerator);
305 return TRUE;
306 }
307
308 METHOD(updown_listener_t, destroy, void,
309 private_updown_listener_t *this)
310 {
311 this->iface_cache->destroy(this->iface_cache);
312 free(this);
313 }
314
315 /**
316 * See header
317 */
318 updown_listener_t *updown_listener_create()
319 {
320 private_updown_listener_t *this;
321
322 INIT(this,
323 .public = {
324 .listener = {
325 .child_updown = _child_updown,
326 },
327 .destroy = _destroy,
328 },
329 .iface_cache = linked_list_create(),
330 );
331
332 return &this->public;
333 }
334