/* * 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_dispatch.h" #include "xpc_channels.h" #include #include #include #include #include typedef struct private_xpc_dispatch_t private_xpc_dispatch_t; /** * Private data of an xpc_dispatch_t object. */ struct private_xpc_dispatch_t { /** * Public xpc_dispatch_t interface. */ xpc_dispatch_t public; /** * XPC service we offer */ xpc_connection_t service; /** * XPC IKE_SA specific channels to App */ xpc_channels_t *channels; /** * GCD queue for XPC events */ dispatch_queue_t queue; /** * Number of active App connections */ refcount_t refcount; /** * PID of main thread */ pid_t pid; }; /** * Return version of this helper */ static void get_version(private_xpc_dispatch_t *this, xpc_object_t request, xpc_object_t reply) { xpc_dictionary_set_string(reply, "version", PACKAGE_VERSION); } /** * Create peer config with associated ike config */ static peer_cfg_t* create_peer_cfg(char *name, char *host) { ike_cfg_t *ike_cfg; peer_cfg_t *peer_cfg; uint16_t local_port, remote_port = IKEV2_UDP_PORT; local_port = charon->socket->get_port(charon->socket, FALSE); if (local_port != IKEV2_UDP_PORT) { remote_port = IKEV2_NATT_PORT; } ike_cfg = ike_cfg_create(IKEV2, FALSE, FALSE, "0.0.0.0", local_port, host, remote_port, FRAGMENTATION_NO, 0); ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE)); ike_cfg->add_proposal(ike_cfg, proposal_create_default_aead(PROTO_IKE)); peer_cfg = peer_cfg_create(name, ike_cfg, CERT_SEND_IF_ASKED, UNIQUE_REPLACE, 1, /* keyingtries */ 36000, 0, /* rekey 10h, reauth none */ 600, 600, /* jitter, over 10min */ TRUE, FALSE, TRUE, /* mobike, aggressive, pull */ 30, 0, /* DPD delay, timeout */ FALSE, NULL, NULL); /* mediation */ peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0)); return peer_cfg; } /** * Add a single auth cfg of given class to peer cfg */ static void add_auth_cfg(peer_cfg_t *peer_cfg, bool local, char *id, auth_class_t class) { auth_cfg_t *auth; auth = auth_cfg_create(); auth->add(auth, AUTH_RULE_AUTH_CLASS, class); auth->add(auth, AUTH_RULE_IDENTITY, identification_create_from_string(id)); if (!local) { auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE); } peer_cfg->add_auth_cfg(peer_cfg, auth, local); } /** * Attach child config to peer config */ static child_cfg_t* create_child_cfg(char *name) { child_cfg_t *child_cfg; traffic_selector_t *ts; lifetime_cfg_t lifetime = { .time = { .life = 10800 /* 3h */, .rekey = 10200 /* 2h50min */, .jitter = 300 /* 5min */ } }; child_cfg = child_cfg_create(name, &lifetime, NULL, FALSE, MODE_TUNNEL, /* updown, hostaccess */ ACTION_NONE, ACTION_NONE, ACTION_NONE, FALSE, 0, 0, NULL, NULL, 0); child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, "aes128gcm8-aes128gcm12-aes128gcm16-" "aes256gcm8-aes256gcm12-aes256gcm16")); child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP)); child_cfg->add_proposal(child_cfg, proposal_create_default_aead(PROTO_ESP)); ts = traffic_selector_create_dynamic(0, 0, 65535); child_cfg->add_traffic_selector(child_cfg, TRUE, ts); ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE, "0.0.0.0", 0, "255.255.255.255", 65535); child_cfg->add_traffic_selector(child_cfg, FALSE, ts); return child_cfg; } /** * Controller initiate callback */ static bool initiate_cb(uint32_t *sa, debug_t group, level_t level, ike_sa_t *ike_sa, const char *message) { if (ike_sa) { *sa = ike_sa->get_unique_id(ike_sa); return FALSE; } return TRUE; } /** * Start initiating an IKE connection */ void start_connection(private_xpc_dispatch_t *this, xpc_object_t request, xpc_object_t reply) { peer_cfg_t *peer_cfg; child_cfg_t *child_cfg; char *name, *id, *host; bool success = FALSE; xpc_endpoint_t endpoint; xpc_connection_t channel; uint32_t ike_sa; name = (char*)xpc_dictionary_get_string(request, "name"); host = (char*)xpc_dictionary_get_string(request, "host"); id = (char*)xpc_dictionary_get_string(request, "id"); endpoint = xpc_dictionary_get_value(request, "channel"); channel = xpc_connection_create_from_endpoint(endpoint); if (name && id && host && channel) { peer_cfg = create_peer_cfg(name, host); add_auth_cfg(peer_cfg, TRUE, id, AUTH_CLASS_EAP); add_auth_cfg(peer_cfg, FALSE, host, AUTH_CLASS_ANY); child_cfg = create_child_cfg(name); peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg)); if (charon->controller->initiate(charon->controller, peer_cfg, child_cfg, (controller_cb_t)initiate_cb, &ike_sa, 0, FALSE) == NEED_MORE) { this->channels->add(this->channels, channel, ike_sa); success = TRUE; } } xpc_dictionary_set_bool(reply, "success", success); } /** * XPC RPC command dispatch table */ static struct { char *name; void (*handler)(private_xpc_dispatch_t *this, xpc_object_t request, xpc_object_t reply); } commands[] = { { "get_version", get_version }, { "start_connection", start_connection }, }; /** * Handle a received XPC request message */ static void handle(private_xpc_dispatch_t *this, xpc_object_t request) { xpc_connection_t client; xpc_object_t reply; const char *type, *rpc; bool found = FALSE; 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"); if (reply && rpc) { for (i = 0; i < countof(commands); i++) { if (streq(commands[i].name, rpc)) { found = TRUE; commands[i].handler(this, request, reply); break; } } } if (!found) { DBG1(DBG_CFG, "received invalid XPC rpc command: %s", rpc); } if (reply) { client = xpc_dictionary_get_remote_connection(request); xpc_connection_send_message(client, 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"); } } /** * Finalizer for client connections */ static void cleanup_connection(private_xpc_dispatch_t *this) { if (ref_put(&this->refcount)) { DBG1(DBG_CFG, "no XPC connections, raising SIGTERM"); kill(this->pid, SIGTERM); } } /** * Set up GCD handler for XPC events */ static void set_handler(private_xpc_dispatch_t *this) { xpc_retain(this->service); xpc_connection_set_event_handler(this->service, ^(xpc_object_t conn) { xpc_connection_set_event_handler(conn, ^(xpc_object_t event) { if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) { handle(this, event); } }); ref_get(&this->refcount); xpc_connection_set_context(conn, this); xpc_connection_set_finalizer_f(conn, (void*)cleanup_connection); xpc_connection_resume(conn); }); xpc_connection_resume(this->service); } METHOD(xpc_dispatch_t, destroy, void, private_xpc_dispatch_t *this) { charon->bus->remove_listener(charon->bus, &this->channels->listener); this->channels->destroy(this->channels); if (this->service) { xpc_connection_suspend(this->service); xpc_release(this->service); } free(this); } /** * See header */ xpc_dispatch_t *xpc_dispatch_create() { private_xpc_dispatch_t *this; INIT(this, .public = { .destroy = _destroy, }, .channels = xpc_channels_create(), .queue = dispatch_queue_create("org.strongswan.charon-xpc.q", DISPATCH_QUEUE_CONCURRENT), .pid = getpid(), ); charon->bus->add_listener(charon->bus, &this->channels->listener); this->service = xpc_connection_create_mach_service( "org.strongswan.charon-xpc", this->queue, XPC_CONNECTION_MACH_SERVICE_LISTENER); if (!this->service) { destroy(this); return NULL; } set_handler(this); return &this->public; }