define peer and server identities
[strongswan.git] / src / libcharon / plugins / tnc_pdp / tnc_pdp.c
1 /*
2 * Copyright (C) 2012 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
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 "tnc_pdp.h"
17
18 #include <errno.h>
19 #include <unistd.h>
20
21 #include <radius_message.h>
22
23 #include <daemon.h>
24 #include <debug.h>
25 #include <threading/thread.h>
26 #include <processing/jobs/callback_job.h>
27 #include <sa/authenticators/eap/eap_method.h>
28
29 typedef struct private_tnc_pdp_t private_tnc_pdp_t;
30
31 /**
32 * Maximum size of a RADIUS IP packet
33 */
34 #define MAX_PACKET 4096
35
36 /**
37 * private data of tnc_pdp_t
38 */
39 struct private_tnc_pdp_t {
40
41 /**
42 * implements tnc_pdp_t interface
43 */
44 tnc_pdp_t public;
45
46 /**
47 * IPv4 RADIUS socket
48 */
49 int ipv4;
50
51 /**
52 * IPv6 RADIUS socket
53 */
54 int ipv6;
55
56 /**
57 * Callback job dispatching commands
58 */
59 callback_job_t *job;
60
61 /**
62 * RADIUS shared secret
63 */
64 chunk_t secret;
65
66 /**
67 * MD5 hasher
68 */
69 hasher_t *hasher;
70
71 /**
72 * HMAC MD5 signer, with secret set
73 */
74 signer_t *signer;
75
76 /**
77 * EAP method
78 */
79 eap_method_t *method;
80 };
81
82
83 /**
84 * Open IPv4 or IPv6 UDP RADIUS socket
85 */
86 static int open_socket(private_tnc_pdp_t *this, int family, u_int16_t port)
87 {
88 int on = TRUE;
89 struct sockaddr_storage addr;
90 socklen_t addrlen;
91 int skt;
92
93 memset(&addr, 0, sizeof(addr));
94 addr.ss_family = family;
95
96 /* precalculate constants depending on address family */
97 switch (family)
98 {
99 case AF_INET:
100 {
101 struct sockaddr_in *sin = (struct sockaddr_in *)&addr;
102
103 htoun32(&sin->sin_addr.s_addr, INADDR_ANY);
104 htoun16(&sin->sin_port, port);
105 addrlen = sizeof(struct sockaddr_in);
106 break;
107 }
108 case AF_INET6:
109 {
110 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr;
111
112 memcpy(&sin6->sin6_addr, &in6addr_any, sizeof(in6addr_any));
113 htoun16(&sin6->sin6_port, port);
114 addrlen = sizeof(struct sockaddr_in6);
115 break;
116 }
117 default:
118 return 0;
119 }
120
121 /* open the socket */
122 skt = socket(family, SOCK_DGRAM, IPPROTO_UDP);
123 if (skt < 0)
124 {
125 DBG1(DBG_CFG, "opening RADIUS socket failed: %s", strerror(errno));
126 return 0;
127 }
128 if (setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
129 {
130 DBG1(DBG_CFG, "unable to set SO_REUSEADDR on socket: %s", strerror(errno));
131 close(skt);
132 return 0;
133 }
134
135 /* bind the socket */
136 if (bind(skt, (struct sockaddr *)&addr, addrlen) < 0)
137 {
138 DBG1(DBG_CFG, "unable to bind RADIUS socket: %s", strerror(errno));
139 close(skt);
140 return 0;
141 }
142
143 return skt;
144 }
145
146 /**
147 * Send a RADIUS message to client
148 */
149 static void send_message(private_tnc_pdp_t *this, radius_message_t *message,
150 host_t *client)
151 {
152 int fd;
153 chunk_t data;
154
155 fd = (client->get_family(client) == AF_INET) ? this->ipv4 : this->ipv6;
156 data = message->get_encoding(message);
157
158 DBG2(DBG_CFG, "sending RADIUS packet to %#H", client);
159 DBG3(DBG_CFG, "%B", &data);
160
161 if (sendto(fd, data.ptr, data.len, 0, client->get_sockaddr(client),
162 *client->get_sockaddr_len(client)) != data.len)
163 {
164 DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
165 }
166 }
167
168 /**
169 * Send a RADIUS response for a request
170 */
171 static void send_response(private_tnc_pdp_t *this,
172 radius_message_t *request, radius_message_code_t code,
173 eap_payload_t *eap, host_t *client)
174 {
175 radius_message_t *response;
176 chunk_t data;
177
178 response = radius_message_create(code);
179 if (eap)
180 {
181 data = eap->get_data(eap);
182 response->add(response, RAT_EAP_MESSAGE, data);
183 }
184 response->set_identifier(response, request->get_identifier(request));
185 response->sign(response, request->get_authenticator(request),
186 this->secret, this->hasher, this->signer, NULL, TRUE);
187
188 DBG1(DBG_CFG, "sending RADIUS %N to client '%H'", radius_message_code_names,
189 code, client);
190 send_message(this, response, client);
191 }
192
193 /**
194 * Process EAP message
195 */
196 static void process_eap(private_tnc_pdp_t *this, radius_message_t *request,
197 host_t *source)
198 {
199 enumerator_t *enumerator;
200 eap_payload_t *in, *out = NULL;
201 eap_type_t eap_type;
202 chunk_t data, message = chunk_empty;
203 radius_message_code_t code = RMC_ACCESS_CHALLENGE;
204 u_int32_t eap_vendor;
205 int type;
206
207 enumerator = request->create_enumerator(request);
208 while (enumerator->enumerate(enumerator, &type, &data))
209 {
210 if (type == RAT_EAP_MESSAGE && data.len)
211 {
212 message = chunk_cat("mc", message, data);
213 }
214 }
215 enumerator->destroy(enumerator);
216
217 if (message.len)
218 {
219 in = eap_payload_create_data(message);
220
221 /* apply EAP method selected by RADIUS server */
222 eap_type = in->get_type(in, &eap_vendor);
223
224 DBG3(DBG_CFG, "%N payload %B", eap_type_names, eap_type, &message);
225 free(message.ptr);
226
227 if (eap_type == EAP_IDENTITY)
228 {
229 identification_t *server, *peer;
230 chunk_t eap_identity;
231
232 if (message.len < 5)
233 {
234 return;
235 }
236 eap_identity = chunk_create(message.ptr + 5, message.len - 5);
237 peer = identification_create_from_data(eap_identity);
238 server = identification_create_from_string("%any");
239
240 this->method = charon->eap->create_instance(charon->eap, EAP_MD5, 0,
241 EAP_SERVER, server, peer);
242 if (!this->method)
243 {
244 peer->destroy(peer);
245 server->destroy(server);
246 in->destroy(in);
247 return;
248 }
249 this->method->initiate(this->method, &out);
250 }
251 else
252 {
253 switch (this->method->process(this->method, in, &out))
254 {
255 case NEED_MORE:
256 code = RMC_ACCESS_CHALLENGE;
257 break;
258 case SUCCESS:
259 code = RMC_ACCESS_ACCEPT;
260 DESTROY_IF(out);
261 out = eap_payload_create_code(EAP_SUCCESS,
262 in->get_identifier(in));
263 break;
264 case FAILED:
265 default:
266 code = RMC_ACCESS_REJECT;
267 DESTROY_IF(out);
268 out = eap_payload_create_code(EAP_FAILURE,
269 in->get_identifier(in));
270 }
271 }
272
273 send_response(this, request, code, out, source);
274 in->destroy(in);
275 out->destroy(out);
276 }
277 }
278
279 /**
280 * Process packets received on the RADIUS socket
281 */
282 static job_requeue_t receive(private_tnc_pdp_t *this)
283 {
284 while (TRUE)
285 {
286 radius_message_t *request;
287 char buffer[MAX_PACKET];
288 int max_fd = 0, selected = 0, bytes_read = 0;
289 fd_set rfds;
290 bool oldstate;
291 host_t *source;
292 struct msghdr msg;
293 struct iovec iov;
294 union {
295 struct sockaddr_in in4;
296 struct sockaddr_in6 in6;
297 } src;
298
299 FD_ZERO(&rfds);
300
301 if (this->ipv4)
302 {
303 FD_SET(this->ipv4, &rfds);
304 }
305 if (this->ipv6)
306 {
307 FD_SET(this->ipv6, &rfds);
308 }
309 max_fd = max(this->ipv4, this->ipv6);
310
311 DBG2(DBG_CFG, "waiting for data on RADIUS sockets");
312 oldstate = thread_cancelability(TRUE);
313 if (select(max_fd + 1, &rfds, NULL, NULL, NULL) <= 0)
314 {
315 thread_cancelability(oldstate);
316 continue;
317 }
318 thread_cancelability(oldstate);
319
320 if (FD_ISSET(this->ipv4, &rfds))
321 {
322 selected = this->ipv4;
323 }
324 else if (FD_ISSET(this->ipv6, &rfds))
325 {
326 selected = this->ipv6;
327 }
328 else
329 {
330 /* oops, shouldn't happen */
331 continue;
332 }
333
334 /* read received packet */
335 msg.msg_name = &src;
336 msg.msg_namelen = sizeof(src);
337 iov.iov_base = buffer;
338 iov.iov_len = MAX_PACKET;
339 msg.msg_iov = &iov;
340 msg.msg_iovlen = 1;
341 msg.msg_flags = 0;
342
343 bytes_read = recvmsg(selected, &msg, 0);
344 if (bytes_read < 0)
345 {
346 DBG1(DBG_CFG, "error reading RADIUS socket: %s", strerror(errno));
347 continue;
348 }
349 if (msg.msg_flags & MSG_TRUNC)
350 {
351 DBG1(DBG_CFG, "receive buffer too small, RADIUS packet discarded");
352 continue;
353 }
354 source = host_create_from_sockaddr((sockaddr_t*)&src);
355 DBG2(DBG_CFG, "received RADIUS packet from %#H", source);
356 DBG3(DBG_CFG, "%b", buffer, bytes_read);
357 request = radius_message_parse(chunk_create(buffer, bytes_read));
358 if (request)
359 {
360 DBG1(DBG_CFG, "received RADIUS %N from client '%H'",
361 radius_message_code_names, request->get_code(request), source);
362
363 if (request->verify(request, NULL, this->secret, this->hasher,
364 this->signer))
365 {
366 process_eap(this, request, source);
367 }
368 request->destroy(request);
369
370 }
371 else
372 {
373 DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
374 }
375 source->destroy(source);
376 }
377 return JOB_REQUEUE_FAIR;
378 }
379
380 METHOD(tnc_pdp_t, destroy, void,
381 private_tnc_pdp_t *this)
382 {
383 if (this->job)
384 {
385 this->job->cancel(this->job);
386 }
387 if (this->ipv4)
388 {
389 close(this->ipv4);
390 }
391 if (this->ipv6)
392 {
393 close(this->ipv6);
394 }
395 DESTROY_IF(this->signer);
396 DESTROY_IF(this->hasher);
397 DESTROY_IF(this->method);
398 free(this);
399 }
400
401 /*
402 * see header file
403 */
404 tnc_pdp_t *tnc_pdp_create(u_int16_t port)
405 {
406 private_tnc_pdp_t *this;
407 char *secret;
408
409 INIT(this,
410 .public = {
411 .destroy = _destroy,
412 },
413 .ipv4 = open_socket(this, AF_INET, port),
414 .ipv6 = open_socket(this, AF_INET6, port),
415 .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
416 .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
417 );
418
419 if (!this->ipv4 && !this->ipv6)
420 {
421 DBG1(DBG_NET, "couldd not create any RADIUS sockets");
422 destroy(this);
423 return NULL;
424 }
425 if (!this->ipv4)
426 {
427 DBG1(DBG_NET, "could not open IPv4 RADIUS socket, IPv4 disabled");
428 }
429 if (!this->ipv6)
430 {
431 DBG1(DBG_NET, "could not open IPv6 RADIUS socket, IPv6 disabled");
432 }
433 if (!this->hasher || !this->signer)
434 {
435 destroy(this);
436 return NULL;
437 }
438 secret = lib->settings->get_str(lib->settings,
439 "charon.plugins.tnc-pdp.secret", NULL);
440 if (!secret)
441 {
442 DBG1(DBG_CFG, "missing RADIUS secret, PDP disabled");
443 destroy(this);
444 return NULL;
445 }
446 this->secret = chunk_create(secret, strlen(secret));
447 this->signer->set_key(this->signer, this->secret);
448
449 this->job = callback_job_create_with_prio((callback_job_cb_t)receive,
450 this, NULL, NULL, JOB_PRIO_CRITICAL);
451 lib->processor->queue_job(lib->processor, (job_t*)this->job);
452
453 return &this->public;
454 }
455