child-cfg: Allow passing NULL as proposal to add_proposal()
[strongswan.git] / src / libcharon / config / child_cfg.c
1 /*
2 * Copyright (C) 2008-2009 Tobias Brunner
3 * Copyright (C) 2005-2007 Martin Willi
4 * Copyright (C) 2005 Jan Hutter
5 * Hochschule fuer Technik Rapperswil
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 #include "child_cfg.h"
19
20 #include <stdint.h>
21
22 #include <daemon.h>
23
24 ENUM(action_names, ACTION_NONE, ACTION_RESTART,
25 "clear",
26 "hold",
27 "restart",
28 );
29
30 typedef struct private_child_cfg_t private_child_cfg_t;
31
32 /**
33 * Private data of an child_cfg_t object
34 */
35 struct private_child_cfg_t {
36
37 /**
38 * Public part
39 */
40 child_cfg_t public;
41
42 /**
43 * Number of references hold by others to this child_cfg
44 */
45 refcount_t refcount;
46
47 /**
48 * Name of the child_cfg, used to query it
49 */
50 char *name;
51
52 /**
53 * list for all proposals
54 */
55 linked_list_t *proposals;
56
57 /**
58 * list for traffic selectors for my site
59 */
60 linked_list_t *my_ts;
61
62 /**
63 * list for traffic selectors for others site
64 */
65 linked_list_t *other_ts;
66
67 /**
68 * updown script
69 */
70 char *updown;
71
72 /**
73 * allow host access
74 */
75 bool hostaccess;
76
77 /**
78 * Mode to propose for a initiated CHILD: tunnel/transport
79 */
80 ipsec_mode_t mode;
81
82 /**
83 * action to take to start CHILD_SA
84 */
85 action_t start_action;
86
87 /**
88 * action to take on DPD
89 */
90 action_t dpd_action;
91
92 /**
93 * action to take on CHILD_SA close
94 */
95 action_t close_action;
96
97 /**
98 * CHILD_SA lifetime config
99 */
100 lifetime_cfg_t lifetime;
101
102 /**
103 * enable IPComp
104 */
105 bool use_ipcomp;
106
107 /**
108 * Inactivity timeout
109 */
110 u_int32_t inactivity;
111
112 /**
113 * Reqid to install CHILD_SA with
114 */
115 u_int32_t reqid;
116
117 /**
118 * Optional mark to install inbound CHILD_SA with
119 */
120 mark_t mark_in;
121
122 /**
123 * Optional mark to install outbound CHILD_SA with
124 */
125 mark_t mark_out;
126
127 /**
128 * Traffic Flow Confidentiality padding, if enabled
129 */
130 u_int32_t tfc;
131
132 /**
133 * set up IPsec transport SA in MIPv6 proxy mode
134 */
135 bool proxy_mode;
136
137 /**
138 * enable installation and removal of kernel IPsec policies
139 */
140 bool install_policy;
141 };
142
143 METHOD(child_cfg_t, get_name, char*,
144 private_child_cfg_t *this)
145 {
146 return this->name;
147 }
148
149 METHOD(child_cfg_t, add_proposal, void,
150 private_child_cfg_t *this, proposal_t *proposal)
151 {
152 if (proposal)
153 {
154 this->proposals->insert_last(this->proposals, proposal);
155 }
156 }
157
158 METHOD(child_cfg_t, get_proposals, linked_list_t*,
159 private_child_cfg_t *this, bool strip_dh)
160 {
161 enumerator_t *enumerator;
162 proposal_t *current;
163 linked_list_t *proposals = linked_list_create();
164
165 enumerator = this->proposals->create_enumerator(this->proposals);
166 while (enumerator->enumerate(enumerator, &current))
167 {
168 current = current->clone(current);
169 if (strip_dh)
170 {
171 current->strip_dh(current, MODP_NONE);
172 }
173 proposals->insert_last(proposals, current);
174 }
175 enumerator->destroy(enumerator);
176
177 DBG2(DBG_CFG, "configured proposals: %#P", proposals);
178
179 return proposals;
180 }
181
182 METHOD(child_cfg_t, select_proposal, proposal_t*,
183 private_child_cfg_t*this, linked_list_t *proposals, bool strip_dh,
184 bool private)
185 {
186 enumerator_t *stored_enum, *supplied_enum;
187 proposal_t *stored, *supplied, *selected = NULL;
188
189 stored_enum = this->proposals->create_enumerator(this->proposals);
190 supplied_enum = proposals->create_enumerator(proposals);
191
192 /* compare all stored proposals with all supplied. Stored ones are preferred. */
193 while (stored_enum->enumerate(stored_enum, &stored))
194 {
195 stored = stored->clone(stored);
196 while (supplied_enum->enumerate(supplied_enum, &supplied))
197 {
198 if (strip_dh)
199 {
200 stored->strip_dh(stored, MODP_NONE);
201 }
202 selected = stored->select(stored, supplied, private);
203 if (selected)
204 {
205 DBG2(DBG_CFG, "received proposals: %#P", proposals);
206 DBG2(DBG_CFG, "configured proposals: %#P", this->proposals);
207 DBG2(DBG_CFG, "selected proposal: %P", selected);
208 break;
209 }
210 }
211 stored->destroy(stored);
212 if (selected)
213 {
214 break;
215 }
216 supplied_enum->destroy(supplied_enum);
217 supplied_enum = proposals->create_enumerator(proposals);
218 }
219 stored_enum->destroy(stored_enum);
220 supplied_enum->destroy(supplied_enum);
221 if (selected == NULL)
222 {
223 DBG1(DBG_CFG, "received proposals: %#P", proposals);
224 DBG1(DBG_CFG, "configured proposals: %#P", this->proposals);
225 }
226 return selected;
227 }
228
229 METHOD(child_cfg_t, add_traffic_selector, void,
230 private_child_cfg_t *this, bool local, traffic_selector_t *ts)
231 {
232 if (local)
233 {
234 this->my_ts->insert_last(this->my_ts, ts);
235 }
236 else
237 {
238 this->other_ts->insert_last(this->other_ts, ts);
239 }
240 }
241
242 METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*,
243 private_child_cfg_t *this, bool local, linked_list_t *supplied,
244 linked_list_t *hosts)
245 {
246 enumerator_t *e1, *e2;
247 traffic_selector_t *ts1, *ts2, *selected;
248 linked_list_t *result, *derived;
249 host_t *host;
250
251 result = linked_list_create();
252 derived = linked_list_create();
253 if (local)
254 {
255 e1 = this->my_ts->create_enumerator(this->my_ts);
256 }
257 else
258 {
259 e1 = this->other_ts->create_enumerator(this->other_ts);
260 }
261 /* In a first step, replace "dynamic" TS with the host list */
262 while (e1->enumerate(e1, &ts1))
263 {
264 if (hosts && hosts->get_count(hosts) &&
265 ts1->is_dynamic(ts1))
266 {
267 e2 = hosts->create_enumerator(hosts);
268 while (e2->enumerate(e2, &host))
269 {
270 ts2 = ts1->clone(ts1);
271 ts2->set_address(ts2, host);
272 derived->insert_last(derived, ts2);
273 }
274 e2->destroy(e2);
275 }
276 else
277 {
278 derived->insert_last(derived, ts1->clone(ts1));
279 }
280 }
281 e1->destroy(e1);
282
283 DBG2(DBG_CFG, "%s traffic selectors for %s:",
284 supplied ? "selecting" : "proposing", local ? "us" : "other");
285 if (supplied == NULL)
286 {
287 while (derived->remove_first(derived, (void**)&ts1) == SUCCESS)
288 {
289 DBG2(DBG_CFG, " %R", ts1);
290 result->insert_last(result, ts1);
291 }
292 derived->destroy(derived);
293 }
294 else
295 {
296 e1 = derived->create_enumerator(derived);
297 e2 = supplied->create_enumerator(supplied);
298 /* enumerate all configured/derived selectors */
299 while (e1->enumerate(e1, &ts1))
300 {
301 /* enumerate all supplied traffic selectors */
302 while (e2->enumerate(e2, &ts2))
303 {
304 selected = ts1->get_subset(ts1, ts2);
305 if (selected)
306 {
307 DBG2(DBG_CFG, " config: %R, received: %R => match: %R",
308 ts1, ts2, selected);
309 result->insert_last(result, selected);
310 }
311 else
312 {
313 DBG2(DBG_CFG, " config: %R, received: %R => no match",
314 ts1, ts2);
315 }
316 }
317 supplied->reset_enumerator(supplied, e2);
318 }
319 e1->destroy(e1);
320 e2->destroy(e2);
321
322 /* check if we/peer did any narrowing, raise alert */
323 e1 = derived->create_enumerator(derived);
324 e2 = result->create_enumerator(result);
325 while (e1->enumerate(e1, &ts1))
326 {
327 if (!e2->enumerate(e2, &ts2) || !ts1->equals(ts1, ts2))
328 {
329 charon->bus->alert(charon->bus, ALERT_TS_NARROWED,
330 local, result, this);
331 break;
332 }
333 }
334 e1->destroy(e1);
335 e2->destroy(e2);
336
337 derived->destroy_offset(derived, offsetof(traffic_selector_t, destroy));
338 }
339
340 /* remove any redundant traffic selectors in the list */
341 e1 = result->create_enumerator(result);
342 e2 = result->create_enumerator(result);
343 while (e1->enumerate(e1, &ts1))
344 {
345 while (e2->enumerate(e2, &ts2))
346 {
347 if (ts1 != ts2)
348 {
349 if (ts2->is_contained_in(ts2, ts1))
350 {
351 result->remove_at(result, e2);
352 ts2->destroy(ts2);
353 result->reset_enumerator(result, e1);
354 break;
355 }
356 if (ts1->is_contained_in(ts1, ts2))
357 {
358 result->remove_at(result, e1);
359 ts1->destroy(ts1);
360 break;
361 }
362 }
363 }
364 result->reset_enumerator(result, e2);
365 }
366 e1->destroy(e1);
367 e2->destroy(e2);
368
369 return result;
370 }
371
372 METHOD(child_cfg_t, get_updown, char*,
373 private_child_cfg_t *this)
374 {
375 return this->updown;
376 }
377
378 METHOD(child_cfg_t, get_hostaccess, bool,
379 private_child_cfg_t *this)
380 {
381 return this->hostaccess;
382 }
383
384 /**
385 * Applies jitter to the rekey value. Returns the new rekey value.
386 * Note: The distribution of random values is not perfect, but it
387 * should get the job done.
388 */
389 static u_int64_t apply_jitter(u_int64_t rekey, u_int64_t jitter)
390 {
391 if (jitter == 0)
392 {
393 return rekey;
394 }
395 jitter = (jitter == UINT64_MAX) ? jitter : jitter + 1;
396 return rekey - jitter * (random() / (RAND_MAX + 1.0));
397 }
398 #define APPLY_JITTER(l) l.rekey = apply_jitter(l.rekey, l.jitter)
399
400 METHOD(child_cfg_t, get_lifetime, lifetime_cfg_t*,
401 private_child_cfg_t *this)
402 {
403 lifetime_cfg_t *lft = malloc_thing(lifetime_cfg_t);
404 memcpy(lft, &this->lifetime, sizeof(lifetime_cfg_t));
405 APPLY_JITTER(lft->time);
406 APPLY_JITTER(lft->bytes);
407 APPLY_JITTER(lft->packets);
408 return lft;
409 }
410
411 METHOD(child_cfg_t, get_mode, ipsec_mode_t,
412 private_child_cfg_t *this)
413 {
414 return this->mode;
415 }
416
417 METHOD(child_cfg_t, get_start_action, action_t,
418 private_child_cfg_t *this)
419 {
420 return this->start_action;
421 }
422
423 METHOD(child_cfg_t, get_dpd_action, action_t,
424 private_child_cfg_t *this)
425 {
426 return this->dpd_action;
427 }
428
429 METHOD(child_cfg_t, get_close_action, action_t,
430 private_child_cfg_t *this)
431 {
432 return this->close_action;
433 }
434
435 METHOD(child_cfg_t, get_dh_group, diffie_hellman_group_t,
436 private_child_cfg_t *this)
437 {
438 enumerator_t *enumerator;
439 proposal_t *proposal;
440 u_int16_t dh_group = MODP_NONE;
441
442 enumerator = this->proposals->create_enumerator(this->proposals);
443 while (enumerator->enumerate(enumerator, &proposal))
444 {
445 if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP, &dh_group, NULL))
446 {
447 break;
448 }
449 }
450 enumerator->destroy(enumerator);
451 return dh_group;
452 }
453
454 METHOD(child_cfg_t, use_ipcomp, bool,
455 private_child_cfg_t *this)
456 {
457 return this->use_ipcomp;
458 }
459
460 METHOD(child_cfg_t, get_inactivity, u_int32_t,
461 private_child_cfg_t *this)
462 {
463 return this->inactivity;
464 }
465
466 METHOD(child_cfg_t, get_reqid, u_int32_t,
467 private_child_cfg_t *this)
468 {
469 return this->reqid;
470 }
471
472 METHOD(child_cfg_t, get_mark, mark_t,
473 private_child_cfg_t *this, bool inbound)
474 {
475 return inbound ? this->mark_in : this->mark_out;
476 }
477
478 METHOD(child_cfg_t, get_tfc, u_int32_t,
479 private_child_cfg_t *this)
480 {
481 return this->tfc;
482 }
483
484 METHOD(child_cfg_t, set_mipv6_options, void,
485 private_child_cfg_t *this, bool proxy_mode, bool install_policy)
486 {
487 this->proxy_mode = proxy_mode;
488 this->install_policy = install_policy;
489 }
490
491 METHOD(child_cfg_t, use_proxy_mode, bool,
492 private_child_cfg_t *this)
493 {
494 return this->proxy_mode;
495 }
496
497 METHOD(child_cfg_t, install_policy, bool,
498 private_child_cfg_t *this)
499 {
500 return this->install_policy;
501 }
502
503 METHOD(child_cfg_t, get_ref, child_cfg_t*,
504 private_child_cfg_t *this)
505 {
506 ref_get(&this->refcount);
507 return &this->public;
508 }
509
510 METHOD(child_cfg_t, destroy, void,
511 private_child_cfg_t *this)
512 {
513 if (ref_put(&this->refcount))
514 {
515 this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
516 this->my_ts->destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy));
517 this->other_ts->destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy));
518 if (this->updown)
519 {
520 free(this->updown);
521 }
522 free(this->name);
523 free(this);
524 }
525 }
526
527 /*
528 * Described in header-file
529 */
530 child_cfg_t *child_cfg_create(char *name, lifetime_cfg_t *lifetime,
531 char *updown, bool hostaccess,
532 ipsec_mode_t mode, action_t start_action,
533 action_t dpd_action, action_t close_action,
534 bool ipcomp, u_int32_t inactivity, u_int32_t reqid,
535 mark_t *mark_in, mark_t *mark_out, u_int32_t tfc)
536 {
537 private_child_cfg_t *this;
538
539 INIT(this,
540 .public = {
541 .get_name = _get_name,
542 .add_traffic_selector = _add_traffic_selector,
543 .get_traffic_selectors = _get_traffic_selectors,
544 .add_proposal = _add_proposal,
545 .get_proposals = _get_proposals,
546 .select_proposal = _select_proposal,
547 .get_updown = _get_updown,
548 .get_hostaccess = _get_hostaccess,
549 .get_mode = _get_mode,
550 .get_start_action = _get_start_action,
551 .get_dpd_action = _get_dpd_action,
552 .get_close_action = _get_close_action,
553 .get_lifetime = _get_lifetime,
554 .get_dh_group = _get_dh_group,
555 .set_mipv6_options = _set_mipv6_options,
556 .use_ipcomp = _use_ipcomp,
557 .get_inactivity = _get_inactivity,
558 .get_reqid = _get_reqid,
559 .get_mark = _get_mark,
560 .get_tfc = _get_tfc,
561 .use_proxy_mode = _use_proxy_mode,
562 .install_policy = _install_policy,
563 .get_ref = _get_ref,
564 .destroy = _destroy,
565 },
566 .name = strdup(name),
567 .updown = strdupnull(updown),
568 .hostaccess = hostaccess,
569 .mode = mode,
570 .start_action = start_action,
571 .dpd_action = dpd_action,
572 .close_action = close_action,
573 .use_ipcomp = ipcomp,
574 .inactivity = inactivity,
575 .reqid = reqid,
576 .proxy_mode = FALSE,
577 .install_policy = TRUE,
578 .refcount = 1,
579 .proposals = linked_list_create(),
580 .my_ts = linked_list_create(),
581 .other_ts = linked_list_create(),
582 .tfc = tfc,
583 );
584
585 if (mark_in)
586 {
587 this->mark_in = *mark_in;
588 }
589 if (mark_out)
590 {
591 this->mark_out = *mark_out;
592 }
593 memcpy(&this->lifetime, lifetime, sizeof(lifetime_cfg_t));
594
595 return &this->public;
596 }