Use faster ike_sa_id and a delete job to handle RADIUS DAE Delete-Request
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_dae.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_dae.h"
17
18 #include "radius_message.h"
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/socket.h>
23 #include <unistd.h>
24 #include <errno.h>
25
26 #include <daemon.h>
27 #include <threading/thread.h>
28 #include <processing/jobs/callback_job.h>
29 #include <processing/jobs/delete_ike_sa_job.h>
30
31 #define RADIUS_DAE_PORT 3799
32
33 typedef struct private_eap_radius_dae_t private_eap_radius_dae_t;
34
35 /**
36 * Private data of an eap_radius_dae_t object.
37 */
38 struct private_eap_radius_dae_t {
39
40 /**
41 * Public eap_radius_dae_t interface.
42 */
43 eap_radius_dae_t public;
44
45 /**
46 * RADIUS session state
47 */
48 eap_radius_accounting_t *accounting;
49
50 /**
51 * Socket to listen on authorization extension port
52 */
53 int fd;
54
55 /**
56 * Listen job
57 */
58 callback_job_t *job;
59
60 /**
61 * RADIUS shared secret for DAE exchanges
62 */
63 chunk_t secret;
64
65 /**
66 * MD5 hasher
67 */
68 hasher_t *hasher;
69
70 /**
71 * HMAC MD5 signer, with secret set
72 */
73 signer_t *signer;
74 };
75
76 /**
77 * Send an ACK/NAK response for a request
78 */
79 static void send_response(private_eap_radius_dae_t *this,
80 radius_message_t *request, radius_message_code_t code,
81 host_t *client)
82 {
83 radius_message_t *response;
84 chunk_t data;
85
86 response = radius_message_create(code);
87 response->set_identifier(response, request->get_identifier(request));
88 response->sign(response, request->get_authenticator(request),
89 this->secret, this->hasher, this->signer, NULL);
90
91 data = response->get_encoding(response);
92 if (sendto(this->fd, data.ptr, data.len, 0, client->get_sockaddr(client),
93 *client->get_sockaddr_len(client)) != data.len)
94 {
95 DBG1(DBG_CFG, "sending RADIUS DAE response failed: %s", strerror(errno));
96 }
97 response->destroy(response);
98 }
99
100 /**
101 * Add all IKE_SAs matching to user to a list
102 */
103 static void add_matching_ike_sas(linked_list_t *list, identification_t *user)
104 {
105 enumerator_t *enumerator;
106 ike_sa_t *ike_sa;
107 ike_sa_id_t *id;
108
109 enumerator = charon->ike_sa_manager->create_enumerator(
110 charon->ike_sa_manager, FALSE);
111 while (enumerator->enumerate(enumerator, &ike_sa))
112 {
113 if (user->matches(user, ike_sa->get_other_eap_id(ike_sa)))
114 {
115 id = ike_sa->get_id(ike_sa);
116 list->insert_last(list, id->clone(id));
117 }
118 }
119 enumerator->destroy(enumerator);
120 }
121
122 /**
123 * Get list of IKE_SAs matching a Disconnect/CoA request
124 */
125 static linked_list_t *get_matching_ike_sas(private_eap_radius_dae_t *this,
126 radius_message_t *request, host_t *client)
127 {
128 enumerator_t *enumerator;
129 identification_t *user;
130 linked_list_t *ids;
131 chunk_t data;
132 int type;
133
134 ids = linked_list_create();
135
136 enumerator = request->create_enumerator(request);
137 while (enumerator->enumerate(enumerator, &type, &data))
138 {
139 if (type == RAT_USER_NAME && data.len)
140 {
141 user = identification_create_from_data(data);
142 DBG1(DBG_CFG, "received RADIUS DAE %N for %Y from %H",
143 radius_message_code_names, RMC_DISCONNECT_REQUEST, user, client);
144 add_matching_ike_sas(ids, user);
145 user->destroy(user);
146 }
147 }
148 enumerator->destroy(enumerator);
149
150 return ids;
151 }
152
153 /**
154 * Process a DAE disconnect request, send response
155 */
156 static void process_disconnect(private_eap_radius_dae_t *this,
157 radius_message_t *request, host_t *client)
158 {
159 enumerator_t *enumerator;
160 linked_list_t *ids;
161 ike_sa_id_t *id;
162
163 ids = get_matching_ike_sas(this, request, client);
164
165 if (ids->get_count(ids))
166 {
167 DBG1(DBG_CFG, "closing %d IKE_SA%s matching %N, sending %N",
168 ids->get_count(ids), ids->get_count(ids) > 1 ? "s" : "",
169 radius_message_code_names, RMC_DISCONNECT_REQUEST,
170 radius_message_code_names, RMC_DISCONNECT_ACK);
171
172 enumerator = ids->create_enumerator(ids);
173 while (enumerator->enumerate(enumerator, &id))
174 {
175 lib->processor->queue_job(lib->processor, (job_t*)
176 delete_ike_sa_job_create(id, TRUE));
177 }
178 enumerator->destroy(enumerator);
179
180 send_response(this, request, RMC_DISCONNECT_ACK, client);
181 }
182 else
183 {
184 DBG1(DBG_CFG, "no IKE_SA matches %N, sending %N",
185 radius_message_code_names, RMC_DISCONNECT_REQUEST,
186 radius_message_code_names, RMC_DISCONNECT_NAK);
187 send_response(this, request, RMC_DISCONNECT_NAK, client);
188 }
189 ids->destroy_offset(ids, offsetof(ike_sa_id_t, destroy));
190 }
191
192 /**
193 * Receive RADIUS DAE requests
194 */
195 static job_requeue_t receive(private_eap_radius_dae_t *this)
196 {
197 struct sockaddr_storage addr;
198 socklen_t addr_len = sizeof(addr);
199 radius_message_t *request;
200 char buf[2048];
201 ssize_t len;
202 bool oldstate;
203 host_t *client;
204
205 oldstate = thread_cancelability(TRUE);
206 len = recvfrom(this->fd, buf, sizeof(buf), 0,
207 (struct sockaddr*)&addr, &addr_len);
208 thread_cancelability(oldstate);
209
210 if (len > 0)
211 {
212 request = radius_message_parse(chunk_create(buf, len));
213 if (request)
214 {
215 client = host_create_from_sockaddr((struct sockaddr*)&addr);
216 if (client)
217 {
218 if (request->verify(request, NULL, this->secret,
219 this->hasher, this->signer))
220 {
221 switch (request->get_code(request))
222 {
223 case RMC_DISCONNECT_REQUEST:
224 process_disconnect(this, request, client);
225 break;
226 case RMC_COA_REQUEST:
227 /* TODO */
228 default:
229 DBG1(DBG_CFG, "ignoring unsupported RADIUS DAE %N "
230 "message from %H", radius_message_code_names,
231 request->get_code(request), client);
232 break;
233 }
234 }
235 client->destroy(client);
236 }
237 request->destroy(request);
238 }
239 else
240 {
241 DBG1(DBG_NET, "ignoring invalid RADIUS DAE request");
242 }
243 }
244 else
245 {
246 DBG1(DBG_NET, "receving RADIUS DAE request failed: %s", strerror(errno));
247 }
248 return JOB_REQUEUE_DIRECT;
249 }
250
251 /**
252 * Open DAE socket
253 */
254 static bool open_socket(private_eap_radius_dae_t *this)
255 {
256 host_t *host;
257
258 this->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
259 if (this->fd == -1)
260 {
261 DBG1(DBG_CFG, "unable to open RADIUS DAE socket: %s", strerror(errno));
262 return FALSE;
263 }
264
265 host = host_create_from_string(
266 lib->settings->get_str(lib->settings,
267 "charon.plugins.eap-radius.dae.listen", "0.0.0.0"),
268 lib->settings->get_int(lib->settings,
269 "charon.plugins.eap-radius.dae.port", RADIUS_DAE_PORT));
270 if (!host)
271 {
272 DBG1(DBG_CFG, "invalid RADIUS DAE listen address");
273 return FALSE;
274 }
275
276 if (bind(this->fd, host->get_sockaddr(host),
277 *host->get_sockaddr_len(host)) == -1)
278 {
279 DBG1(DBG_CFG, "unable to bind RADIUS DAE socket: %s", strerror(errno));
280 host->destroy(host);
281 return FALSE;
282 }
283 host->destroy(host);
284 return TRUE;
285 }
286
287 METHOD(eap_radius_dae_t, destroy, void,
288 private_eap_radius_dae_t *this)
289 {
290 if (this->job)
291 {
292 this->job->cancel(this->job);
293 }
294 if (this->fd != -1)
295 {
296 close(this->fd);
297 }
298 DESTROY_IF(this->signer);
299 DESTROY_IF(this->hasher);
300 free(this);
301 }
302
303 /**
304 * See header
305 */
306 eap_radius_dae_t *eap_radius_dae_create(eap_radius_accounting_t *accounting)
307 {
308 private_eap_radius_dae_t *this;
309
310 INIT(this,
311 .public = {
312 .destroy = _destroy,
313 },
314 .accounting = accounting,
315 .fd = -1,
316 .secret = {
317 .ptr = lib->settings->get_str(lib->settings,
318 "charon.plugins.eap-radius.dae.secret", NULL),
319 },
320 .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
321 .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
322 );
323
324 if (!this->hasher || !this->signer)
325 {
326 destroy(this);
327 return NULL;
328 }
329 if (!this->secret.ptr)
330 {
331 DBG1(DBG_CFG, "missing RADIUS DAE secret, disabled");
332 destroy(this);
333 return NULL;
334 }
335 this->secret.len = strlen(this->secret.ptr);
336 this->signer->set_key(this->signer, this->secret);
337
338 if (!open_socket(this))
339 {
340 destroy(this);
341 return NULL;
342 }
343
344 this->job = callback_job_create_with_prio((callback_job_cb_t)receive,
345 this, NULL, NULL, JOB_PRIO_CRITICAL);
346 lib->processor->queue_job(lib->processor, (job_t*)this->job);
347
348 return &this->public;
349 }