eap-radius: do RADIUS/IKE attribute forwarding in XAuth backend
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_forward.c
1 /*
2 * Copyright (C) 2012 Martin Willi
3 * Copyright (C) 2012 revosec AG
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "eap_radius_forward.h"
17
18 #include <daemon.h>
19 #include <collections/linked_list.h>
20 #include <collections/hashtable.h>
21 #include <threading/mutex.h>
22
23 typedef struct private_eap_radius_forward_t private_eap_radius_forward_t;
24
25 /**
26 * Private data of an eap_radius_forward_t object.
27 */
28 struct private_eap_radius_forward_t {
29
30 /**
31 * Public eap_radius_forward_t interface.
32 */
33 eap_radius_forward_t public;
34
35 /**
36 * List of attribute types to copy from IKE, as attr_t
37 */
38 linked_list_t *from_attr;
39
40 /**
41 * List of attribute types to copy to IKE, as attr_t
42 */
43 linked_list_t *to_attr;
44
45 /**
46 * Queued to forward from IKE, unique_id => linked_list_t of chunk_t
47 */
48 hashtable_t *from;
49
50 /**
51 * Queued to forward to IKE, unique_id => linked_list_t of chunk_t
52 */
53 hashtable_t *to;
54
55 /**
56 * Mutex to lock concurrent access to hashtables
57 */
58 mutex_t *mutex;
59 };
60
61 /**
62 * RADIUS attribute selector
63 */
64 typedef struct {
65 /** vendor ID, 0 for standard attributes */
66 u_int32_t vendor;
67 /** attribute type */
68 u_int8_t type;
69 } attr_t;
70
71 /**
72 * Single instance of this
73 */
74 static private_eap_radius_forward_t *singleton = NULL;
75
76 /**
77 * Hashtable hash function
78 */
79 static u_int hash(uintptr_t key)
80 {
81 return key;
82 }
83
84 /**
85 * Hashtable equals function
86 */
87 static bool equals(uintptr_t a, uintptr_t b)
88 {
89 return a == b;
90 }
91
92 /**
93 * Free a queue entry
94 */
95 static void free_attribute(chunk_t *chunk)
96 {
97 free(chunk->ptr);
98 free(chunk);
99 }
100
101 /**
102 * Lookup/create an attribute queue from a table
103 */
104 static linked_list_t *lookup_queue(private_eap_radius_forward_t *this,
105 hashtable_t *table)
106 {
107 linked_list_t *queue = NULL;
108 ike_sa_t *ike_sa;
109 uintptr_t id;
110
111 ike_sa = charon->bus->get_sa(charon->bus);
112 if (ike_sa && ike_sa->supports_extension(ike_sa, EXT_STRONGSWAN))
113 {
114 id = ike_sa->get_unique_id(ike_sa);
115 this->mutex->lock(this->mutex);
116 queue = table->get(table, (void*)id);
117 if (!queue)
118 {
119 queue = linked_list_create();
120 table->put(table, (void*)id, queue);
121 }
122 this->mutex->unlock(this->mutex);
123 }
124 return queue;
125 }
126
127 /**
128 * Remove attribute queue from table
129 */
130 static void remove_queue(private_eap_radius_forward_t *this,
131 hashtable_t *table, ike_sa_t *ike_sa)
132 {
133 linked_list_t *queue;
134
135 this->mutex->lock(this->mutex);
136 queue = table->remove(table, (void*)(uintptr_t)ike_sa->get_unique_id(ike_sa));
137 this->mutex->unlock(this->mutex);
138 if (queue)
139 {
140 queue->destroy_function(queue, (void*)free_attribute);
141 }
142 }
143
144 /**
145 * Check if RADIUS attribute is contained in selector
146 */
147 static bool is_attribute_selected(linked_list_t *selector,
148 radius_attribute_type_t type, chunk_t data)
149 {
150 enumerator_t *enumerator;
151 u_int32_t vendor = 0;
152 attr_t *sel;
153 bool found = FALSE;
154
155 if (type == RAT_VENDOR_SPECIFIC)
156 {
157 if (data.len < 4)
158 {
159 return FALSE;
160 }
161 vendor = untoh32(data.ptr);
162 }
163 enumerator = selector->create_enumerator(selector);
164 while (!found && enumerator->enumerate(enumerator, &sel))
165 {
166 if (sel->vendor == vendor)
167 {
168 if (vendor)
169 {
170 if (sel->type == 0)
171 { /* any of that vendor is fine */
172 found = TRUE;
173 }
174 else if (data.len > 4 && data.ptr[4] == sel->type)
175 { /* vendor specific type field, as defined in RFC 2865 */
176 found = TRUE;
177 }
178 }
179 else
180 {
181 if (sel->type == type)
182 {
183 found = TRUE;
184 }
185 }
186 }
187 }
188 enumerator->destroy(enumerator);
189
190 return found;
191 }
192
193 /**
194 * Copy RADIUS attributes from queue to a RADIUS message
195 */
196 static void queue2radius(linked_list_t *queue, radius_message_t *message)
197 {
198 chunk_t *data;
199
200 while (queue->remove_last(queue, (void**)&data) == SUCCESS)
201 {
202 if (data->len >= 2)
203 {
204 message->add(message, data->ptr[0], chunk_skip(*data, 2));
205 }
206 free_attribute(data);
207 }
208 }
209
210 /**
211 * Copy RADIUS attributes from a RADIUS message to the queue
212 */
213 static void radius2queue(radius_message_t *message, linked_list_t *queue,
214 linked_list_t *selector)
215 {
216 enumerator_t *enumerator;
217 int type;
218 chunk_t data, hdr, *ptr;
219
220 enumerator = message->create_enumerator(message);
221 while (enumerator->enumerate(enumerator, &type, &data))
222 {
223 if (is_attribute_selected(selector, type, data))
224 {
225 hdr = chunk_alloc(2);
226 hdr.ptr[0] = type;
227 hdr.ptr[1] = data.len + 2;
228
229 INIT(ptr);
230 *ptr = chunk_cat("mc", hdr, data);
231 queue->insert_last(queue, ptr);
232 }
233 }
234 enumerator->destroy(enumerator);
235 }
236
237 /**
238 * Copy RADIUS attribute nofifies from IKE message to queue
239 */
240 static void ike2queue(message_t *message, linked_list_t *queue,
241 linked_list_t *selector)
242 {
243 enumerator_t *enumerator;
244 payload_t *payload;
245 notify_payload_t *notify;
246 chunk_t data, *ptr;
247
248 enumerator = message->create_payload_enumerator(message);
249 while (enumerator->enumerate(enumerator, &payload))
250 {
251 if (payload->get_type(payload) == NOTIFY ||
252 payload->get_type(payload) == NOTIFY_V1)
253 {
254 notify = (notify_payload_t*)payload;
255 if (notify->get_notify_type(notify) == RADIUS_ATTRIBUTE)
256 {
257 data = notify->get_notification_data(notify);
258 if (data.len >= 2 && is_attribute_selected(selector,
259 data.ptr[0], chunk_skip(data, 2)))
260 {
261 INIT(ptr);
262 *ptr = chunk_clone(data);
263 queue->insert_last(queue, ptr);
264 }
265 }
266 }
267 }
268 enumerator->destroy(enumerator);
269 }
270
271 /**
272 * Copy RADUIS attributes from queue to IKE message notifies
273 */
274 static void queue2ike(linked_list_t *queue, message_t *message)
275 {
276 chunk_t *data;
277
278 while (queue->remove_last(queue, (void**)&data) == SUCCESS)
279 {
280 message->add_notify(message, FALSE, RADIUS_ATTRIBUTE, *data);
281 free_attribute(data);
282 }
283 }
284
285 /**
286 * See header.
287 */
288 void eap_radius_forward_from_ike(radius_message_t *request)
289 {
290 private_eap_radius_forward_t *this = singleton;
291 linked_list_t *queue;
292
293 if (this)
294 {
295 queue = lookup_queue(this, this->from);
296 if (queue)
297 {
298 queue2radius(queue, request);
299 }
300 }
301 }
302
303 /**
304 * See header.
305 */
306 void eap_radius_forward_to_ike(radius_message_t *response)
307 {
308 private_eap_radius_forward_t *this = singleton;
309 linked_list_t *queue;
310
311 if (this)
312 {
313 queue = lookup_queue(this, this->to);
314 if (queue)
315 {
316 radius2queue(response, queue, this->to_attr);
317 }
318 }
319 }
320
321 METHOD(listener_t, message, bool,
322 private_eap_radius_forward_t *this,
323 ike_sa_t *ike_sa, message_t *message, bool incoming, bool plain)
324 {
325 linked_list_t *queue;
326
327 if (plain && message->get_exchange_type(message) == IKE_AUTH)
328 {
329 if (incoming)
330 {
331 queue = lookup_queue(this, this->from);
332 if (queue)
333 {
334 ike2queue(message, queue, this->from_attr);
335 }
336 }
337 else
338 {
339 queue = lookup_queue(this, this->to);
340 if (queue)
341 {
342 queue2ike(queue, message);
343 }
344 }
345 }
346 return TRUE;
347 }
348
349 METHOD(listener_t, ike_updown, bool,
350 private_eap_radius_forward_t *this, ike_sa_t *ike_sa, bool up)
351 {
352 /* up or down, we don't need the state anymore */
353 remove_queue(this, this->from, ike_sa);
354 remove_queue(this, this->to, ike_sa);
355 return TRUE;
356 }
357
358 /**
359 * Parse a selector string to a list of attr_t selectors
360 */
361 static linked_list_t* parse_selector(char *selector)
362 {
363 enumerator_t *enumerator;
364 linked_list_t *list;
365 char *token, *pos;
366
367 list = linked_list_create();
368 enumerator = enumerator_create_token(selector, ",", " ");
369 while (enumerator->enumerate(enumerator, &token))
370 {
371 int type, vendor = 0;
372 attr_t *attr;
373
374 pos = strchr(token, ':');
375 if (pos)
376 {
377 *(pos++) = 0;
378 vendor = atoi(token);
379 token = pos;
380 }
381 type = enum_from_name(radius_attribute_type_names, token);
382 if (type == -1)
383 {
384 type = atoi(token);
385 }
386 if (vendor == 0 && type == 0)
387 {
388 DBG1(DBG_CFG, "ignoring unknown RADIUS attribute type '%s'", token);
389 }
390 else
391 {
392 INIT(attr,
393 .type = type,
394 .vendor = vendor,
395 );
396 list->insert_last(list, attr);
397 if (!vendor)
398 {
399 DBG1(DBG_IKE, "forwarding RADIUS attribute %N",
400 radius_attribute_type_names, type);
401 }
402 else
403 {
404 DBG1(DBG_IKE, "forwarding RADIUS VSA %d-%d", vendor, type);
405 }
406 }
407 }
408 enumerator->destroy(enumerator);
409 return list;
410 }
411
412 METHOD(eap_radius_forward_t, destroy, void,
413 private_eap_radius_forward_t *this)
414 {
415 this->from_attr->destroy_function(this->from_attr, free);
416 this->to_attr->destroy_function(this->to_attr, free);
417 this->from->destroy(this->from);
418 this->to->destroy(this->to);
419 this->mutex->destroy(this->mutex);
420 free(this);
421 singleton = NULL;
422 }
423
424 /**
425 * See header
426 */
427 eap_radius_forward_t *eap_radius_forward_create()
428 {
429 private_eap_radius_forward_t *this;
430
431 INIT(this,
432 .public = {
433 .listener = {
434 .message = _message,
435 .ike_updown = _ike_updown,
436 },
437 .destroy = _destroy,
438 },
439 .from_attr = parse_selector(lib->settings->get_str(lib->settings,
440 "%s.plugins.eap-radius.forward.ike_to_radius", "",
441 charon->name)),
442 .to_attr = parse_selector(lib->settings->get_str(lib->settings,
443 "%s.plugins.eap-radius.forward.radius_to_ike", "",
444 charon->name)),
445 .from = hashtable_create((hashtable_hash_t)hash,
446 (hashtable_equals_t)equals, 8),
447 .to = hashtable_create((hashtable_hash_t)hash,
448 (hashtable_equals_t)equals, 8),
449 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
450 );
451
452 if (this->from_attr->get_count(this->from_attr) == 0 &&
453 this->to_attr->get_count(this->to_attr) == 0)
454 {
455 destroy(this);
456 return NULL;
457 }
458
459 singleton = this;
460 return &this->public;
461 }