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