controller: Add option to force destruction of an IKE_SA
[strongswan.git] / src / frontends / osx / charon-xpc / xpc_channels.c
index fc03ab3..d013e75 100644 (file)
@@ -14,6 +14,7 @@
  */
 
 #include "xpc_channels.h"
+#include "xpc_logger.h"
 
 #include <credentials/sets/callback_cred.h>
 #include <collections/hashtable.h>
@@ -58,6 +59,8 @@ typedef struct {
        uintptr_t sa;
        /* did we already ask for a password? */
        bool passworded;
+       /* channel specific logger */
+       xpc_logger_t *logger;
 } entry_t;
 
 /**
@@ -65,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);
@@ -132,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)
 {
@@ -144,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);
@@ -151,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)
 {
@@ -213,8 +445,11 @@ static shared_key_t *query_password(xpc_connection_t conn, identification_t *id)
        if (xpc_get_type(response) == XPC_TYPE_DICTIONARY)
        {
                password = (char*)xpc_dictionary_get_string(response, "password");
-               shared = shared_key_create(SHARED_EAP,
-                                                                  chunk_clone(chunk_from_str(password)));
+               if (password)
+               {
+                       shared = shared_key_create(SHARED_EAP,
+                                                                          chunk_clone(chunk_from_str(password)));
+               }
        }
        xpc_release(response);
        return shared;
@@ -231,7 +466,7 @@ static shared_key_t* password_cb(private_xpc_channels_t *this,
        shared_key_t *shared = NULL;
        ike_sa_t *ike_sa;
        entry_t *entry;
-       u_int32_t sa;
+       uint32_t sa;
 
        switch (type)
        {
@@ -245,7 +480,7 @@ static shared_key_t* password_cb(private_xpc_channels_t *this,
        {
                sa = ike_sa->get_unique_id(ike_sa);
                this->lock->read_lock(this->lock);
-               entry = this->channels->get(this->channels, (void*)sa);
+               entry = this->channels->get(this->channels, (void*)(uintptr_t)sa);
                if (entry && !entry->passworded)
                {
                        entry->passworded = TRUE;
@@ -271,19 +506,8 @@ static shared_key_t* password_cb(private_xpc_channels_t *this,
 METHOD(xpc_channels_t, destroy, void,
        private_xpc_channels_t *this)
 {
-       enumerator_t *enumerator;
-       entry_t *entry;
-
        lib->credmgr->remove_set(lib->credmgr, &this->creds->set);
        this->creds->destroy(this->creds);
-
-       enumerator = this->channels->create_enumerator(this->channels);
-       while (enumerator->enumerate(enumerator, NULL, &entry))
-       {
-               destroy_entry(entry);
-       }
-       enumerator->destroy(enumerator);
-
        this->channels->destroy(this->channels);
        this->lock->destroy(this->lock);
        free(this);
@@ -299,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,