ike-cfg: Pass arguments as struct
[strongswan.git] / src / charon-cmd / cmd / cmd_connection.c
index f58df5c..b91c898 100644 (file)
@@ -1,4 +1,7 @@
 /*
+ * Copyright (C) 2013 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
  * Copyright (C) 2013 Martin Willi
  * Copyright (C) 2013 revosec AG
  *
 
 #include <utils/debug.h>
 #include <processing/jobs/callback_job.h>
+#include <threading/thread.h>
 #include <daemon.h>
 
+typedef enum profile_t profile_t;
 typedef struct private_cmd_connection_t private_cmd_connection_t;
 
 /**
+ * Connection profiles we support
+ */
+enum profile_t {
+       PROF_UNDEF,
+       PROF_V2_PUB,
+       PROF_V2_EAP,
+       PROF_V2_PUB_EAP,
+       PROF_V1_PUB,
+       PROF_V1_PUB_AM,
+       PROF_V1_XAUTH,
+       PROF_V1_XAUTH_AM,
+       PROF_V1_XAUTH_PSK,
+       PROF_V1_XAUTH_PSK_AM,
+       PROF_V1_HYBRID,
+       PROF_V1_HYBRID_AM,
+};
+
+ENUM(profile_names, PROF_V2_PUB, PROF_V1_HYBRID_AM,
+       "ikev2-pub",
+       "ikev2-eap",
+       "ikev2-pub-eap",
+       "ikev1-pub",
+       "ikev1-pub-am",
+       "ikev1-xauth",
+       "ikev1-xauth-am",
+       "ikev1-xauth-psk",
+       "ikev1-xauth-psk-am",
+       "ikev1-hybrid",
+       "ikev1-hybrid-am",
+);
+
+/**
  * Private data of an cmd_connection_t object.
  */
 struct private_cmd_connection_t {
@@ -40,22 +77,62 @@ struct private_cmd_connection_t {
        pid_t pid;
 
        /**
+        * List of local traffic selectors
+        */
+       linked_list_t *local_ts;
+
+       /**
+        * List of remote traffic selectors
+        */
+       linked_list_t *remote_ts;
+
+       /**
+        * List of IKE proposals
+        */
+       linked_list_t *ike_proposals;
+
+       /**
+        * List of CHILD proposals
+        */
+       linked_list_t *child_proposals;
+
+       /**
         * Hostname to connect to
         */
        char *host;
 
        /**
+        * Server identity, or NULL to use host
+        */
+       char *server;
+
+       /**
         * Local identity
         */
        char *identity;
+
+       /**
+        * XAuth/EAP identity
+        */
+       char *xautheap;
+
+       /**
+        * Is a private key configured
+        */
+       bool key_seen;
+
+       /**
+        * Selected connection profile
+        */
+       profile_t profile;
 };
 
 /**
  * Shut down application
  */
-static void terminate(private_cmd_connection_t *this)
+static void terminate(pid_t pid)
 {
-       kill(this->pid, SIGUSR1);
+       kill(pid, SIGUSR1);
 }
 
 /**
@@ -65,73 +142,265 @@ static peer_cfg_t* create_peer_cfg(private_cmd_connection_t *this)
 {
        ike_cfg_t *ike_cfg;
        peer_cfg_t *peer_cfg;
-       u_int16_t local_port, remote_port = IKEV2_UDP_PORT;
+       proposal_t *proposal;
+       ike_cfg_create_t ike = {
+               .local = "0.0.0.0",
+               .remote = this->host,
+               .remote_port = IKEV2_UDP_PORT,
+               .fragmentation = FRAGMENTATION_YES,
+       };
+       peer_cfg_create_t peer = {
+               .cert_policy = CERT_SEND_IF_ASKED,
+               .unique = UNIQUE_REPLACE,
+               .keyingtries = 1,
+               .rekey_time = 36000, /* 10h */
+               .jitter_time = 600, /* 10min */
+               .over_time = 600, /* 10min */
+               .dpd = 30,
+       };
 
