Merge branch 'cmd-proposals'
[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 }
191 peer_cfg = peer_cfg_create("cmd", ike_cfg,
192 CERT_SEND_IF_ASKED, UNIQUE_REPLACE, 1, /* keyingtries */
193 36000, 0, /* rekey 10h, reauth none */
194 600, 600, /* jitter, over 10min */
195 TRUE, aggressive, TRUE, /* mobike, aggressive, pull */
196 30, 0, /* DPD delay, timeout */
197 FALSE, NULL, NULL); /* mediation */
198
199 return peer_cfg;
200 }
201
202 /**
203 * Add a single auth cfg of given class to peer cfg
204 */
205 static void add_auth_cfg(private_cmd_connection_t *this, peer_cfg_t *peer_cfg,
206 bool local, auth_class_t class)
207 {
208 identification_t *id;
209 auth_cfg_t *auth;
210
211 auth = auth_cfg_create();
212 auth->add(auth, AUTH_RULE_AUTH_CLASS, class);
213 if (local)
214 {
215 id = identification_create_from_string(this->identity);
216 if (this->xautheap)
217 {
218 switch (class)
219 {
220 case AUTH_CLASS_EAP:
221 auth->add(auth, AUTH_RULE_EAP_IDENTITY,
222 identification_create_from_string(this->xautheap));
223 break;
224 case AUTH_CLASS_XAUTH:
225 auth->add(auth, AUTH_RULE_XAUTH_IDENTITY,
226 identification_create_from_string(this->xautheap));
227 break;
228 default:
229 break;
230 }
231 }
232 }
233 else
234 {
235 if (this->server)
236 {
237 id = identification_create_from_string(this->server);
238 }
239 else
240 {
241 id = identification_create_from_string(this->host);
242 }
243 auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE);
244 }
245 auth->add(auth, AUTH_RULE_IDENTITY, id);
246 peer_cfg->add_auth_cfg(peer_cfg, auth, local);
247 }
248
249 /**
250 * Attach authentication configs to peer config
251 */
252 static bool add_auth_cfgs(private_cmd_connection_t *this, peer_cfg_t *peer_cfg)
253 {
254 if (this->profile == PROF_UNDEF)
255 {
256 if (this->key_seen)
257 {
258 this->profile = PROF_V2_PUB;
259 }
260 else
261 {
262 this->profile = PROF_V2_EAP;
263 }
264 }
265 switch (this->profile)
266 {
267 case PROF_V2_PUB:
268 case PROF_V2_PUB_EAP:
269 case PROF_V1_PUB:
270 case PROF_V1_XAUTH:
271 case PROF_V1_PUB_AM:
272 case PROF_V1_XAUTH_AM:
273 if (!this->key_seen)
274 {
275 DBG1(DBG_CFG, "missing private key for profile %N",
276 profile_names, this->profile);
277 return FALSE;
278 }
279 break;
280 default:
281 break;
282 }
283
284 switch (this->profile)
285 {
286 case PROF_V2_PUB:
287 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
288 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
289 break;
290 case PROF_V2_EAP:
291 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_EAP);
292 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
293 break;
294 case PROF_V2_PUB_EAP:
295 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
296 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_EAP);
297 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
298 break;
299 case PROF_V1_PUB:
300 case PROF_V1_PUB_AM:
301 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
302 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
303 break;
304 case PROF_V1_XAUTH:
305 case PROF_V1_XAUTH_AM:
306 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
307 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
308 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
309 break;
310 case PROF_V1_XAUTH_PSK:
311 case PROF_V1_XAUTH_PSK_AM:
312 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PSK);
313 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
314 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PSK);
315 break;
316 case PROF_V1_HYBRID:
317 case PROF_V1_HYBRID_AM:
318 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
319 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
320 break;
321 default:
322 return FALSE;
323 }
324 return TRUE;
325 }
326
327 /**
328 * Attach child config to peer config
329 */
330 static child_cfg_t* create_child_cfg(private_cmd_connection_t *this,
331 peer_cfg_t *peer_cfg)
332 {
333 child_cfg_t *child_cfg;
334 traffic_selector_t *ts;
335 proposal_t *proposal;
336 bool has_v4 = FALSE, has_v6 = FALSE;
337 lifetime_cfg_t lifetime = {
338 .time = {
339 .life = 10800 /* 3h */,
340 .rekey = 10200 /* 2h50min */,
341 .jitter = 300 /* 5min */
342 }
343 };
344
345 child_cfg = child_cfg_create("cmd", &lifetime,
346 NULL, FALSE, MODE_TUNNEL, /* updown, hostaccess */
347 ACTION_NONE, ACTION_NONE, ACTION_NONE, FALSE,
348 0, 0, NULL, NULL, 0);
349 if (this->child_proposals->get_count(this->child_proposals))
350 {
351 while (this->child_proposals->remove_first(this->child_proposals,
352 (void**)&proposal) == SUCCESS)
353 {
354 child_cfg->add_proposal(child_cfg, proposal);
355 }
356 }
357 else
358 {
359 child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
360 }
361 while (this->local_ts->remove_first(this->local_ts, (void**)&ts) == SUCCESS)
362 {
363 child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
364 }
365 if (this->remote_ts->get_count(this->remote_ts) == 0)
366 {
367 /* add a 0.0.0.0/0 TS for remote side if none given */
368 ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE,
369 "0.0.0.0", 0, "255.255.255.255", 65535);
370 this->remote_ts->insert_last(this->remote_ts, ts);
371 has_v4 = TRUE;
372 }
373 while (this->remote_ts->remove_first(this->remote_ts,
374 (void**)&ts) == SUCCESS)
375 {
376 switch (ts->get_type(ts))
377 {
378 case TS_IPV4_ADDR_RANGE:
379 has_v4 = TRUE;
380 break;
381 case TS_IPV6_ADDR_RANGE:
382 has_v6 = TRUE;
383 break;
384 }
385 child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
386 }
387 if (has_v4)
388 {
389 peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0));
390 }
391 if (has_v6)
392 {
393 peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("::", 0));
394 }
395 peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg));
396
397 return child_cfg;
398 }
399
400 /**
401 * Initiate the configured connection
402 */
403 static job_requeue_t initiate(private_cmd_connection_t *this)
404 {
405 peer_cfg_t *peer_cfg;
406 child_cfg_t *child_cfg;
407 pid_t pid = this->pid;
408
409 if (!this->host)
410 {
411 DBG1(DBG_CFG, "unable to initiate, missing --host option");
412 terminate(pid);
413 return JOB_REQUEUE_NONE;
414 }
415 if (!this->identity)
416 {
417 DBG1(DBG_CFG, "unable to initiate, missing --identity option");
418 terminate(pid);
419 return JOB_REQUEUE_NONE;
420 }
421
422 peer_cfg = create_peer_cfg(this);
423
424 if (!add_auth_cfgs(this, peer_cfg))
425 {
426 peer_cfg->destroy(peer_cfg);
427 terminate(pid);
428 return JOB_REQUEUE_NONE;
429 }
430
431 child_cfg = create_child_cfg(this, peer_cfg);
432
433 if (charon->controller->initiate(charon->controller, peer_cfg, child_cfg,
434 controller_cb_empty, NULL, 0) != SUCCESS)
435 {
436 terminate(pid);
437 }
438 return JOB_REQUEUE_NONE;
439 }
440
441 /**
442 * Create a traffic selector from string, add to list
443 */
444 static void add_ts(private_cmd_connection_t *this,
445 linked_list_t *list, char *string)
446 {
447 traffic_selector_t *ts;
448
449 ts = traffic_selector_create_from_cidr(string, 0, 0, 65535);
450 if (!ts)
451 {
452 DBG1(DBG_CFG, "invalid traffic selector: %s", string);
453 exit(1);
454 }
455 list->insert_last(list, ts);
456 }
457
458 /**
459 * Parse profile name identifier
460 */
461 static void set_profile(private_cmd_connection_t *this, char *name)
462 {
463 int profile;
464
465 profile = enum_from_name(profile_names, name);
466 if (profile == -1)
467 {
468 DBG1(DBG_CFG, "unknown connection profile: %s", name);
469 exit(1);
470 }
471 this->profile = profile;
472 }
473
474 METHOD(cmd_connection_t, handle, bool,
475 private_cmd_connection_t *this, cmd_option_type_t opt, char *arg)
476 {
477 proposal_t *proposal;
478
479 switch (opt)
480 {
481 case CMD_OPT_HOST:
482 this->host = arg;
483 break;
484 case CMD_OPT_REMOTE_IDENTITY:
485 this->server = arg;
486 break;
487 case CMD_OPT_IDENTITY:
488 this->identity = arg;
489 break;
490 case CMD_OPT_EAP_IDENTITY:
491 case CMD_OPT_XAUTH_USER:
492 this->xautheap = arg;
493 break;
494 case CMD_OPT_RSA:
495 case CMD_OPT_AGENT:
496 case CMD_OPT_PKCS12:
497 this->key_seen = TRUE;
498 break;
499 case CMD_OPT_LOCAL_TS:
500 add_ts(this, this->local_ts, arg);
501 break;
502 case CMD_OPT_REMOTE_TS:
503 add_ts(this, this->remote_ts, arg);
504 break;
505 case CMD_OPT_IKE_PROPOSAL:
506 proposal = proposal_create_from_string(PROTO_IKE, arg);
507 if (!proposal)
508 {
509 exit(1);
510 }
511 this->ike_proposals->insert_last(this->ike_proposals, proposal);
512 break;
513 case CMD_OPT_ESP_PROPOSAL:
514 proposal = proposal_create_from_string(PROTO_ESP, arg);
515 if (!proposal)
516 {
517 exit(1);
518 }
519 this->child_proposals->insert_last(this->child_proposals, proposal);
520 break;
521 case CMD_OPT_AH_PROPOSAL:
522 proposal = proposal_create_from_string(PROTO_AH, arg);
523 if (!proposal)
524 {
525 exit(1);
526 }
527 this->child_proposals->insert_last(this->child_proposals, proposal);
528 break;
529 case CMD_OPT_PROFILE:
530 set_profile(this, arg);
531 break;
532 default:
533 return FALSE;
534 }
535 return TRUE;
536 }
537
538 METHOD(cmd_connection_t, destroy, void,
539 private_cmd_connection_t *this)
540 {
541 this->ike_proposals->destroy_offset(this->ike_proposals,
542 offsetof(proposal_t, destroy));
543 this->child_proposals->destroy_offset(this->child_proposals,
544 offsetof(proposal_t, destroy));
545 this->local_ts->destroy_offset(this->local_ts,
546 offsetof(traffic_selector_t, destroy));
547 this->remote_ts->destroy_offset(this->remote_ts,
548 offsetof(traffic_selector_t, destroy));
549 free(this);
550 }
551
552 /**
553 * See header
554 */
555 cmd_connection_t *cmd_connection_create()
556 {
557 private_cmd_connection_t *this;
558
559 INIT(this,
560 .public = {
561 .handle = _handle,
562 .destroy = _destroy,
563 },
564 .pid = getpid(),
565 .local_ts = linked_list_create(),
566 .remote_ts = linked_list_create(),
567 .ike_proposals = linked_list_create(),
568 .child_proposals = linked_list_create(),
569 .profile = PROF_UNDEF,
570 );
571
572 /* always include the virtual IP in traffic selector list */
573 this->local_ts->insert_last(this->local_ts,
574 traffic_selector_create_dynamic(0, 0, 65535));
575
576 /* queue job, gets initiated as soon as we are up and running */
577 lib->processor->queue_job(lib->processor,
578 (job_t*)callback_job_create_with_prio(
579 (callback_job_cb_t)initiate, this, NULL,
580 (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
581
582 return &this->public;
583 }