charon-cmd: --agent optionally takes the path to an ssh-agent socket
[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_XAUTH,
42 PROF_V1_XAUTH_PSK,
43 PROF_V1_HYBRID,
44 };
45
46 ENUM(profile_names, PROF_V2_PUB, PROF_V1_HYBRID,
47 "ikev2-pub",
48 "ikev2-eap",
49 "ikev2-pub-eap",
50 "ikev1-pub",
51 "ikev1-xauth",
52 "ikev1-xauth-psk",
53 "ikev1-hybrid",
54 );
55
56 /**
57 * Private data of an cmd_connection_t object.
58 */
59 struct private_cmd_connection_t {
60
61 /**
62 * Public cmd_connection_t interface.
63 */
64 cmd_connection_t public;
65
66 /**
67 * Process ID to terminate on failure
68 */
69 pid_t pid;
70
71 /**
72 * List of local traffic selectors
73 */
74 linked_list_t *local_ts;
75
76 /**
77 * List of remote traffic selectors
78 */
79 linked_list_t *remote_ts;
80
81 /**
82 * Hostname to connect to
83 */
84 char *host;
85
86 /**
87 * Server identity, or NULL to use host
88 */
89 char *server;
90
91 /**
92 * Local identity
93 */
94 char *identity;
95
96 /**
97 * Is a private key configured
98 */
99 bool key_seen;
100
101 /**
102 * Selected connection profile
103 */
104 profile_t profile;
105 };
106
107 /**
108 * Shut down application
109 */
110 static void terminate(private_cmd_connection_t *this)
111 {
112 kill(this->pid, SIGUSR1);
113 }
114
115 /**
116 * Create peer config with associated ike config
117 */
118 static peer_cfg_t* create_peer_cfg(private_cmd_connection_t *this)
119 {
120 ike_cfg_t *ike_cfg;
121 peer_cfg_t *peer_cfg;
122 u_int16_t local_port, remote_port = IKEV2_UDP_PORT;
123 ike_version_t version = IKE_ANY;
124
125 switch (this->profile)
126 {
127 case PROF_UNDEF:
128 case PROF_V2_PUB:
129 case PROF_V2_EAP:
130 case PROF_V2_PUB_EAP:
131 version = IKEV2;
132 break;
133 case PROF_V1_PUB:
134 case PROF_V1_XAUTH:
135 case PROF_V1_XAUTH_PSK:
136 case PROF_V1_HYBRID:
137 version = IKEV1;
138 break;
139 }
140
141 local_port = charon->socket->get_port(charon->socket, FALSE);
142 if (local_port != IKEV2_UDP_PORT)
143 {
144 remote_port = IKEV2_NATT_PORT;
145 }
146 ike_cfg = ike_cfg_create(version, TRUE, FALSE, "0.0.0.0", FALSE, local_port,
147 this->host, FALSE, remote_port, FRAGMENTATION_NO, 0);
148 ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE));
149 peer_cfg = peer_cfg_create("cmd", ike_cfg,
150 CERT_SEND_IF_ASKED, UNIQUE_REPLACE, 1, /* keyingtries */
151 36000, 0, /* rekey 10h, reauth none */
152 600, 600, /* jitter, over 10min */
153 TRUE, FALSE, /* mobike, aggressive */
154 30, 0, /* DPD delay, timeout */
155 FALSE, NULL, NULL); /* mediation */
156 peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0));
157
158 return peer_cfg;
159 }
160
161 /**
162 * Add a single auth cfg of given class to peer cfg
163 */
164 static void add_auth_cfg(private_cmd_connection_t *this, peer_cfg_t *peer_cfg,
165 bool local, auth_class_t class)
166 {
167 identification_t *id;
168 auth_cfg_t *auth;
169
170 auth = auth_cfg_create();
171 auth->add(auth, AUTH_RULE_AUTH_CLASS, class);
172 if (local)
173 {
174 id = identification_create_from_string(this->identity);
175 }
176 else
177 {
178 if (this->server)
179 {
180 id = identification_create_from_string(this->server);
181 }
182 else
183 {
184 id = identification_create_from_string(this->host);
185 }
186 auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE);
187 }
188 auth->add(auth, AUTH_RULE_IDENTITY, id);
189 peer_cfg->add_auth_cfg(peer_cfg, auth, local);
190 }
191
192 /**
193 * Attach authentication configs to peer config
194 */
195 static bool add_auth_cfgs(private_cmd_connection_t *this, peer_cfg_t *peer_cfg)
196 {
197 if (this->profile == PROF_UNDEF)
198 {
199 if (this->key_seen)
200 {
201 this->profile = PROF_V2_PUB;
202 }
203 else
204 {
205 this->profile = PROF_V2_EAP;
206 }
207 }
208 switch (this->profile)
209 {
210 case PROF_V2_PUB:
211 case PROF_V2_PUB_EAP:
212 case PROF_V1_PUB:
213 case PROF_V1_XAUTH:
214 if (!this->key_seen)
215 {
216 DBG1(DBG_CFG, "missing private key for profile %N",
217 profile_names, this->profile);
218 return FALSE;
219 }
220 break;
221 default:
222 break;
223 }
224
225 switch (this->profile)
226 {
227 case PROF_V2_PUB:
228 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
229 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
230 break;
231 case PROF_V2_EAP:
232 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_EAP);
233 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
234 break;
235 case PROF_V2_PUB_EAP:
236 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
237 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_EAP);
238 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_ANY);
239 break;
240 case PROF_V1_PUB:
241 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
242 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
243 break;
244 case PROF_V1_XAUTH:
245 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PUBKEY);
246 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
247 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
248 break;
249 case PROF_V1_XAUTH_PSK:
250 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_PSK);
251 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
252 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PSK);
253 break;
254 case PROF_V1_HYBRID:
255 add_auth_cfg(this, peer_cfg, TRUE, AUTH_CLASS_XAUTH);
256 add_auth_cfg(this, peer_cfg, FALSE, AUTH_CLASS_PUBKEY);
257 break;
258 default:
259 return FALSE;
260 }
261 return TRUE;
262 }
263
264 /**
265 * Attach child config to peer config
266 */
267 static child_cfg_t* create_child_cfg(private_cmd_connection_t *this)
268 {
269 child_cfg_t *child_cfg;
270 traffic_selector_t *ts;
271 lifetime_cfg_t lifetime = {
272 .time = {
273 .life = 10800 /* 3h */,
274 .rekey = 10200 /* 2h50min */,
275 .jitter = 300 /* 5min */
276 }
277 };
278
279 child_cfg = child_cfg_create("cmd", &lifetime,
280 NULL, FALSE, MODE_TUNNEL, /* updown, hostaccess */
281 ACTION_NONE, ACTION_NONE, ACTION_NONE, FALSE,
282 0, 0, NULL, NULL, 0);
283 child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
284 while (this->local_ts->remove_first(this->local_ts, (void**)&ts) == SUCCESS)
285 {
286 child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
287 }
288 if (this->remote_ts->get_count(this->remote_ts) == 0)
289 {
290 /* add a 0.0.0.0/0 TS for remote side if none given */
291 ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE,
292 "0.0.0.0", 0, "255.255.255.255", 65535);
293 this->remote_ts->insert_last(this->remote_ts, ts);
294 }
295 while (this->remote_ts->remove_first(this->remote_ts,
296 (void**)&ts) == SUCCESS)
297 {
298 child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
299 }
300
301 return child_cfg;
302 }
303
304 /**
305 * Initiate the configured connection
306 */
307 static job_requeue_t initiate(private_cmd_connection_t *this)
308 {
309 peer_cfg_t *peer_cfg;
310 child_cfg_t *child_cfg;
311
312 if (!this->host)
313 {
314 DBG1(DBG_CFG, "unable to initiate, missing --host option");
315 terminate(this);
316 return JOB_REQUEUE_NONE;
317 }
318 if (!this->identity)
319 {
320 DBG1(DBG_CFG, "unable to initiate, missing --identity option");
321 terminate(this);
322 return JOB_REQUEUE_NONE;
323 }
324
325 peer_cfg = create_peer_cfg(this);
326
327 if (!add_auth_cfgs(this, peer_cfg))
328 {
329 peer_cfg->destroy(peer_cfg);
330 terminate(this);
331 return JOB_REQUEUE_NONE;
332 }
333
334 child_cfg = create_child_cfg(this);
335 peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg));
336
337 if (charon->controller->initiate(charon->controller, peer_cfg, child_cfg,
338 controller_cb_empty, NULL, 0) != SUCCESS)
339 {
340 terminate(this);
341 }
342 return JOB_REQUEUE_NONE;
343 }
344
345 /**
346 * Create a traffic selector from string, add to list
347 */
348 static void add_ts(private_cmd_connection_t *this,
349 linked_list_t *list, char *string)
350 {
351 traffic_selector_t *ts;
352
353 ts = traffic_selector_create_from_cidr(string, 0, 0, 65535);
354 if (!ts)
355 {
356 DBG1(DBG_CFG, "invalid traffic selector: %s", string);
357 exit(1);
358 }
359 list->insert_last(list, ts);
360 }
361
362 /**
363 * Parse profile name identifier
364 */
365 static void set_profile(private_cmd_connection_t *this, char *name)
366 {
367 int profile;
368
369 profile = enum_from_name(profile_names, name);
370 if (profile == -1)
371 {
372 DBG1(DBG_CFG, "unknown connection profile: %s", name);
373 exit(1);
374 }
375 this->profile = profile;
376 }
377
378 METHOD(cmd_connection_t, handle, bool,
379 private_cmd_connection_t *this, cmd_option_type_t opt, char *arg)
380 {
381 switch (opt)
382 {
383 case CMD_OPT_HOST:
384 this->host = arg;
385 break;
386 case CMD_OPT_REMOTE_IDENTITY:
387 this->server = arg;
388 break;
389 case CMD_OPT_IDENTITY:
390 this->identity = arg;
391 break;
392 case CMD_OPT_RSA:
393 case CMD_OPT_AGENT:
394 this->key_seen = TRUE;
395 break;
396 case CMD_OPT_LOCAL_TS:
397 add_ts(this, this->local_ts, arg);
398 break;
399 case CMD_OPT_REMOTE_TS:
400 add_ts(this, this->remote_ts, arg);
401 break;
402 case CMD_OPT_PROFILE:
403 set_profile(this, arg);
404 break;
405 default:
406 return FALSE;
407 }
408 return TRUE;
409 }
410
411 METHOD(cmd_connection_t, destroy, void,
412 private_cmd_connection_t *this)
413 {
414 this->local_ts->destroy_offset(this->local_ts,
415 offsetof(traffic_selector_t, destroy));
416 this->remote_ts->destroy_offset(this->remote_ts,
417 offsetof(traffic_selector_t, destroy));
418 free(this);
419 }
420
421 /**
422 * See header
423 */
424 cmd_connection_t *cmd_connection_create()
425 {
426 private_cmd_connection_t *this;
427
428 INIT(this,
429 .public = {
430 .handle = _handle,
431 .destroy = _destroy,
432 },
433 .pid = getpid(),
434 .local_ts = linked_list_create(),
435 .remote_ts = linked_list_create(),
436 .profile = PROF_UNDEF,
437 );
438
439 /* always include the virtual IP in traffic selector list */
440 this->local_ts->insert_last(this->local_ts,
441 traffic_selector_create_dynamic(0, 0, 65535));
442
443 /* queue job, gets initiated as soon as we are up and running */
444 lib->processor->queue_job(lib->processor,
445 (job_t*)callback_job_create_with_prio(
446 (callback_job_cb_t)initiate, this, NULL,
447 (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
448
449 return &this->public;
450 }