Make the UDP ports charon listens for packets on (and uses as source ports) configurable.
[strongswan.git] / src / libcharon / plugins / maemo / maemo_service.c
1 /*
2 * Copyright (C) 2010 Tobias Brunner
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 #include <glib.h>
17 #include <libosso.h>
18 #include <sys/stat.h>
19
20 #include "maemo_service.h"
21
22 #include <daemon.h>
23 #include <credentials/sets/mem_cred.h>
24 #include <processing/jobs/callback_job.h>
25
26 #define OSSO_STATUS_NAME "status"
27 #define OSSO_STATUS_SERVICE "org.strongswan."OSSO_STATUS_NAME
28 #define OSSO_STATUS_OBJECT "/org/strongswan/"OSSO_STATUS_NAME
29 #define OSSO_STATUS_IFACE "org.strongswan."OSSO_STATUS_NAME
30
31 #define OSSO_CHARON_NAME "charon"
32 #define OSSO_CHARON_SERVICE "org.strongswan."OSSO_CHARON_NAME
33 #define OSSO_CHARON_OBJECT "/org/strongswan/"OSSO_CHARON_NAME
34 #define OSSO_CHARON_IFACE "org.strongswan."OSSO_CHARON_NAME
35
36 #define MAEMO_COMMON_CA_DIR "/etc/certs/common-ca"
37 #define MAEMO_USER_CA_DIR "/home/user/.maemosec-certs/wifi-ca"
38 /* there is also an smime-ca and an ssl-ca sub-directory and the same for
39 * ...-user, which store end user/server certificates */
40
41 typedef enum {
42 VPN_STATUS_DISCONNECTED,
43 VPN_STATUS_CONNECTING,
44 VPN_STATUS_CONNECTED,
45 VPN_STATUS_AUTH_FAILED,
46 VPN_STATUS_CONNECTION_FAILED,
47 } vpn_status_t;
48
49 typedef struct private_maemo_service_t private_maemo_service_t;
50
51 /**
52 * private data of maemo service
53 */
54 struct private_maemo_service_t {
55
56 /**
57 * public interface
58 */
59 maemo_service_t public;
60
61 /**
62 * credentials
63 */
64 mem_cred_t *creds;
65
66 /**
67 * Glib main loop for a thread, handles DBUS calls
68 */
69 GMainLoop *loop;
70
71 /**
72 * Context for OSSO
73 */
74 osso_context_t *context;
75
76 /**
77 * Current IKE_SA
78 */
79 ike_sa_t *ike_sa;
80
81 /**
82 * Status of the current connection
83 */
84 vpn_status_t status;
85
86 /**
87 * Name of the current connection
88 */
89 gchar *current;
90
91 };
92
93 static gint change_status(private_maemo_service_t *this, int status)
94 {
95 osso_rpc_t retval;
96 gint res;
97 this->status = status;
98 res = osso_rpc_run (this->context, OSSO_STATUS_SERVICE, OSSO_STATUS_OBJECT,
99 OSSO_STATUS_IFACE, "StatusChanged", &retval,
100 DBUS_TYPE_INT32, status,
101 DBUS_TYPE_INVALID);
102 return res;
103 }
104
105 METHOD(listener_t, ike_updown, bool,
106 private_maemo_service_t *this, ike_sa_t *ike_sa, bool up)
107 {
108 /* this callback is only registered during initiation, so if the IKE_SA
109 * goes down we assume an authentication error */
110 if (this->ike_sa == ike_sa && !up)
111 {
112 change_status(this, VPN_STATUS_AUTH_FAILED);
113 return FALSE;
114 }
115 return TRUE;
116 }
117
118 METHOD(listener_t, ike_state_change, bool,
119 private_maemo_service_t *this, ike_sa_t *ike_sa, ike_sa_state_t state)
120 {
121 /* this call back is only registered during initiation */
122 if (this->ike_sa == ike_sa && state == IKE_DESTROYING)
123 {
124 change_status(this, VPN_STATUS_CONNECTION_FAILED);
125 return FALSE;
126 }
127 return TRUE;
128 }
129
130 METHOD(listener_t, child_updown, bool,
131 private_maemo_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
132 bool up)
133 {
134 if (this->ike_sa == ike_sa)
135 {
136 if (up)
137 {
138 /* disable hooks registered to catch initiation failures */
139 this->public.listener.ike_updown = NULL;
140 this->public.listener.ike_state_change = NULL;
141 change_status(this, VPN_STATUS_CONNECTED);
142 }
143 else
144 {
145 change_status(this, VPN_STATUS_CONNECTION_FAILED);
146 return FALSE;
147 }
148 }
149 return TRUE;
150 }
151
152 METHOD(listener_t, ike_rekey, bool,
153 private_maemo_service_t *this, ike_sa_t *old, ike_sa_t *new)
154 {
155 if (this->ike_sa == old)
156 {
157 this->ike_sa = new;
158 }
159 return TRUE;
160 }
161
162 /**
163 * load all CA certificates in the given directory
164 */
165 static void load_ca_dir(private_maemo_service_t *this, char *dir)
166 {
167 enumerator_t *enumerator;
168 char *rel, *abs;
169 struct stat st;
170
171 enumerator = enumerator_create_directory(dir);
172 if (enumerator)
173 {
174 while (enumerator->enumerate(enumerator, &rel, &abs, &st))
175 {
176 if (rel[0] != '.')
177 {
178 if (S_ISREG(st.st_mode))
179 {
180 certificate_t *cert;
181 cert = lib->creds->create(lib->creds, CRED_CERTIFICATE,
182 CERT_X509, BUILD_FROM_FILE, abs,
183 BUILD_END);
184 if (!cert)
185 {
186 DBG1(DBG_CFG, "loading CA certificate '%s' failed",
187 abs);
188 continue;
189 }
190 DBG2(DBG_CFG, "loaded CA certificate '%Y'",
191 cert->get_subject(cert));
192 this->creds->add_cert(this->creds, TRUE, cert);
193 }
194 }
195 }
196 enumerator->destroy(enumerator);
197 }
198 }
199
200 static void disconnect(private_maemo_service_t *this)
201 {
202 ike_sa_t *ike_sa;
203 u_int id;
204
205 if (!this->current)
206 {
207 return;
208 }
209
210 /* avoid status updates, as this is called from the Glib main loop */
211 charon->bus->remove_listener(charon->bus, &this->public.listener);
212
213 ike_sa = charon->ike_sa_manager->checkout_by_name(charon->ike_sa_manager,
214 this->current, FALSE);
215 if (ike_sa)
216 {
217 id = ike_sa->get_unique_id(ike_sa);
218 charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
219 charon->controller->terminate_ike(charon->controller, id,
220 NULL, NULL, 0);
221 }
222 this->current = (g_free(this->current), NULL);
223 this->status = VPN_STATUS_DISCONNECTED;
224 }
225
226 static gboolean initiate_connection(private_maemo_service_t *this,
227 GArray *arguments)
228 {
229 gint i;
230 gchar *hostname = NULL, *cacert = NULL, *username = NULL, *password = NULL;
231 identification_t *gateway = NULL, *user = NULL;
232 ike_sa_t *ike_sa;
233 ike_cfg_t *ike_cfg;
234 peer_cfg_t *peer_cfg;
235 child_cfg_t *child_cfg;
236 traffic_selector_t *ts;
237 auth_cfg_t *auth;
238 certificate_t *cert;
239 lifetime_cfg_t lifetime = {
240 .time = {
241 .life = 10800, /* 3h */
242 .rekey = 10200, /* 2h50min */
243 .jitter = 300 /* 5min */
244 }
245 };
246
247 if (this->status == VPN_STATUS_CONNECTED ||
248 this->status == VPN_STATUS_CONNECTING)
249 {
250 DBG1(DBG_CFG, "currently connected to '%s', disconnecting first",
251 this->current);
252 disconnect (this);
253 }
254
255 if (arguments->len != 5)
256 {
257 DBG1(DBG_CFG, "wrong number of arguments: %d", arguments->len);
258 return FALSE;
259 }
260
261 for (i = 0; i < arguments->len; i++)
262 {
263 osso_rpc_t *arg = &g_array_index(arguments, osso_rpc_t, i);
264 if (arg->type != DBUS_TYPE_STRING)
265 {
266 DBG1(DBG_CFG, "invalid argument [%d]: %d", i, arg->type);
267 return FALSE;
268 }
269 switch (i)
270 {
271 case 0: /* name */
272 this->current = (g_free(this->current), NULL);
273 this->current = g_strdup(arg->value.s);
274 break;
275 case 1: /* hostname */
276 hostname = arg->value.s;
277 break;
278 case 2: /* CA certificate path */
279 cacert = arg->value.s;
280 break;
281 case 3: /* username */
282 username = arg->value.s;
283 break;
284 case 4: /* password */
285 password = arg->value.s;
286 break;
287 }
288 }
289
290 DBG1(DBG_CFG, "received initiate for connection '%s'", this->current);
291
292 this->creds->clear(this->creds);
293
294 if (cacert && !streq(cacert, ""))
295 {
296 cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
297 BUILD_FROM_FILE, cacert, BUILD_END);
298 if (cert)
299 {
300 this->creds->add_cert(this->creds, TRUE, cert);
301 }
302 else
303 {
304 DBG1(DBG_CFG, "failed to load CA certificate");
305 }
306 /* if this is a server cert we could use the cert subject as id */
307 }
308 else
309 {
310 load_ca_dir(this, MAEMO_COMMON_CA_DIR);
311 load_ca_dir(this, MAEMO_USER_CA_DIR);
312 }
313
314 gateway = identification_create_from_string(hostname);
315 DBG1(DBG_CFG, "using CA certificate, gateway identitiy '%Y'", gateway);
316
317 {
318 shared_key_t *shared_key;
319 chunk_t secret = chunk_create(password, strlen(password));
320 user = identification_create_from_string(username);
321 shared_key = shared_key_create(SHARED_EAP, chunk_clone(secret));
322 this->creds->add_shared(this->creds, shared_key, user->clone(user),
323 NULL);
324 }
325
326 ike_cfg = ike_cfg_create(TRUE, FALSE, "0.0.0.0", FALSE, CHARON_UDP_PORT,
327 hostname, FALSE, IKEV2_UDP_PORT);
328 ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE));
329
330 peer_cfg = peer_cfg_create(this->current, IKEV2, ike_cfg,
331 CERT_SEND_IF_ASKED,
332 UNIQUE_REPLACE, 1, /* keyingtries */
333 36000, 0, /* rekey 10h, reauth none */
334 600, 600, /* jitter, over 10min */
335 TRUE, FALSE, /* mobike, aggressive */
336 0, 0, /* DPD delay, timeout */
337 host_create_from_string("0.0.0.0", 0) /* virt */,
338 NULL, FALSE, NULL, NULL); /* pool, mediation */
339
340 auth = auth_cfg_create();
341 auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
342 auth->add(auth, AUTH_RULE_IDENTITY, user);
343 peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
344 auth = auth_cfg_create();
345 auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
346 auth->add(auth, AUTH_RULE_IDENTITY, gateway);
347 peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE);
348
349 child_cfg = child_cfg_create(this->current, &lifetime, NULL /* updown */,
350 TRUE, MODE_TUNNEL, ACTION_NONE, ACTION_NONE,
351 ACTION_NONE, FALSE, 0, 0, NULL, NULL, 0);
352 child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
353 ts = traffic_selector_create_dynamic(0, 0, 65535);
354 child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
355 ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE, "0.0.0.0",
356 0, "255.255.255.255", 65535);
357 child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
358 peer_cfg->add_child_cfg(peer_cfg, child_cfg);
359
360 /* get us an IKE_SA */
361 ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager,
362 peer_cfg);
363 if (!ike_sa)
364 {
365 peer_cfg->destroy(peer_cfg);
366 this->status = VPN_STATUS_CONNECTION_FAILED;
367 return FALSE;
368 }
369 if (!ike_sa->get_peer_cfg(ike_sa))
370 {
371 ike_sa->set_peer_cfg(ike_sa, peer_cfg);
372 }
373 peer_cfg->destroy(peer_cfg);
374
375 /* store the IKE_SA, so we can track its progress */
376 this->ike_sa = ike_sa;
377 this->status = VPN_STATUS_CONNECTING;
378 this->public.listener.ike_updown = _ike_updown;
379 this->public.listener.ike_state_change = _ike_state_change;
380 charon->bus->add_listener(charon->bus, &this->public.listener);
381
382 /* get an additional reference because initiate consumes one */
383 child_cfg->get_ref(child_cfg);
384 if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS)
385 {
386 DBG1(DBG_CFG, "failed to initiate tunnel");
387 charon->bus->remove_listener(charon->bus, &this->public.listener);
388 charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
389 ike_sa);
390 this->status = VPN_STATUS_CONNECTION_FAILED;
391 return FALSE;
392 }
393 charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
394 return TRUE;
395 }
396
397 /**
398 * Callback for libosso dbus wrapper
399 */
400 static gint dbus_req_handler(const gchar *interface, const gchar *method,
401 GArray *arguments, private_maemo_service_t *this,
402 osso_rpc_t *retval)
403 {
404 if (streq(method, "Start"))
405 { /* void start (void), dummy function to start charon as root */
406 return OSSO_OK;
407 }
408 else if (streq(method, "Connect"))
409 { /* bool connect (name, host, cert, user, pass) */
410 retval->value.b = initiate_connection(this, arguments);
411 retval->type = DBUS_TYPE_BOOLEAN;
412 }
413 else if (streq(method, "Disconnect"))
414 { /* void disconnect (void) */
415 disconnect(this);
416 }
417 else
418 {
419 return OSSO_ERROR;
420 }
421 return OSSO_OK;
422 }
423
424 /**
425 * Main loop to handle D-BUS messages.
426 */
427 static job_requeue_t run(private_maemo_service_t *this)
428 {
429 this->loop = g_main_loop_new(NULL, FALSE);
430 g_main_loop_run(this->loop);
431 return JOB_REQUEUE_NONE;
432 }
433
434 /**
435 * Cancel the GLib Main Event Loop
436 */
437 static bool cancel(private_maemo_service_t *this)
438 {
439 if (this->loop)
440 {
441 if (g_main_loop_is_running(this->loop))
442 {
443 g_main_loop_quit(this->loop);
444 }
445 g_main_loop_unref(this->loop);
446 }
447 return TRUE;
448 }
449
450 METHOD(maemo_service_t, destroy, void,
451 private_maemo_service_t *this)
452 {
453 if (this->context)
454 {
455 osso_rpc_unset_cb_f(this->context,
456 OSSO_CHARON_SERVICE,
457 OSSO_CHARON_OBJECT,
458 OSSO_CHARON_IFACE,
459 (osso_rpc_cb_f*)dbus_req_handler,
460 this);
461 osso_deinitialize(this->context);
462 }
463 charon->bus->remove_listener(charon->bus, &this->public.listener);
464 lib->credmgr->remove_set(lib->credmgr, &this->creds->set);
465 this->creds->destroy(this->creds);
466 this->current = (g_free(this->current), NULL);
467 free(this);
468 }
469
470 /*
471 * See header
472 */
473 maemo_service_t *maemo_service_create()
474 {
475 osso_return_t result;
476 private_maemo_service_t *this;
477
478 INIT(this,
479 .public = {
480 .listener = {
481 .ike_updown = _ike_updown,
482 .ike_state_change = _ike_state_change,
483 .child_updown = _child_updown,
484 .ike_rekey = _ike_rekey,
485 },
486 .destroy = _destroy,
487 },
488 .creds = mem_cred_create(),
489 );
490
491 lib->credmgr->add_set(lib->credmgr, &this->creds->set);
492
493 this->context = osso_initialize(OSSO_CHARON_SERVICE, "0.0.1", TRUE, NULL);
494 if (!this->context)
495 {
496 DBG1(DBG_CFG, "failed to initialize OSSO context");
497 destroy(this);
498 return NULL;
499 }
500
501 result = osso_rpc_set_cb_f(this->context,
502 OSSO_CHARON_SERVICE,
503 OSSO_CHARON_OBJECT,
504 OSSO_CHARON_IFACE,
505 (osso_rpc_cb_f*)dbus_req_handler,
506 this);
507 if (result != OSSO_OK)
508 {
509 DBG1(DBG_CFG, "failed to set D-BUS callback (%d)", result);
510 destroy(this);
511 return NULL;
512 }
513
514 this->loop = NULL;
515 if (!g_thread_supported())
516 {
517 g_thread_init(NULL);
518 }
519
520 lib->processor->queue_job(lib->processor,
521 (job_t*)callback_job_create_with_prio((callback_job_cb_t)run, this,
522 NULL, (callback_job_cancel_t)cancel, JOB_PRIO_CRITICAL));
523
524 return &this->public;
525 }
526