charon-xpc: fix TS getting after changing CHILD_SA API
[strongswan.git] / src / frontends / osx / charon-xpc / xpc_channels.c
1 /*
2 * Copyright (C) 2013 Martin Willi
3 * Copyright (C) 2013 revosec AG
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 "xpc_channels.h"
17 #include "xpc_logger.h"
18
19 #include <credentials/sets/callback_cred.h>
20 #include <collections/hashtable.h>
21 #include <threading/rwlock.h>
22 #include <daemon.h>
23
24 typedef struct private_xpc_channels_t private_xpc_channels_t;
25
26 /**
27 * Private data of an xpc_channels_t object.
28 */
29 struct private_xpc_channels_t {
30
31 /**
32 * Public xpc_channels_t interface.
33 */
34 xpc_channels_t public;
35
36 /**
37 * Registered channels, IKE_SA unique ID => entry_t
38 */
39 hashtable_t *channels;
40
41 /**
42 * Lock for channels list
43 */
44 rwlock_t *lock;
45
46 /**
47 * Callback credential set for passwords
48 */
49 callback_cred_t *creds;
50 };
51
52 /**
53 * Channel entry
54 */
55 typedef struct {
56 /* XPC channel to App */
57 xpc_connection_t conn;
58 /* associated IKE_SA unique identifier */
59 uintptr_t sa;
60 /* did we already ask for a password? */
61 bool passworded;
62 /* channel specific logger */
63 xpc_logger_t *logger;
64 } entry_t;
65
66 /**
67 * Clean up an entry, cancelling connection
68 */
69 static void destroy_entry(entry_t *entry)
70 {
71 charon->bus->remove_logger(charon->bus, &entry->logger->logger);
72 entry->logger->destroy(entry->logger);
73 xpc_connection_suspend(entry->conn);
74 xpc_release(entry->conn);
75 free(entry);
76 }
77
78 /**
79 * Find an IKE_SA unique identifier by a given XPC channel
80 */
81 static u_int32_t find_ike_sa_by_conn(private_xpc_channels_t *this,
82 xpc_connection_t conn)
83 {
84 enumerator_t *enumerator;
85 entry_t *entry;
86 u_int32_t ike_sa = 0;
87
88 this->lock->read_lock(this->lock);
89 enumerator = this->channels->create_enumerator(this->channels);
90 while (enumerator->enumerate(enumerator, NULL, &entry))
91 {
92 if (entry->conn == conn)
93 {
94 ike_sa = entry->sa;
95 break;
96 }
97 }
98 enumerator->destroy(enumerator);
99 this->lock->unlock(this->lock);
100
101 return ike_sa;
102 }
103
104 /**
105 * Remove an entry for a given XPC connection
106 */
107 static void remove_conn(private_xpc_channels_t *this, xpc_connection_t conn)
108 {
109 uintptr_t ike_sa;
110 entry_t *entry;
111
112 ike_sa = find_ike_sa_by_conn(this, conn);
113 if (ike_sa)
114 {
115 this->lock->write_lock(this->lock);
116 entry = this->channels->remove(this->channels, (void*)ike_sa);
117 this->lock->unlock(this->lock);
118
119 if (entry)
120 {
121 destroy_entry(entry);
122 }
123 }
124 }
125
126 /**
127 * Trigger termination of a connection
128 */
129 static void stop_connection(private_xpc_channels_t *this, u_int32_t ike_sa,
130 xpc_object_t request, xpc_object_t reply)
131 {
132 status_t status;
133
134 status = charon->controller->terminate_ike(charon->controller, ike_sa,
135 NULL, NULL, 0);
136 xpc_dictionary_set_bool(reply, "success", status != NOT_FOUND);
137 }
138
139 /**
140 * XPC RPC command dispatch table
141 */
142 static struct {
143 char *name;
144 void (*handler)(private_xpc_channels_t *this, u_int32_t ike_sa,
145 xpc_object_t request, xpc_object_t reply);
146 } commands[] = {
147 { "stop_connection", stop_connection },
148 };
149
150 /**
151 * Handle a request message from App
152 */
153 static void handle(private_xpc_channels_t *this, xpc_connection_t conn,
154 xpc_object_t request)
155 {
156 xpc_object_t reply;
157 const char *type, *rpc;
158 bool found = FALSE;
159 u_int32_t ike_sa;
160 int i;
161
162 type = xpc_dictionary_get_string(request, "type");
163 if (type)
164 {
165 if (streq(type, "rpc"))
166 {
167 reply = xpc_dictionary_create_reply(request);
168 rpc = xpc_dictionary_get_string(request, "rpc");
169 ike_sa = find_ike_sa_by_conn(this, conn);
170 if (reply && rpc && ike_sa)
171 {
172 for (i = 0; i < countof(commands); i++)
173 {
174 if (streq(commands[i].name, rpc))
175 {
176 found = TRUE;
177 commands[i].handler(this, ike_sa, request, reply);
178 break;
179 }
180 }
181 }
182 if (!found)
183 {
184 DBG1(DBG_CFG, "received invalid XPC rpc command: %s", rpc);
185 }
186 if (reply)
187 {
188 xpc_connection_send_message(conn, reply);
189 xpc_release(reply);
190 }
191 }
192 else
193 {
194 DBG1(DBG_CFG, "received unknown XPC message type: %s", type);
195 }
196 }
197 else
198 {
199 DBG1(DBG_CFG, "received XPC message without a type");
200 }
201 }
202
203 METHOD(xpc_channels_t, add, void,
204 private_xpc_channels_t *this, xpc_connection_t conn, u_int32_t ike_sa)
205 {
206 entry_t *entry;
207
208 INIT(entry,
209 .conn = conn,
210 .sa = ike_sa,
211 .logger = xpc_logger_create(conn),
212 );
213
214 xpc_connection_set_event_handler(entry->conn, ^(xpc_object_t event)
215 {
216 if (event == XPC_ERROR_CONNECTION_INVALID ||
217 event == XPC_ERROR_CONNECTION_INTERRUPTED)
218 {
219 remove_conn(this, conn);
220 }
221 else if (xpc_get_type(event) == XPC_TYPE_DICTIONARY)
222 {
223 handle(this, conn, event);
224 }
225 });
226
227 entry->logger->set_ike_sa(entry->logger, entry->sa);
228 charon->bus->add_logger(charon->bus, &entry->logger->logger);
229
230 this->lock->write_lock(this->lock);
231 this->channels->put(this->channels, (void*)entry->sa, entry);
232 this->lock->unlock(this->lock);
233
234 xpc_connection_resume(conn);
235 }
236
237 METHOD(listener_t, alert, bool,
238 private_xpc_channels_t *this, ike_sa_t *ike_sa, alert_t alert, va_list args)
239 {
240 const char *desc;
241
242 switch (alert)
243 {
244 case ALERT_LOCAL_AUTH_FAILED:
245 desc = "local-auth";
246 break;
247 case ALERT_PEER_AUTH_FAILED:
248 desc = "remote-auth";
249 break;
250 case ALERT_PEER_ADDR_FAILED:
251 desc = "dns";
252 break;
253 case ALERT_PEER_INIT_UNREACHABLE:
254 desc = "unreachable";
255 break;
256 case ALERT_RETRANSMIT_SEND_TIMEOUT:
257 desc = "timeout";
258 break;
259 case ALERT_PROPOSAL_MISMATCH_IKE:
260 case ALERT_PROPOSAL_MISMATCH_CHILD:
261 desc = "proposal-mismatch";
262 break;
263 case ALERT_TS_MISMATCH:
264 desc = "ts-mismatch";
265 break;
266 default:
267 return TRUE;
268 }
269 if (ike_sa)
270 {
271 entry_t *entry;
272 uintptr_t sa;
273
274 sa = ike_sa->get_unique_id(ike_sa);
275 this->lock->read_lock(this->lock);
276 entry = this->channels->get(this->channels, (void*)sa);
277 if (entry)
278 {
279 xpc_object_t msg;
280
281 msg = xpc_dictionary_create(NULL, NULL, 0);
282 xpc_dictionary_set_string(msg, "type", "event");
283 xpc_dictionary_set_string(msg, "event", "alert");
284 xpc_dictionary_set_string(msg, "alert", desc);
285 xpc_connection_send_message(entry->conn, msg);
286 xpc_release(msg);
287 }
288 this->lock->unlock(this->lock);
289 }
290 return TRUE;
291 }
292
293 METHOD(listener_t, ike_rekey, bool,
294 private_xpc_channels_t *this, ike_sa_t *old, ike_sa_t *new)
295 {
296 entry_t *entry;
297 uintptr_t sa;
298
299 sa = old->get_unique_id(old);
300 this->lock->write_lock(this->lock);
301 entry = this->channels->remove(this->channels, (void*)sa);
302 if (entry)
303 {
304 entry->sa = new->get_unique_id(new);
305 entry->logger->set_ike_sa(entry->logger, entry->sa);
306 this->channels->put(this->channels, (void*)entry->sa, entry);
307 }
308 this->lock->unlock(this->lock);
309
310 return TRUE;
311 }
312
313 METHOD(listener_t, ike_state_change, bool,
314 private_xpc_channels_t *this, ike_sa_t *ike_sa, ike_sa_state_t state)
315 {
316 if (state == IKE_CONNECTING)
317 {
318 entry_t *entry;
319 uintptr_t sa;
320
321 sa = ike_sa->get_unique_id(ike_sa);
322 this->lock->read_lock(this->lock);
323 entry = this->channels->get(this->channels, (void*)sa);
324 if (entry)
325 {
326 xpc_object_t msg;
327
328 msg = xpc_dictionary_create(NULL, NULL, 0);
329 xpc_dictionary_set_string(msg, "type", "event");
330 xpc_dictionary_set_string(msg, "event", "connecting");
331 xpc_connection_send_message(entry->conn, msg);
332 xpc_release(msg);
333 }
334 this->lock->unlock(this->lock);
335 }
336 return TRUE;
337 }
338
339 METHOD(listener_t, child_updown, bool,
340 private_xpc_channels_t *this, ike_sa_t *ike_sa,
341 child_sa_t *child_sa, bool up)
342 {
343 entry_t *entry;
344 uintptr_t sa;
345
346 sa = ike_sa->get_unique_id(ike_sa);
347 this->lock->read_lock(this->lock);
348 entry = this->channels->get(this->channels, (void*)sa);
349 if (entry)
350 {
351 xpc_object_t msg;
352 linked_list_t *list;
353 char buf[256];
354
355 msg = xpc_dictionary_create(NULL, NULL, 0);
356 xpc_dictionary_set_string(msg, "type", "event");
357 if (up)
358 {
359 xpc_dictionary_set_string(msg, "event", "child_up");
360 }
361 else
362 {
363 xpc_dictionary_set_string(msg, "event", "child_down");
364 }
365
366 list = linked_list_create_from_enumerator(
367 child_sa->create_ts_enumerator(child_sa, TRUE));
368 snprintf(buf, sizeof(buf), "%#R", list);
369 list->destroy(list);
370 xpc_dictionary_set_string(msg, "ts_local", buf);
371
372 list = linked_list_create_from_enumerator(
373 child_sa->create_ts_enumerator(child_sa, FALSE));
374 snprintf(buf, sizeof(buf), "%#R", list);
375 list->destroy(list);
376 xpc_dictionary_set_string(msg, "ts_remote", buf);
377
378 xpc_connection_send_message(entry->conn, msg);
379 xpc_release(msg);
380 }
381 this->lock->unlock(this->lock);
382 return TRUE;
383 }
384
385 METHOD(listener_t, ike_updown, bool,
386 private_xpc_channels_t *this, ike_sa_t *ike_sa, bool up)
387 {
388 xpc_object_t msg;
389 entry_t *entry;
390 uintptr_t sa;
391
392 sa = ike_sa->get_unique_id(ike_sa);
393 if (up)
394 {
395 this->lock->read_lock(this->lock);
396 entry = this->channels->get(this->channels, (void*)sa);
397 if (entry)
398 {
399 msg = xpc_dictionary_create(NULL, NULL, 0);
400 xpc_dictionary_set_string(msg, "type", "event");
401 xpc_dictionary_set_string(msg, "event", "up");
402 xpc_connection_send_message(entry->conn, msg);
403 xpc_release(msg);
404 }
405 this->lock->unlock(this->lock);
406 }
407 else
408 {
409 this->lock->write_lock(this->lock);
410 entry = this->channels->remove(this->channels, (void*)sa);
411 this->lock->unlock(this->lock);
412 if (entry)
413 {
414 msg = xpc_dictionary_create(NULL, NULL, 0);
415 xpc_dictionary_set_string(msg, "type", "event");
416 xpc_dictionary_set_string(msg, "event", "down");
417 xpc_connection_send_message(entry->conn, msg);
418 xpc_release(msg);
419 xpc_connection_send_barrier(entry->conn, ^() {
420 destroy_entry(entry);
421 });
422 }
423 }
424 return TRUE;
425 }
426
427 /**
428 * Query password from App using XPC channel
429 */
430 static shared_key_t *query_password(xpc_connection_t conn, identification_t *id)
431 {
432 char buf[128], *password;
433 xpc_object_t request, response;
434 shared_key_t *shared = NULL;
435
436 request = xpc_dictionary_create(NULL, NULL, 0);
437 xpc_dictionary_set_string(request, "type", "rpc");
438 xpc_dictionary_set_string(request, "rpc", "get_password");
439 snprintf(buf, sizeof(buf), "%Y", id);
440 xpc_dictionary_set_string(request, "username", buf);
441
442 response = xpc_connection_send_message_with_reply_sync(conn, request);
443 xpc_release(request);
444 if (xpc_get_type(response) == XPC_TYPE_DICTIONARY)
445 {
446 password = (char*)xpc_dictionary_get_string(response, "password");
447 if (password)
448 {
449 shared = shared_key_create(SHARED_EAP,
450 chunk_clone(chunk_from_str(password)));
451 }
452 }
453 xpc_release(response);
454 return shared;
455 }
456
457 /**
458 * Password query callback
459 */
460 static shared_key_t* password_cb(private_xpc_channels_t *this,
461 shared_key_type_t type,
462 identification_t *me, identification_t *other,
463 id_match_t *match_me, id_match_t *match_other)
464 {
465 shared_key_t *shared = NULL;
466 ike_sa_t *ike_sa;
467 entry_t *entry;
468 u_int32_t sa;
469
470 switch (type)
471 {
472 case SHARED_EAP:
473 break;
474 default:
475 return NULL;
476 }
477 ike_sa = charon->bus->get_sa(charon->bus);
478 if (ike_sa)
479 {
480 sa = ike_sa->get_unique_id(ike_sa);
481 this->lock->read_lock(this->lock);
482 entry = this->channels->get(this->channels, (void*)sa);
483 if (entry && !entry->passworded)
484 {
485 entry->passworded = TRUE;
486
487 shared = query_password(entry->conn, me);
488 if (shared)
489 {
490 if (match_me)
491 {
492 *match_me = ID_MATCH_PERFECT;
493 }
494 if (match_other)
495 {
496 *match_other = ID_MATCH_PERFECT;
497 }
498 }
499 }
500 this->lock->unlock(this->lock);
501 }
502 return shared;
503 }
504
505 METHOD(xpc_channels_t, destroy, void,
506 private_xpc_channels_t *this)
507 {
508 lib->credmgr->remove_set(lib->credmgr, &this->creds->set);
509 this->creds->destroy(this->creds);
510 this->channels->destroy(this->channels);
511 this->lock->destroy(this->lock);
512 free(this);
513 }
514
515 /**
516 * See header
517 */
518 xpc_channels_t *xpc_channels_create()
519 {
520 private_xpc_channels_t *this;
521
522 INIT(this,
523 .public = {
524 .listener = {
525 .alert = _alert,
526 .ike_updown = _ike_updown,
527 .ike_rekey = _ike_rekey,
528 .ike_state_change = _ike_state_change,
529 .child_updown = _child_updown,
530 },
531 .add = _add,
532 .destroy = _destroy,
533 },
534 .channels = hashtable_create(hashtable_hash_ptr,
535 hashtable_equals_ptr, 4),
536 .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
537 );
538
539 this->creds = callback_cred_create_shared(
540 (callback_cred_shared_cb_t)password_cb, this);
541 lib->credmgr->add_set(lib->credmgr, &this->creds->set);
542
543 return &this->public;
544 }