-       local_port = charon->socket->get_port(charon->socket, FALSE);
-       if (local_port != IKEV2_UDP_PORT)
+       switch (this->profile)
        {
-               remote_port = IKEV2_NATT_PORT;
+               case PROF_UNDEF:
+               case PROF_V2_PUB:
+               case PROF_V2_EAP:
+               case PROF_V2_PUB_EAP:
+                       ike.version = IKEV2;
+                       break;
+               case PROF_V1_PUB_AM:
+               case PROF_V1_XAUTH_AM:
+               case PROF_V1_XAUTH_PSK_AM:
+               case PROF_V1_HYBRID_AM:
+                       peer.aggressive = TRUE;
+                       /* FALL */
+               case PROF_V1_PUB:
+               case PROF_V1_XAUTH:
+               case PROF_V1_XAUTH_PSK:
+               case PROF_V1_HYBRID:
+                       ike.version = IKEV1;
+                       break;
+       }
+
+       ike.local_port = charon->socket->get_port(charon->socket, FALSE);
+       if (ike.local_port != IKEV2_UDP_PORT)
+       {
+               ike.remote_port = IKEV2_NATT_PORT;
+       }
+       ike_cfg = ike_cfg_create(&ike);
+       if (this->ike_proposals->get_count(this->ike_proposals))
+       {
+               while (this->ike_proposals->remove_first(this->ike_proposals,
+                                                                                                (void**)&proposal) == SUCCESS)
+               {
+                       ike_cfg->add_proposal(ike_cfg, proposal);
+               }
        }
-       ike_cfg = ike_cfg_create(IKEV2, TRUE, FALSE, "0.0.0.0", FALSE, local_port,
-                                       this->host, FALSE, remote_port, FRAGMENTATION_NO, 0);
-       ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE));
-       peer_cfg = peer_cfg_create("cmd", ike_cfg,
-                                       CERT_SEND_IF_ASKED, UNIQUE_REPLACE, 1, /* keyingtries */
-                                       36000, 0, /* rekey 10h, reauth none */
-                                       600, 600, /* jitter, over 10min */
-                                       TRUE, FALSE, /* mobike, aggressive */
-                                       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));
+       else
+       {
+               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("cmd", ike_cfg, &peer);
 
        return peer_cfg;
 }
 
 /**
- * Attach authentication configs to peer config
+ * Add a single auth cfg of given class to peer cfg
  */
-static void add_auth_cfgs(private_cmd_connection_t *this, peer_cfg_t *peer_cfg)
+static void add_auth_cfg(private_cmd_connection_t *this, peer_cfg_t *peer_cfg,
+                                                bool local, auth_class_t class)
 {
+       identification_t *id;
        auth_cfg_t *auth;
 
        auth = auth_cfg_create();
-       auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
-       auth->add(auth, AUTH_RULE_IDENTITY,
-                         identification_create_from_string(this->identity));
-       peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
+       auth->add(auth, AUTH_RULE_AUTH_CLASS, class);
+       if (local)
+       {
+               id = identification_create_from_string(this->identity);
+               if (this->xautheap)
+               {
+                       switch (class)
+                       {
+                               case AUTH_CLASS_EAP:
+                                       auth->add(auth, AUTH_RULE_EAP_IDENTITY,
+                                                       identification_create_from_string(this->xautheap));
+                                       break;
+                               case AUTH_CLASS_XAUTH:
+                                       auth->add(auth, AUTH_RULE_XAUTH_IDENTITY,
+                                                       identification_create_from_string(this->xautheap));
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+       }
+       else
+       {
+               if (this->server)
+               {
+                       id = identification_create_from_string(this->server);
+               }
+               else
+               {
+                       id = identification_create_from_string(this->host);
+               }
+               auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE);
+       }
+       auth->add(auth, AUTH_RULE_IDENTITY, id);
+       peer_cfg->add_auth_cfg(peer_cfg, auth, local);
+}
 
-       auth = auth_cfg_create();
+/**
+ * Attach authentication configs to peer config
+ */
+static bool add_auth_cfgs(private_cmd_connection_t *this, peer_cfg_t *peer_cfg)
+{
+       if (this->profile == PROF_UNDEF)
+       {
+               if (this->key_seen)
+               {
+                       this->profile = PROF_V2_PUB;
+               }
+               else
+               {
+                       this->profile = PROF_V2_EAP;
+               }
+       }
+       switch (this->profile)
+       {
+               case PROF_V2_PUB:
+               case PROF_V2_PUB_EAP:
+               case PROF_V1_PUB:
+               case PROF_V1_XAUTH:
+               case PROF_V1_PUB_AM:
+               case PROF_V1_XAUTH_AM:
+                       if (!this->key_seen)
+                       {
+                               DBG1(DBG_CFG, "missing private key for profile %N",
+                                        profile_names, this->profile);
+                               return FALSE;
+                       }
+                       break;
+               default:
+                       break;
+       }
 
-       auth->add(auth, AUTH_RULE_IDENTITY,
-                         identification_create_from_string(this->host));
-       peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE);
+       switch (this->profile)
+       {
+               case PROF_V2_PUB:
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
+                       add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
+                       break;
+               case PROF_V2_EAP:
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_EAP);
+                       add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
+                       break;
+               case PROF_V2_PUB_EAP:
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_EAP);
+                       add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
+                       break;
+               case PROF_V1_PUB:
+               case PROF_V1_PUB_AM:
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
+                       add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
+                       break;
+               case PROF_V1_XAUTH:
+               case PROF_V1_XAUTH_AM:
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
+                       add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
+                       break;
+               case PROF_V1_XAUTH_PSK:
+               case PROF_V1_XAUTH_PSK_AM:
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PSK);
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
+                       add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PSK);
+                       break;
+               case PROF_V1_HYBRID:
+               case PROF_V1_HYBRID_AM:
+                       add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
+                       add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
+                       break;
+               default:
+                       return FALSE;
+       }
+       return TRUE;
 }
 
 /**
  * Attach child config to peer config
  */
