separated updown listener to its own class
[strongswan.git] / src / charon / 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 * $Id$
16 */
17
18 #define _GNU_SOURCE
19 #include <stdio.h>
20
21 #include "updown_listener.h"
22
23 #include <daemon.h>
24 #include <config/child_cfg.h>
25
26 typedef struct private_updown_listener_t private_updown_listener_t;
27
28 /**
29 * Private data of an updown_listener_t object.
30 */
31 struct private_updown_listener_t {
32
33 /**
34 * Public updown_listener_t interface.
35 */
36 updown_listener_t public;
37
38 /**
39 * List of cached interface names
40 */
41 linked_list_t *iface_cache;
42 };
43
44 typedef struct cache_entry_t cache_entry_t;
45
46 /**
47 * Cache line in the interface name cache.
48 */
49 struct cache_entry_t {
50 /** requid of the CHILD_SA */
51 u_int32_t reqid;
52 /** cached interface name */
53 char *iface;
54 };
55
56 /**
57 * Insert an interface name to the cache
58 */
59 static void cache_iface(private_updown_listener_t *this, u_int32_t reqid,
60 char *iface)
61 {
62 cache_entry_t *entry = malloc_thing(cache_entry_t);
63
64 entry->reqid = reqid;
65 entry->iface = strdup(iface);
66
67 this->iface_cache->insert_first(this->iface_cache, entry);
68 }
69
70 /**
71 * Remove a cached interface name and return it.
72 */
73 static char* uncache_iface(private_updown_listener_t *this, u_int32_t reqid)
74 {
75 enumerator_t *enumerator;
76 cache_entry_t *entry;
77 char *iface = NULL;
78
79 enumerator = this->iface_cache->create_enumerator(this->iface_cache);
80 while (enumerator->enumerate(enumerator, &entry))
81 {
82 if (entry->reqid == reqid)
83 {
84 this->iface_cache->remove_at(this->iface_cache, enumerator);
85 iface = entry->iface;
86 free(entry);
87 break;
88 }
89 }
90 enumerator->destroy(enumerator);
91 return iface;
92 }
93
94 /**
95 * Run the up/down script
96 */
97 static void updown(private_updown_listener_t *this, ike_sa_t *ike_sa,
98 child_sa_t *child_sa, bool up)
99 {
100 traffic_selector_t *my_ts, *other_ts;
101 enumerator_t *enumerator;
102 child_cfg_t *config;
103 host_t *vip, *me, *other;
104 char *script;
105
106 config = child_sa->get_config(child_sa);
107 vip = ike_sa->get_virtual_ip(ike_sa, TRUE);
108 script = config->get_updown(config);
109 me = ike_sa->get_my_host(ike_sa);
110 other = ike_sa->get_other_host(ike_sa);
111
112 if (script == NULL)
113 {
114 return;
115 }
116
117 enumerator = child_sa->create_policy_enumerator(child_sa);
118 while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
119 {
120 char command[1024];
121 char *my_client, *other_client, *my_client_mask, *other_client_mask;
122 char *pos, *virtual_ip, *iface;
123 FILE *shell;
124
125 /* get subnet/bits from string */
126 if (asprintf(&my_client, "%R", my_ts) < 0)
127 {
128 my_client = NULL;
129 }
130 pos = strchr(my_client, '/');
131 *pos = '\0';
132 my_client_mask = pos + 1;
133 pos = strchr(my_client_mask, '[');
134 if (pos)
135 {
136 *pos = '\0';
137 }
138 if (asprintf(&other_client, "%R", other_ts) < 0)
139 {
140 other_client = NULL;
141 }
142 pos = strchr(other_client, '/');
143 *pos = '\0';
144 other_client_mask = pos + 1;
145 pos = strchr(other_client_mask, '[');
146 if (pos)
147 {
148 *pos = '\0';
149 }
150
151 if (vip)
152 {
153 if (asprintf(&virtual_ip, "PLUTO_MY_SOURCEIP='%H' ", vip) < 0)
154 {
155 virtual_ip = NULL;
156 }
157 }
158 else
159 {
160 if (asprintf(&virtual_ip, "") < 0)
161 {
162 virtual_ip = NULL;
163 }
164 }
165
166 if (up)
167 {
168 iface = charon->kernel_interface->get_interface(
169 charon->kernel_interface, me);
170 if (iface)
171 {
172 cache_iface(this, child_sa->get_reqid(child_sa), iface);
173 }
174 }
175 else
176 {
177 iface = uncache_iface(this, child_sa->get_reqid(child_sa));
178 }
179
180 /* build the command with all env variables.
181 * TODO: PLUTO_PEER_CA and PLUTO_NEXT_HOP are currently missing
182 */
183 snprintf(command, sizeof(command),
184 "2>&1 "
185 "PLUTO_VERSION='1.1' "
186 "PLUTO_VERB='%s%s%s' "
187 "PLUTO_CONNECTION='%s' "
188 "PLUTO_INTERFACE='%s' "
189 "PLUTO_REQID='%u' "
190 "PLUTO_ME='%H' "
191 "PLUTO_MY_ID='%D' "
192 "PLUTO_MY_CLIENT='%s/%s' "
193 "PLUTO_MY_CLIENT_NET='%s' "
194 "PLUTO_MY_CLIENT_MASK='%s' "
195 "PLUTO_MY_PORT='%u' "
196 "PLUTO_MY_PROTOCOL='%u' "
197 "PLUTO_PEER='%H' "
198 "PLUTO_PEER_ID='%D' "
199 "PLUTO_PEER_CLIENT='%s/%s' "
200 "PLUTO_PEER_CLIENT_NET='%s' "
201 "PLUTO_PEER_CLIENT_MASK='%s' "
202 "PLUTO_PEER_PORT='%u' "
203 "PLUTO_PEER_PROTOCOL='%u' "
204 "%s"
205 "%s"
206 "%s",
207 up ? "up" : "down",
208 my_ts->is_host(my_ts, me) ? "-host" : "-client",
209 me->get_family(me) == AF_INET ? "" : "-v6",
210 config->get_name(config),
211 iface ? iface : "unknown",
212 child_sa->get_reqid(child_sa),
213 me, ike_sa->get_my_id(ike_sa),
214 my_client, my_client_mask,
215 my_client, my_client_mask,
216 my_ts->get_from_port(my_ts),
217 my_ts->get_protocol(my_ts),
218 other, ike_sa->get_other_id(ike_sa),
219 other_client, other_client_mask,
220 other_client, other_client_mask,
221 other_ts->get_from_port(other_ts),
222 other_ts->get_protocol(other_ts),
223 virtual_ip,
224 config->get_hostaccess(config) ? "PLUTO_HOST_ACCESS='1' " : "",
225 script);
226 free(my_client);
227 free(other_client);
228 free(virtual_ip);
229 free(iface);
230
231 DBG3(DBG_CHD, "running updown script: %s", command);
232 shell = popen(command, "r");
233
234 if (shell == NULL)
235 {
236 DBG1(DBG_CHD, "could not execute updown script '%s'", script);
237 return;
238 }
239
240 while (TRUE)
241 {
242 char resp[128];
243
244 if (fgets(resp, sizeof(resp), shell) == NULL)
245 {
246 if (ferror(shell))
247 {
248 DBG1(DBG_CHD, "error reading output from updown script");
249 return;
250 }
251 else
252 {
253 break;
254 }
255 }
256 else
257 {
258 char *e = resp + strlen(resp);
259 if (e > resp && e[-1] == '\n')
260 { /* trim trailing '\n' */
261 e[-1] = '\0';
262 }
263 DBG1(DBG_CHD, "updown: %s", resp);
264 }
265 }
266 pclose(shell);
267 }
268 enumerator->destroy(enumerator);
269 }
270
271 /**
272 * Listener implementation
273 */
274 static bool child_state_change(private_updown_listener_t *this, ike_sa_t *ike_sa,
275 child_sa_t *child_sa, child_sa_state_t state)
276 {
277 child_sa_state_t old;
278
279 if (ike_sa)
280 {
281 old = child_sa->get_state(child_sa);
282
283 if ((old == CHILD_INSTALLED && state != CHILD_REKEYING ) ||
284 (old == CHILD_DELETING && state == CHILD_DESTROYING))
285 {
286 updown(this, ike_sa, child_sa, FALSE);
287 }
288 else if (state == CHILD_INSTALLED)
289 {
290 updown(this, ike_sa, child_sa, TRUE);
291 }
292 }
293 return TRUE;
294 }
295
296 /**
297 * Implementation of updown_listener_t.destroy.
298 */
299 static void destroy(private_updown_listener_t *this)
300 {
301 this->iface_cache->destroy(this->iface_cache);
302 free(this);
303 }
304
305 /**
306 * See header
307 */
308 updown_listener_t *updown_listener_create()
309 {
310 private_updown_listener_t *this = malloc_thing(private_updown_listener_t);
311
312 memset(&this->public.listener, 0, sizeof(listener_t));
313 this->public.listener.child_state_change = (void*)child_state_change;
314 this->public.destroy = (void(*)(updown_listener_t*))destroy;
315
316 this->iface_cache = linked_list_create();
317
318 return &this->public;
319 }
320