/* * Copyright (C) 2013 Martin Willi * Copyright (C) 2013 revosec AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "xpc_channels.h" #include "xpc_logger.h" #include #include #include #include typedef struct private_xpc_channels_t private_xpc_channels_t; /** * Private data of an xpc_channels_t object. */ struct private_xpc_channels_t { /** * Public xpc_channels_t interface. */ xpc_channels_t public; /** * Registered channels, IKE_SA unique ID => entry_t */ hashtable_t *channels; /** * Lock for channels list */ rwlock_t *lock; /** * Callback credential set for passwords */ callback_cred_t *creds; }; /** * Channel entry */ typedef struct { /* XPC channel to App */ xpc_connection_t conn; /* associated IKE_SA unique identifier */ uintptr_t sa; /* did we already ask for a password? */ bool passworded; /* channel specific logger */ xpc_logger_t *logger; } entry_t; /** * Clean up an entry, canceling connection */ static void destroy_entry(entry_t *entry) { charon->bus->remove_logger(charon->bus, &entry->logger->logger); entry->logger->destroy(entry->logger); xpc_connection_suspend(entry->conn); xpc_release(entry->conn); free(entry); } /** * Find an IKE_SA unique identifier by a given XPC channel */ static uint32_t find_ike_sa_by_conn(private_xpc_channels_t *this, xpc_connection_t conn) { enumerator_t *enumerator; entry_t *entry; uint32_t ike_sa = 0; this->lock->read_lock(this->lock); enumerator = this->channels->create_enumerator(this->channels); while (enumerator->enumerate(enumerator, NULL, &entry)) { if (entry->conn == conn) { ike_sa = entry->sa; break; } } enumerator->destroy(enumerator); this->lock->unlock(this->lock); return ike_sa; } /** * Remove an entry for a given XPC connection */ static void remove_conn(private_xpc_channels_t *this, xpc_connection_t conn) { uintptr_t ike_sa; entry_t *entry; ike_sa = find_ike_sa_by_conn(this, conn); if (ike_sa) { this->lock->write_lock(this->lock); entry = this->channels->remove(this->channels, (void*)ike_sa); this->lock->unlock(this->lock); if (entry) { destroy_entry(entry); } } } /** * Trigger termination of a connection */ static void stop_connection(private_xpc_channels_t *this, uint32_t ike_sa, xpc_object_t request, xpc_object_t reply) { status_t status; status = charon->controller->terminate_ike(charon->controller, ike_sa, FALSE, NULL, NULL, 0); xpc_dictionary_set_bool(reply, "success", status != NOT_FOUND); } /** * XPC RPC command dispatch table */ static struct { char *name; void (*handler)(private_xpc_channels_t *this, uint32_t ike_sa, xpc_object_t request, xpc_object_t reply); } commands[] = { { "stop_connection", stop_connection }, }; /** * Handle a request message from App */ static void handle(private_xpc_channels_t *this, xpc_connection_t conn, xpc_object_t request) { xpc_object_t reply; const char *type, *rpc; bool found = FALSE; uint32_t ike_sa; int i; type = xpc_dictionary_get_string(request, "type"); if (type) { if (streq(type, "rpc")) { reply = xpc_dictionary_create_reply(request); rpc = xpc_dictionary_get_string(request, "rpc"); ike_sa = find_ike_sa_by_conn(this, conn); if (reply && rpc && ike_sa) { for (i = 0; i < countof(commands); i++) { if (streq(commands[i].name, rpc)) { found = TRUE; commands[i].handler(this, ike_sa, request, reply); break; } } } if (!found) { DBG1(DBG_CFG, "received invalid XPC rpc command: %s", rpc); } if (reply) { xpc_connection_send_message(conn, reply); xpc_release(reply); } } else { DBG1(DBG_CFG, "received unknown XPC message type: %s", type); } } else { DBG1(DBG_CFG, "received XPC message without a type"); } } METHOD(xpc_channels_t, add, void, private_xpc_channels_t *this, xpc_connection_t conn, uint32_t ike_sa) { entry_t *entry; INIT(entry, .conn = conn, .sa = ike_sa, .logger = xpc_logger_create(conn), ); xpc_retain(entry->conn); xpc_connection_set_event_handler(entry->conn, ^(xpc_object_t event) { if (event == XPC_ERROR_CONNECTION_INVALID || event == XPC_ERROR_CONNECTION_INTERRUPTED) { remove_conn(this, conn); } else if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) { handle(this, conn, event); } }); entry->logger->set_ike_sa(entry->logger, entry->sa); charon->bus->add_logger(charon->bus, &entry->logger->logger); this->lock->write_lock(this->lock); this->channels->put(this->channels, (void*)entry->sa, entry); this->lock->unlock(this->lock); xpc_connection_resume(conn); } METHOD(listener_t, alert, bool, private_xpc_channels_t *this, ike_sa_t *ike_sa, alert_t alert, va_list args) { const char *desc; switch (alert) { case ALERT_LOCAL_AUTH_FAILED: desc = "local-auth"; break; case ALERT_PEER_AUTH_FAILED: desc = "remote-auth"; break; case ALERT_PEER_ADDR_FAILED: desc = "dns"; break; case ALERT_PEER_INIT_UNREACHABLE: desc = "unreachable"; break; case ALERT_RETRANSMIT_SEND_TIMEOUT: desc = "timeout"; break; case ALERT_PROPOSAL_MISMATCH_IKE: case ALERT_PROPOSAL_MISMATCH_CHILD: desc = "proposal-mismatch"; break; case ALERT_TS_MISMATCH: desc = "ts-mismatch"; break; default: return TRUE; } if (ike_sa) { entry_t *entry; uintptr_t sa; sa = ike_sa->get_unique_id(ike_sa); this->lock->read_lock(this->lock); entry = this->channels->get(this->channels, (void*)sa); if (entry) { xpc_object_t msg; msg = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(msg, "type", "event"); xpc_dictionary_set_string(msg, "event", "alert"); xpc_dictionary_set_string(msg, "alert", desc); xpc_connection_send_message(entry->conn, msg); xpc_release(msg); } this->lock->unlock(this->lock); } return TRUE; } METHOD(listener_t, ike_rekey, bool, private_xpc_channels_t *this, ike_sa_t *old, ike_sa_t *new) { entry_t *entry; uintptr_t sa; sa = old->get_unique_id(old); this->lock->write_lock(this->lock); entry = this->channels->remove(this->channels, (void*)sa); if (entry) { entry->sa = new->get_unique_id(new); entry->logger->set_ike_sa(entry->logger, entry->sa); this->channels->put(this->channels, (void*)entry->sa, entry); } this->lock->unlock(this->lock); return TRUE; } METHOD(listener_t, ike_state_change, bool, private_xpc_channels_t *this, ike_sa_t *ike_sa, ike_sa_state_t state) { if (state == IKE_CONNECTING) { entry_t *entry; uintptr_t sa; sa = ike_sa->get_unique_id(ike_sa); this->lock->read_lock(this->lock); entry = this->channels->get(this->channels, (void*)sa); if (entry) { xpc_object_t msg; msg = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(msg, "type", "event"); xpc_dictionary_set_string(msg, "event", "connecting"); xpc_connection_send_message(entry->conn, msg); xpc_release(msg); } this->lock->unlock(this->lock); } return TRUE; } METHOD(listener_t, child_updown, bool, private_xpc_channels_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, bool up) { entry_t *entry; uintptr_t sa; sa = ike_sa->get_unique_id(ike_sa); this->lock->read_lock(this->lock); entry = this->channels->get(this->channels, (void*)sa); if (entry) { xpc_object_t msg; linked_list_t *list; char buf[256]; msg = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(msg, "type", "event"); if (up) { xpc_dictionary_set_string(msg, "event", "child_up"); } else { xpc_dictionary_set_string(msg, "event", "child_down"); } list = linked_list_create_from_enumerator( child_sa->create_ts_enumerator(child_sa, TRUE)); snprintf(buf, sizeof(buf), "%#R", list); list->destroy(list); xpc_dictionary_set_string(msg, "ts_local", buf); list = linked_list_create_from_enumerator( child_sa->create_ts_enumerator(child_sa, FALSE)); snprintf(buf, sizeof(buf), "%#R", list); list->destroy(list); xpc_dictionary_set_string(msg, "ts_remote", buf); xpc_connection_send_message(entry->conn, msg); xpc_release(msg); } this->lock->unlock(this->lock); return TRUE; } METHOD(listener_t, ike_updown, bool, private_xpc_channels_t *this, ike_sa_t *ike_sa, bool up) { xpc_object_t msg; entry_t *entry; uintptr_t sa; sa = ike_sa->get_unique_id(ike_sa); if (up) { this->lock->read_lock(this->lock); entry = this->channels->get(this->channels, (void*)sa); if (entry) { msg = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(msg, "type", "event"); xpc_dictionary_set_string(msg, "event", "up"); xpc_connection_send_message(entry->conn, msg); xpc_release(msg); } this->lock->unlock(this->lock); } else { this->lock->write_lock(this->lock); entry = this->channels->remove(this->channels, (void*)sa); this->lock->unlock(this->lock); if (entry) { msg = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(msg, "type", "event"); xpc_dictionary_set_string(msg, "event", "down"); xpc_connection_send_message(entry->conn, msg); xpc_release(msg); xpc_connection_send_barrier(entry->conn, ^() { destroy_entry(entry); }); } } return TRUE; } /** * Query password from App using XPC channel */ static shared_key_t *query_password(xpc_connection_t conn, identification_t *id) { char buf[128], *password; xpc_object_t request, response; shared_key_t *shared = NULL; request = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(request, "type", "rpc"); xpc_dictionary_set_string(request, "rpc", "get_password"); snprintf(buf, sizeof(buf), "%Y", id); xpc_dictionary_set_string(request, "username", buf); response = xpc_connection_send_message_with_reply_sync(conn, request); xpc_release(request); if (xpc_get_type(response) == XPC_TYPE_DICTIONARY) { password = (char*)xpc_dictionary_get_string(response, "password"); if (password) { shared = shared_key_create(SHARED_EAP, chunk_clone(chunk_from_str(password))); } } xpc_release(response); return shared; } /** * Password query callback */ static shared_key_t* password_cb(private_xpc_channels_t *this, shared_key_type_t type, identification_t *me, identification_t *other, id_match_t *match_me, id_match_t *match_other) { shared_key_t *shared = NULL; ike_sa_t *ike_sa; entry_t *entry; uint32_t sa; switch (type) { case SHARED_EAP: break; default: return NULL; } ike_sa = charon->bus->get_sa(charon->bus); if (ike_sa) { sa = ike_sa->get_unique_id(ike_sa); this->lock->read_lock(this->lock); entry = this->channels->get(this->channels, (void*)(uintptr_t)sa); if (entry && !entry->passworded) { entry->passworded = TRUE; shared = query_password(entry->conn, me); if (shared) { if (match_me) { *match_me = ID_MATCH_PERFECT; } if (match_other) { *match_other = ID_MATCH_PERFECT; } } } this->lock->unlock(this->lock); } return shared; } METHOD(xpc_channels_t, destroy, void, private_xpc_channels_t *this) { lib->credmgr->remove_set(lib->credmgr, &this->creds->set); this->creds->destroy(this->creds); this->channels->destroy(this->channels); this->lock->destroy(this->lock); free(this); } /** * See header */ xpc_channels_t *xpc_channels_create() { private_xpc_channels_t *this; INIT(this, .public = { .listener = { .alert = _alert, .ike_updown = _ike_updown, .ike_rekey = _ike_rekey, .ike_state_change = _ike_state_change, .child_updown = _child_updown, }, .add = _add, .destroy = _destroy, }, .channels = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), ); this->creds = callback_cred_create_shared( (callback_cred_shared_cb_t)password_cb, this); lib->credmgr->add_set(lib->credmgr, &this->creds->set); return &this->public; }