-static child_cfg_t* create_child_cfg(private_cmd_connection_t *this)
+static child_cfg_t* create_child_cfg(private_cmd_connection_t *this,
+                                                                        peer_cfg_t *peer_cfg)
 {
        child_cfg_t *child_cfg;
        traffic_selector_t *ts;
-       lifetime_cfg_t lifetime = {
-               .time = {
-                       .life = 10800 /* 3h */,
-                       .rekey = 10200 /* 2h50min */,
-                       .jitter = 300 /* 5min */
-               }
+       proposal_t *proposal;
+       bool has_v4 = FALSE, has_v6 = FALSE;
+       child_cfg_create_t child = {
+               .lifetime = {
+                       .time = {
+                               .life = 10800 /* 3h */,
+                               .rekey = 10200 /* 2h50min */,
+                               .jitter = 300 /* 5min */
+                       }
+               },
+               .mode = MODE_TUNNEL,
        };
 
-       child_cfg = child_cfg_create("cmd", &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_default(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,
+       child_cfg = child_cfg_create("cmd", &child);
+       if (this->child_proposals->get_count(this->child_proposals))
+       {
+               while (this->child_proposals->remove_first(this->child_proposals,
+                                                                                               (void**)&proposal) == SUCCESS)
+               {
+                       child_cfg->add_proposal(child_cfg, proposal);
+               }
+       }
+       else
+       {
+               child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
+               child_cfg->add_proposal(child_cfg,
+                                                               proposal_create_default_aead(PROTO_ESP));
+       }
+       while (this->local_ts->remove_first(this->local_ts, (void**)&ts) == SUCCESS)
+       {
+               child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
+       }
+       if (this->remote_ts->get_count(this->remote_ts) == 0)
+       {
+               /* add a 0.0.0.0/0 TS for remote side if none given */
+               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);
+               this->remote_ts->insert_last(this->remote_ts, ts);
+               has_v4 = TRUE;
+       }
+       while (this->remote_ts->remove_first(this->remote_ts,
+                                                                                (void**)&ts) == SUCCESS)
+       {
+               switch (ts->get_type(ts))
+               {
+                       case TS_IPV4_ADDR_RANGE:
+                               has_v4 = TRUE;
+                               break;
+                       case TS_IPV6_ADDR_RANGE:
+                               has_v6 = TRUE;
+                               break;
+               }
+               child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
+       }
+       if (has_v4)
+       {
+               peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0));
+       }
+       if (has_v6)
+       {
+               peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("::", 0));
+       }
+       peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg));
 
        return child_cfg;
 }
@@ -143,46 +412,130 @@ static job_requeue_t initiate(private_cmd_connection_t *this)
 {
        peer_cfg_t *peer_cfg;
        child_cfg_t *child_cfg;
+       pid_t pid = this->pid;
 
        if (!this->host)
        {
                DBG1(DBG_CFG, "unable to initiate, missing --host option");
-               terminate(this);
+               terminate(pid);
                return JOB_REQUEUE_NONE;
        }
        if (!this->identity)
        {
                DBG1(DBG_CFG, "unable to initiate, missing --identity option");
-               terminate(this);
+               terminate(pid);
                return JOB_REQUEUE_NONE;
        }
 
        peer_cfg = create_peer_cfg(this);
 
-       add_auth_cfgs(this, peer_cfg);
+       if (!add_auth_cfgs(this, peer_cfg))
+       {
+               peer_cfg->destroy(peer_cfg);
+               terminate(pid);
+               return JOB_REQUEUE_NONE;
+       }
 
-       child_cfg = create_child_cfg(this);
-       peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg));
+       child_cfg = create_child_cfg(this, peer_cfg);
 
        if (charon->controller->initiate(charon->controller, peer_cfg, child_cfg,
-                                                                        controller_cb_empty, NULL, 0) != SUCCESS)
+                                                               controller_cb_empty, NULL, 0, FALSE) != SUCCESS)
        {
-               terminate(this);
+               terminate(pid);
        }
        return JOB_REQUEUE_NONE;
 }
 
