Merge branch 'aead-proposal'
[strongswan.git] / src / charon-cmd / cmd / cmd_connection.c
1 /*
2 * Copyright (C) 2013 Tobias Brunner
3 * Hochschule fuer Technik Rapperswil
4 *
5 * Copyright (C) 2013 Martin Willi
6 * Copyright (C) 2013 revosec AG
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 * for more details.
17 */
18
19 #include "cmd_connection.h"
20
21 #include <signal.h>
22 #include <unistd.h>
23
24 #include <utils/debug.h>
25 #include <processing/jobs/callback_job.h>
26 #include <threading/thread.h>
27 #include <daemon.h>
28
29 typedef enum profile_t profile_t;
30 typedef struct private_cmd_connection_t private_cmd_connection_t;
31
32 /**
33 * Connection profiles we support
34 */
35 enum profile_t {
36 PROF_UNDEF,
37 PROF_V2_PUB,
38 PROF_V2_EAP,
39 PROF_V2_PUB_EAP,
40 PROF_V1_PUB,
41 PROF_V1_PUB_AM,
42 PROF_V1_XAUTH,
43 PROF_V1_XAUTH_AM,
44 PROF_V1_XAUTH_PSK,
45 PROF_V1_XAUTH_PSK_AM,
46 PROF_V1_HYBRID,
47 PROF_V1_HYBRID_AM,
48 };
49
50 ENUM(profile_names, PROF_V2_PUB, PROF_V1_HYBRID_AM,
51 "ikev2-pub",
52 "ikev2-eap",
53 "ikev2-pub-eap",
54 "ikev1-pub",
55 "ikev1-pub-am",
56 "ikev1-xauth",
57 "ikev1-xauth-am",
58 "ikev1-xauth-psk",
59 "ikev1-xauth-psk-am",
60 "ikev1-hybrid",
61 "ikev1-hybrid-am",
62 );
63
64 /**
65 * Private data of an cmd_connection_t object.
66 */
67 struct private_cmd_connection_t {
68
69 /**
70 * Public cmd_connection_t interface.
71 */
72 cmd_connection_t public;
73
74 /**
75 * Process ID to terminate on failure
76 */
77 pid_t pid;
78
79 /**
80 * List of local traffic selectors
81 */
82 linked_list_t *local_ts;
83
84 /**
85 * List of remote traffic selectors
86 */
87 linked_list_t *remote_ts;
88
89 /**
90 * List of IKE proposals
91 */
92 linked_list_t *ike_proposals;
93
94 /**
95 * List of CHILD proposals
96 */
97 linked_list_t *child_proposals;
98
99 /**
100 * Hostname to connect to
101 */
102 char *host;
103
104 /**
105 * Server identity, or NULL to use host
106 */
107 char *server;
108
109 /**
110 * Local identity
111 */
112 char *identity;
113
114 /**
115 * XAuth/EAP identity
116 */
117 char *xautheap;
118
119 /**
120 * Is a private key configured
121 */
122 bool key_seen;
123
124 /**
125 * Selected connection profile
126 */
127 profile_t profile;
128 };
129
130 /**
131 * Shut down application
132 */
133 static void terminate(pid_t pid)
134 {
135 kill(pid, SIGUSR1);
136 }
137
138 /**
139 * Create peer config with associated ike config
140 */
141 static peer_cfg_t* create_peer_cfg(private_cmd_connection_t *this)
142 {
143 ike_cfg_t *ike_cfg;
144 peer_cfg_t *peer_cfg;
145 u_int16_t local_port, remote_port = IKEV2_UDP_PORT;
146 ike_version_t version = IKE_ANY;
147 bool aggressive = FALSE;
148 proposal_t *proposal;
149
150 switch (this->profile)
151 {
152 case PROF_UNDEF:
153 case PROF_V2_PUB:
154 case PROF_V2_EAP:
155 case PROF_V2_PUB_EAP:
156 version = IKEV2;
157 break;
158 case PROF_V1_PUB_AM:
159 case PROF_V1_XAUTH_AM:
160 case PROF_V1_XAUTH_PSK_AM:
161 case PROF_V1_HYBRID_AM:
162 aggressive = TRUE;
163 /* FALL */
164 case PROF_V1_PUB:
165 case PROF_V1_XAUTH:
166 case PROF_V1_XAUTH_PSK:
167 case PROF_V1_HYBRID:
168 version = IKEV1;
169 break;
170 }
171
172 local_port = charon->socket->get_port(charon->socket, FALSE);
173 if (local_port != IKEV2_UDP_PORT)
174 {
175 remote_port = IKEV2_NATT_PORT;
176 }
177 ike_cfg = ike_cfg_create(version, TRUE, FALSE, "0.0.0.0", local_port,
178 this->host, remote_port, FRAGMENTATION_NO, 0);
179 if (this->ike_proposals->get_count(this->ike_proposals))
180 {
181 while (this->ike_proposals->remove_first(this->ike_proposals,
182 (void**)&proposal) == SUCCESS)
183 {
184 ike_cfg->add_proposal(ike_cfg, proposal);
185 }
186 }
187 else
188 {
189 ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE));
190 ike_cfg->add_proposal(ike_cfg, proposal_create_default_aead(PROTO_IKE));
191 }
192 peer_cfg = peer_cfg_create("cmd", ike_cfg,
193 CERT_SEND_IF_ASKED, UNIQUE_REPLACE, 1, /* keyingtries */
194 36000, 0, /* rekey 10h, reauth none */
195 600, 600, /* jitter, over 10min */
196 TRUE, aggressive, TRUE, /* mobike, aggressive, pull */
197 30, 0, /* DPD delay, timeout */
198 FALSE, NULL, NULL); /* mediation */
199
200 return peer_cfg;
201 }
202
203 /**
204 * Add a single auth cfg of given class to peer cfg
205 */
206 static void add_auth_cfg(private_cmd_connection_t *this, peer_cfg_t *peer_cfg,
207 bool local, auth_class_t class)
208 {
209 identification_t *id;
210 auth_cfg_t *auth;
211
212 auth = auth_cfg_create();
213 auth->add(auth, AUTH_RULE_AUTH_CLASS, class);
214 if (local)
215 {
216 id = identification_create_from_string(this->identity);
217 if (this->xautheap)
218 {
219 switch (class)
220 {
221 case AUTH_CLASS_EAP:
222 auth->add(auth, AUTH_RULE_EAP_IDENTITY,
223 identification_create_from_string(this->xautheap));
224 break;
225 case AUTH_CLASS_XAUTH:
226 auth->add(auth, AUTH_RULE_XAUTH_IDENTITY,
227 identification_create_from_string(this->xautheap));
228 break;
229 default:
230 break;
231 }
232 }
233 }
234 else
235 {
236 if (this->server)
237 {
238 id = identification_create_from_string(this->server);
239 }
240 else
241 {
242 id = identification_create_from_string(this->host);
243 }
244 auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE);
245 }
246 auth->add(auth, AUTH_RULE_IDENTITY, id);
247 peer_cfg->add_auth_cfg(peer_cfg, auth, local);
248 }
249
250 /**
251 * Attach authentication configs to peer config
252 */
253 static bool add_auth_cfgs(private_cmd_connection_t *this, peer_cfg_t *peer_cfg)
254 {
255 if (this->profile == PROF_UNDEF)
256 {
257 if (this->key_seen)
258 {
259 this->profile = PROF_V2_PUB;
260 }
261 else
262 {
263 this->profile = PROF_V2_EAP;
264 }
265 }
266 switch (this->profile)
267 {
268 case PROF_V2_PUB:
269 case PROF_V2_PUB_EAP:
270 case PROF_V1_PUB:
271 case PROF_V1_XAUTH:
272 case PROF_V1_PUB_AM:
273 case PROF_V1_XAUTH_AM:
274 if (!this->key_seen)
275 {
276 DBG1(DBG_CFG, "missing private key for profile %N",
277 profile_names, this->profile);
278 return FALSE;
279 }
280 break;
281 default:
282 break;
283 }
284
285 switch (this->profile)
286 {
287 case PROF_V2_PUB:
288 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
289 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
290 break;
291 case PROF_V2_EAP:
292 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_EAP);
293 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
294 break;
295 case PROF_V2_PUB_EAP:
296 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
297 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_EAP);
298 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
299 break;
300 case PROF_V1_PUB:
301 case PROF_V1_PUB_AM:
302 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
303 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
304 break;
305 case PROF_V1_XAUTH:
306 case PROF_V1_XAUTH_AM:
307 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
308 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
309 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
310 break;
311 case PROF_V1_XAUTH_PSK:
312 case PROF_V1_XAUTH_PSK_AM:
313 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PSK);
314 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
315 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PSK);
316 break;
317 case PROF_V1_HYBRID:
318 case PROF_V1_HYBRID_AM:
319 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
320 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
321 break;
322 default:
323 return FALSE;
324 }
325 return TRUE;
326 }
327
328 /**
329 * Attach child config to peer config
330 */
331 static child_cfg_t* create_child_cfg(private_cmd_connection_t *this,
332 peer_cfg_t *peer_cfg)
333 {
334 child_cfg_t *child_cfg;
335 traffic_selector_t *ts;
336 proposal_t *proposal;
337 bool has_v4 = FALSE, has_v6 = FALSE;
338 lifetime_cfg_t lifetime = {
339 .time = {
340 .life = 10800 /* 3h */,
341 .rekey = 10200 /* 2h50min */,
342 .jitter = 300 /* 5min */
343 }
344 };
345
346 child_cfg = child_cfg_create("cmd", &lifetime,
347 NULL, FALSE, MODE_TUNNEL, /* updown, hostaccess */
348 ACTION_NONE, ACTION_NONE, ACTION_NONE, FALSE,
349 0, 0, NULL, NULL, 0);
350 if (this->child_proposals->get_count(this->child_proposals))
351 {
352 while (this->child_proposals->remove_first(this->child_proposals,
353 (void**)&proposal) == SUCCESS)
354 {
355 child_cfg->add_proposal(child_cfg, proposal);
356 }
357 }
358 else
359 {
360 child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
361 child_cfg->add_proposal(child_cfg,
362 proposal_create_default_aead(PROTO_ESP));
363 }
364 while (this->local_ts->remove_first(this->local_ts, (void**)&ts) == SUCCESS)
365 {
366 child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
367 }
368 if (this->remote_ts->get_count(this->remote_ts) == 0)
369 {
370 /* add a 0.0.0.0/0 TS for remote side if none given */
371 ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE,
372 "0.0.0.0", 0, "255.255.255.255", 65535);
373 this->remote_ts->insert_last(this->remote_ts, ts);
374 has_v4 = TRUE;
375 }
376 while (this->remote_ts->remove_first(this->remote_ts,
377 (void**)&ts) == SUCCESS)
378 {
379 switch (ts->get_type(ts))
380 {
381 case TS_IPV4_ADDR_RANGE:
382 has_v4 = TRUE;
383 break;
384 case TS_IPV6_ADDR_RANGE:
385 has_v6 = TRUE;
386 break;
387 }
388 child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
389 }
390 if (has_v4)
391 {
392 peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0));
393 }
394 if (has_v6)
395 {
396 peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("::", 0));
397 }
398 peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg));
399
400 return child_cfg;
401 }
402
403 /**
404 * Initiate the configured connection
405 */
406 static job_requeue_t initiate(private_cmd_connection_t *this)
407 {
408 peer_cfg_t *peer_cfg;
409 child_cfg_t *child_cfg;
410 pid_t pid = this->pid;
411
412 if (!this->host)
413 {
414 DBG1(DBG_CFG, "unable to initiate, missing --host option");
415 terminate(pid);
416 return JOB_REQUEUE_NONE;
417 }
418 if (!this->identity)
419 {
420 DBG1(DBG_CFG, "unable to initiate, missing --identity option");
421 terminate(pid);
422 return JOB_REQUEUE_NONE;
423 }
424
425 peer_cfg = create_peer_cfg(this);
426
427 if (!add_auth_cfgs(this, peer_cfg))
428 {
429 peer_cfg->destroy(peer_cfg);
430 terminate(pid);
431 return JOB_REQUEUE_NONE;
432 }
433
434 child_cfg = create_child_cfg(this, peer_cfg);
435
436 if (charon->controller->initiate(charon->controller, peer_cfg, child_cfg,
437 controller_cb_empty, NULL, 0) != SUCCESS)
438 {
439 terminate(pid);
440 }
441 return JOB_REQUEUE_NONE;
442 }
443
444 /**
445 * Create a traffic selector from string, add to list
446 */
447 static void add_ts(private_cmd_connection_t *this,
448 linked_list_t *list, char *string)
449 {
450 traffic_selector_t *ts;
451
452 ts = traffic_selector_create_from_cidr(string, 0, 0, 65535);
453 if (!ts)
454 {
455 DBG1(DBG_CFG, "invalid traffic selector: %s", string);
456 exit(1);
457 }
458 list->insert_last(list, ts);
459 }
460
461 /**
462 * Parse profile name identifier
463 */
464 static void set_profile(private_cmd_connection_t *this, char *name)
465 {
466 profile_t profile;
467
468 if (!enum_from_name(profile_names, name, &profile))
469 {
470 DBG1(DBG_CFG, "unknown connection profile: %s", name);
471 exit(1);
472 }
473 this->profile = profile;
474 }
475
476 METHOD(cmd_connection_t, handle, bool,
477 private_cmd_connection_t *this, cmd_option_type_t opt, char *arg)
478 {
479 proposal_t *proposal;
480
481 switch (opt)
482 {
483 case CMD_OPT_HOST:
484 this->host = arg;
485 break;
486 case CMD_OPT_REMOTE_IDENTITY:
487 this->server = arg;
488 break;
489 case CMD_OPT_IDENTITY:
490 this->identity = arg;
491 break;
492 case CMD_OPT_EAP_IDENTITY:
493 case CMD_OPT_XAUTH_USER:
494 this->xautheap = arg;
495 break;
496 case CMD_OPT_RSA:
497 case CMD_OPT_AGENT:
498 case CMD_OPT_PKCS12:
499 this->key_seen = TRUE;
500 break;
501 case CMD_OPT_LOCAL_TS:
502 add_ts(this, this->local_ts, arg);
503 break;
504 case CMD_OPT_REMOTE_TS:
505 add_ts(this, this->remote_ts, arg);
506 break;
507 case CMD_OPT_IKE_PROPOSAL:
508 proposal = proposal_create_from_string(PROTO_IKE, arg);
509 if (!proposal)
510 {
511 exit(1);
512 }
513 this->ike_proposals->insert_last(this->ike_proposals, proposal);
514 break;
515 case CMD_OPT_ESP_PROPOSAL:
516 proposal = proposal_create_from_string(PROTO_ESP, arg);
517 if (!proposal)
518 {
519 exit(1);
520 }
521 this->child_proposals->insert_last(this->child_proposals, proposal);
522 break;
523 case CMD_OPT_AH_PROPOSAL:
524 proposal = proposal_create_from_string(PROTO_AH, arg);
525 if (!proposal)
526 {
527 exit(1);
528 }
529 this->child_proposals->insert_last(this->child_proposals, proposal);
530 break;
531 case CMD_OPT_PROFILE:
532 set_profile(this, arg);
533 break;
534 default:
535 return FALSE;
536 }
537 return TRUE;
538 }
539
540 METHOD(cmd_connection_t, destroy, void,
541 private_cmd_connection_t *this)
542 {
543 this->ike_proposals->destroy_offset(this->ike_proposals,
544 offsetof(proposal_t, destroy));
545 this->child_proposals->destroy_offset(this->child_proposals,
546 offsetof(proposal_t, destroy));
547 this->local_ts->destroy_offset(this->local_ts,
548 offsetof(traffic_selector_t, destroy));
549 this->remote_ts->destroy_offset(this->remote_ts,
550 offsetof(traffic_selector_t, destroy));
551 free(this);
552 }
553
554 /**
555 * See header
556 */
557 cmd_connection_t *cmd_connection_create()
558 {
559 private_cmd_connection_t *this;
560
561 INIT(this,
562 .public = {
563 .handle = _handle,
564 .destroy = _destroy,
565 },
566 .pid = getpid(),
567 .local_ts = linked_list_create(),
568 .remote_ts = linked_list_create(),
569 .ike_proposals = linked_list_create(),
570 .child_proposals = linked_list_create(),
571 .profile = PROF_UNDEF,
572 );
573
574 /* always include the virtual IP in traffic selector list */
575 this->local_ts->insert_last(this->local_ts,
576 traffic_selector_create_dynamic(0, 0, 65535));
577
578 /* queue job, gets initiated as soon as we are up and running */
579 lib->processor->queue_job(lib->processor,
580 (job_t*)callback_job_create_with_prio(
581 (callback_job_cb_t)initiate, this, NULL,
582 (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
583
584 return &this->public;
585 }