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