+/**
+ * Create a traffic selector from string, add to list
+ */
+static void add_ts(private_cmd_connection_t *this,
+                                  linked_list_t *list, char *string)
+{
+       traffic_selector_t *ts;
+
+       ts = traffic_selector_create_from_cidr(string, 0, 0, 65535);
+       if (!ts)
+       {
+               DBG1(DBG_CFG, "invalid traffic selector: %s", string);
+               exit(1);
+       }
+       list->insert_last(list, ts);
+}
+
+/**
+ * Parse profile name identifier
+ */
+static void set_profile(private_cmd_connection_t *this, char *name)
+{
+       profile_t profile;
+
+       if (!enum_from_name(profile_names, name, &profile))
+       {
+               DBG1(DBG_CFG, "unknown connection profile: %s", name);
+               exit(1);
+       }
+       this->profile = profile;
+}
+
 METHOD(cmd_connection_t, handle, bool,
        private_cmd_connection_t *this, cmd_option_type_t opt, char *arg)
 {
+       proposal_t *proposal;
+
        switch (opt)
        {
                case CMD_OPT_HOST:
                        this->host = arg;
                        break;
+               case CMD_OPT_REMOTE_IDENTITY:
+                       this->server = arg;
+                       break;
                case CMD_OPT_IDENTITY:
                        this->identity = arg;
                        break;
+               case CMD_OPT_EAP_IDENTITY:
+               case CMD_OPT_XAUTH_USER:
+                       this->xautheap = arg;
+                       break;
+               case CMD_OPT_RSA:
+               case CMD_OPT_AGENT:
+               case CMD_OPT_PKCS12:
+                       this->key_seen = TRUE;
+                       break;
+               case CMD_OPT_LOCAL_TS:
+                       add_ts(this, this->local_ts, arg);
+                       break;
+               case CMD_OPT_REMOTE_TS:
+                       add_ts(this, this->remote_ts, arg);
+                       break;
+               case CMD_OPT_IKE_PROPOSAL:
+                       proposal = proposal_create_from_string(PROTO_IKE, arg);
+                       if (!proposal)
+                       {
+                               exit(1);
+                       }
+                       this->ike_proposals->insert_last(this->ike_proposals, proposal);
+                       break;
+               case CMD_OPT_ESP_PROPOSAL:
+                       proposal = proposal_create_from_string(PROTO_ESP, arg);
+                       if (!proposal)
+                       {
+                               exit(1);
+                       }
+                       this->child_proposals->insert_last(this->child_proposals, proposal);
+                       break;
+               case CMD_OPT_AH_PROPOSAL:
+                       proposal = proposal_create_from_string(PROTO_AH, arg);
+                       if (!proposal)
+                       {
+                               exit(1);
+                       }
+                       this->child_proposals->insert_last(this->child_proposals, proposal);
+                       break;
+               case CMD_OPT_PROFILE:
+                       set_profile(this, arg);
+                       break;
                default:
                        return FALSE;
        }
@@ -192,6 +545,14 @@ METHOD(cmd_connection_t, handle, bool,
 METHOD(cmd_connection_t, destroy, void,
        private_cmd_connection_t *this)
 {
+       this->ike_proposals->destroy_offset(this->ike_proposals,
+                                                               offsetof(proposal_t, destroy));
+       this->child_proposals->destroy_offset(this->child_proposals,
+                                                               offsetof(proposal_t, destroy));
+       this->local_ts->destroy_offset(this->local_ts,
+                                                               offsetof(traffic_selector_t, destroy));
+       this->remote_ts->destroy_offset(this->remote_ts,
+                                                               offsetof(traffic_selector_t, destroy));
        free(this);
 }
 
@@ -208,8 +569,17 @@ cmd_connection_t *cmd_connection_create()
                        .destroy = _destroy,
                },
                .pid = getpid(),
+               .local_ts = linked_list_create(),
+               .remote_ts = linked_list_create(),
+               .ike_proposals = linked_list_create(),
+               .child_proposals = linked_list_create(),
+               .profile = PROF_UNDEF,
        );
 
+       /* always include the virtual IP in traffic selector list */
+       this->local_ts->insert_last(this->local_ts,
+                                                               traffic_selector_create_dynamic(0, 0, 65535));
+
        /* queue job, gets initiated as soon as we are up and running */
        lib->processor->queue_job(lib->processor,
                (job_t*)callback_job_create_with_prio(