controller: Add option to force destruction of an IKE_SA
[strongswan.git] / src / frontends / osx / charon-xpc / xpc_channels.c
index e8eb225..d013e75 100644 (file)
@@ -14,7 +14,9 @@
  */
 
 #include "xpc_channels.h"
+#include "xpc_logger.h"
 
+#include <credentials/sets/callback_cred.h>
 #include <collections/hashtable.h>
 #include <threading/rwlock.h>
 #include <daemon.h>
@@ -40,6 +42,11 @@ struct private_xpc_channels_t {
         * Lock for channels list
         */
        rwlock_t *lock;
+
+       /**
+        * Callback credential set for passwords
+        */
+       callback_cred_t *creds;
 };
 
 /**
@@ -50,6 +57,10 @@ typedef struct {
        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;
 
 /**
@@ -57,66 +68,166 @@ typedef struct {
  */
 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_connection_cancel(entry->conn);
        xpc_release(entry->conn);
        free(entry);
 }
 
 /**
- * Remove an entry for a given XPC connection
+ * Find an IKE_SA unique identifier by a given XPC channel
  */
-static void remove_conn(private_xpc_channels_t *this, xpc_connection_t conn)
+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->write_lock(this->lock);
+       this->lock->read_lock(this->lock);
        enumerator = this->channels->create_enumerator(this->channels);
        while (enumerator->enumerate(enumerator, NULL, &entry))
        {
-               if (xpc_equal(entry->conn, conn))
+               if (entry->conn == conn)
                {
-                       this->channels->remove(this->channels, enumerator);
-                       destroy_entry(entry);
+                       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_object_t request)
+static void handle(private_xpc_channels_t *this, xpc_connection_t conn,
+                                  xpc_object_t request)
 {
-       /* TODO: */
+       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, u_int32_t ike_sa)
+       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_connection_set_event_handler(entry->conn, ^(xpc_object_t event) {
-
+       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, entry->conn);
+                       remove_conn(this, conn);
                }
-               else
+               else if (xpc_get_type(event) == XPC_TYPE_DICTIONARY)
                {
-                       handle(this, event);
+                       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);
@@ -124,6 +235,62 @@ METHOD(xpc_channels_t, add, void,
        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)
 {
@@ -136,6 +303,7 @@ METHOD(listener_t, ike_rekey, bool,
        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);
@@ -143,6 +311,78 @@ METHOD(listener_t, ike_rekey, bool,
        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)
 {
@@ -185,19 +425,89 @@ METHOD(listener_t, ike_updown, bool,
        return TRUE;
 }
 
-METHOD(xpc_channels_t, destroy, void,
-       private_xpc_channels_t *this)
+/**
+ * Query password from App using XPC channel
+ */
+static shared_key_t *query_password(xpc_connection_t conn, identification_t *id)
 {
-       enumerator_t *enumerator;
+       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;
 
-       enumerator = this->channels->create_enumerator(this->channels);
-       while (enumerator->enumerate(enumerator, NULL, &entry))
+       switch (type)
        {
-               destroy_entry(entry);
+               case SHARED_EAP:
+                       break;
+               default:
+                       return NULL;
        }
-       enumerator->destroy(enumerator);
+       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);
@@ -213,8 +523,11 @@ xpc_channels_t *xpc_channels_create()
        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,
@@ -224,5 +537,9 @@ xpc_channels_t *xpc_channels_create()
                .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;
 }