charon-cmd: Add --esp/--ah-proposal options to specify CHILD_SA 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 peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0));
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 {
333 child_cfg_t *child_cfg;
334 traffic_selector_t *ts;
335 proposal_t *proposal;
336 lifetime_cfg_t lifetime = {
337 .time = {
338 .life = 10800 /* 3h */,
339 .rekey = 10200 /* 2h50min */,
340 .jitter = 300 /* 5min */
341 }
342 };
343
344 child_cfg = child_cfg_create("cmd", &lifetime,
345 NULL, FALSE, MODE_TUNNEL, /* updown, hostaccess */
346 ACTION_NONE, ACTION_NONE, ACTION_NONE, FALSE,
347 0, 0, NULL, NULL, 0);
348 if (this->child_proposals->get_count(this->child_proposals))
349 {
350 while (this->child_proposals->remove_first(this->child_proposals,
351 (void**)&proposal) == SUCCESS)
352 {
353 child_cfg->add_proposal(child_cfg, proposal);
354 }
355 }
356 else
357 {
358 child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
359 }
360 while (this->local_ts->remove_first(this->local_ts, (void**)&ts) == SUCCESS)
361 {
362 child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
363 }
364 if (this->remote_ts->get_count(this->remote_ts) == 0)
365 {
366 /* add a 0.0.0.0/0 TS for remote side if none given */
367 ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE,
368 "0.0.0.0", 0, "255.255.255.255", 65535);
369 this->remote_ts->insert_last(this->remote_ts, ts);
370 }
371 while (this->remote_ts->remove_first(this->remote_ts,
372 (void**)&ts) == SUCCESS)
373 {
374 child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
375 }
376
377 return child_cfg;
378 }
379
380 /**
381 * Initiate the configured connection
382 */
383 static job_requeue_t initiate(private_cmd_connection_t *this)
384 {
385 peer_cfg_t *peer_cfg;
386 child_cfg_t *child_cfg;
387 pid_t pid = this->pid;
388
389 if (!this->host)
390 {
391 DBG1(DBG_CFG, "unable to initiate, missing --host option");
392 terminate(pid);
393 return JOB_REQUEUE_NONE;
394 }
395 if (!this->identity)
396 {
397 DBG1(DBG_CFG, "unable to initiate, missing --identity option");
398 terminate(pid);
399 return JOB_REQUEUE_NONE;
400 }
401
402 peer_cfg = create_peer_cfg(this);
403
404 if (!add_auth_cfgs(this, peer_cfg))
405 {
406 peer_cfg->destroy(peer_cfg);
407 terminate(pid);
408 return JOB_REQUEUE_NONE;
409 }
410
411 child_cfg = create_child_cfg(this);
412 peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg));
413
414 if (charon->controller->initiate(charon->controller, peer_cfg, child_cfg,
415 controller_cb_empty, NULL, 0) != SUCCESS)
416 {
417 terminate(pid);
418 }
419 return JOB_REQUEUE_NONE;
420 }
421
422 /**
423 * Create a traffic selector from string, add to list
424 */
425 static void add_ts(private_cmd_connection_t *this,
426 linked_list_t *list, char *string)
427 {
428 traffic_selector_t *ts;
429
430 ts = traffic_selector_create_from_cidr(string, 0, 0, 65535);
431 if (!ts)
432 {
433 DBG1(DBG_CFG, "invalid traffic selector: %s", string);
434 exit(1);
435 }
436 list->insert_last(list, ts);
437 }
438
439 /**
440 * Parse profile name identifier
441 */
442 static void set_profile(private_cmd_connection_t *this, char *name)
443 {
444 int profile;
445
446 profile = enum_from_name(profile_names, name);
447 if (profile == -1)
448 {
449 DBG1(DBG_CFG, "unknown connection profile: %s", name);
450 exit(1);
451 }
452 this->profile = profile;
453 }
454
455 METHOD(cmd_connection_t, handle, bool,
456 private_cmd_connection_t *this, cmd_option_type_t opt, char *arg)
457 {
458 proposal_t *proposal;
459
460 switch (opt)
461 {
462 case CMD_OPT_HOST:
463 this->host = arg;
464 break;
465 case CMD_OPT_REMOTE_IDENTITY:
466 this->server = arg;
467 break;
468 case CMD_OPT_IDENTITY:
469 this->identity = arg;
470 break;
471 case CMD_OPT_EAP_IDENTITY:
472 case CMD_OPT_XAUTH_USER:
473 this->xautheap = arg;
474 break;
475 case CMD_OPT_RSA:
476 case CMD_OPT_AGENT:
477 case CMD_OPT_PKCS12:
478 this->key_seen = TRUE;
479 break;
480 case CMD_OPT_LOCAL_TS:
481 add_ts(this, this->local_ts, arg);
482 break;
483 case CMD_OPT_REMOTE_TS:
484 add_ts(this, this->remote_ts, arg);
485 break;
486 case CMD_OPT_IKE_PROPOSAL:
487 proposal = proposal_create_from_string(PROTO_IKE, arg);
488 if (!proposal)
489 {
490 exit(1);
491 }
492 this->ike_proposals->insert_last(this->ike_proposals, proposal);
493 break;
494 case CMD_OPT_ESP_PROPOSAL:
495 proposal = proposal_create_from_string(PROTO_ESP, arg);
496 if (!proposal)
497 {
498 exit(1);
499 }
500 this->child_proposals->insert_last(this->child_proposals, proposal);
501 break;
502 case CMD_OPT_AH_PROPOSAL:
503 proposal = proposal_create_from_string(PROTO_AH, arg);
504 if (!proposal)
505 {
506 exit(1);
507 }
508 this->child_proposals->insert_last(this->child_proposals, proposal);
509 break;
510 case CMD_OPT_PROFILE:
511 set_profile(this, arg);
512 break;
513 default:
514 return FALSE;
515 }
516 return TRUE;
517 }
518
519 METHOD(cmd_connection_t, destroy, void,
520 private_cmd_connection_t *this)
521 {
522 this->ike_proposals->destroy_offset(this->ike_proposals,
523 offsetof(proposal_t, destroy));
524 this->child_proposals->destroy_offset(this->child_proposals,
525 offsetof(proposal_t, destroy));
526 this->local_ts->destroy_offset(this->local_ts,
527 offsetof(traffic_selector_t, destroy));
528 this->remote_ts->destroy_offset(this->remote_ts,
529 offsetof(traffic_selector_t, destroy));
530 free(this);
531 }
532
533 /**
534 * See header
535 */
536 cmd_connection_t *cmd_connection_create()
537 {
538 private_cmd_connection_t *this;
539
540 INIT(this,
541 .public = {
542 .handle = _handle,
543 .destroy = _destroy,
544 },
545 .pid = getpid(),
546 .local_ts = linked_list_create(),
547 .remote_ts = linked_list_create(),
548 .ike_proposals = linked_list_create(),
549 .child_proposals = linked_list_create(),
550 .profile = PROF_UNDEF,
551 );
552
553 /* always include the virtual IP in traffic selector list */
554 this->local_ts->insert_last(this->local_ts,
555 traffic_selector_create_dynamic(0, 0, 65535));
556
557 /* queue job, gets initiated as soon as we are up and running */
558 lib->processor->queue_job(lib->processor,
559 (job_t*)callback_job_create_with_prio(
560 (callback_job_cb_t)initiate, this, NULL,
561 (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
562
563 return &this->public;
564 }