ad5daa54bedca31e625820b327cf89777a424a65
[strongswan.git] / src / libradius / radius_socket.c
1 /*
2 * Copyright (C) 2010 Martin Willi
3 * Copyright (C) 2010 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 "radius_socket.h"
17 #include "radius_mppe.h"
18
19 #include <errno.h>
20 #include <unistd.h>
21
22 #include <pen/pen.h>
23 #include <utils/debug.h>
24
25 typedef struct private_radius_socket_t private_radius_socket_t;
26
27 /**
28 * Private data of an radius_socket_t object.
29 */
30 struct private_radius_socket_t {
31
32 /**
33 * Public radius_socket_t interface.
34 */
35 radius_socket_t public;
36
37 /**
38 * Server port for authentication
39 */
40 u_int16_t auth_port;
41
42 /**
43 * socket file descriptor for authentication
44 */
45 int auth_fd;
46
47 /**
48 * Server port for accounting
49 */
50 u_int16_t acct_port;
51
52 /**
53 * socket file descriptor for accounting
54 */
55 int acct_fd;
56
57 /**
58 * Server address
59 */
60 char *address;
61
62 /**
63 * current RADIUS identifier
64 */
65 u_int8_t identifier;
66
67 /**
68 * hasher to use for response verification
69 */
70 hasher_t *hasher;
71
72 /**
73 * HMAC-MD5 signer to build Message-Authenticator attribute
74 */
75 signer_t *signer;
76
77 /**
78 * random number generator for RADIUS request authenticator
79 */
80 rng_t *rng;
81
82 /**
83 * RADIUS secret
84 */
85 chunk_t secret;
86 };
87
88 /**
89 * Check or establish RADIUS connection
90 */
91 static bool check_connection(private_radius_socket_t *this,
92 int *fd, u_int16_t port)
93 {
94 if (*fd == -1)
95 {
96 host_t *server;
97
98 server = host_create_from_dns(this->address, AF_UNSPEC, port);
99 if (!server)
100 {
101 DBG1(DBG_CFG, "resolving RADIUS server address '%s' failed",
102 this->address);
103 return FALSE;
104 }
105 *fd = socket(server->get_family(server), SOCK_DGRAM, IPPROTO_UDP);
106 if (*fd == -1)
107 {
108 DBG1(DBG_CFG, "opening RADIUS socket for %#H failed: %s",
109 server, strerror(errno));
110 server->destroy(server);
111 return FALSE;
112 }
113 if (connect(*fd, server->get_sockaddr(server),
114 *server->get_sockaddr_len(server)) < 0)
115 {
116 DBG1(DBG_CFG, "connecting RADIUS socket to %#H failed: %s",
117 server, strerror(errno));
118 server->destroy(server);
119 close(*fd);
120 *fd = -1;
121 return FALSE;
122 }
123 server->destroy(server);
124 }
125 return TRUE;
126 }
127
128 /**
129 * Receive the response to the message with the given ID
130 */
131 static status_t receive_response(int fd, int timeout, u_int8_t id,
132 radius_message_t **response)
133 {
134 radius_message_t *msg;
135 char buf[4096];
136 int res;
137 struct pollfd pfd = {
138 .fd = fd,
139 .events = POLLIN,
140 };
141
142 while (TRUE)
143 {
144 res = poll(&pfd, 1, timeout);
145 if (res < 0)
146 {
147 DBG1(DBG_CFG, "waiting for RADIUS message failed: %s",
148 strerror(errno));
149 return FAILED;
150 }
151 if (res == 0)
152 { /* timeout */
153 return OUT_OF_RES;
154 }
155 res = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
156 if (res <= 0)
157 {
158 DBG1(DBG_CFG, "receiving RADIUS message failed: %s",
159 strerror(errno));
160 return FAILED;
161 }
162 msg = radius_message_parse(chunk_create(buf, res));
163 if (!msg)
164 {
165 DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
166 return FAILED;
167 }
168 if (id != msg->get_identifier(msg))
169 {
170 /* we haven't received the response to our current request, but
171 * perhaps one for an earlier request for which we didn't wait
172 * long enough */
173 DBG1(DBG_CFG, "received RADIUS message with unexpected ID %d "
174 "[%d expected], ignored", msg->get_identifier(msg), id);
175 msg->destroy(msg);
176 continue;
177 }
178 *response = msg;
179 return SUCCESS;
180 }
181 }
182
183 METHOD(radius_socket_t, request, radius_message_t*,
184 private_radius_socket_t *this, radius_message_t *request)
185 {
186 radius_message_t *response;
187 chunk_t data;
188 int i, *fd, retransmit = 0;
189 u_int16_t port;
190 rng_t *rng = NULL;
191
192 if (request->get_code(request) == RMC_ACCOUNTING_REQUEST)
193 {
194 fd = &this->acct_fd;
195 port = this->acct_port;
196 }
197 else
198 {
199 fd = &this->auth_fd;
200 port = this->auth_port;
201 rng = this->rng;
202 }
203
204 /* set Message Identifier */
205 request->set_identifier(request, this->identifier++);
206 /* sign the request */
207 if (!request->sign(request, NULL, this->secret, this->hasher, this->signer,
208 rng, rng != NULL))
209 {
210 return NULL;
211 }
212
213 if (!check_connection(this, fd, port))
214 {
215 return NULL;
216 }
217
218 data = request->get_encoding(request);
219 DBG3(DBG_CFG, "%B", &data);
220
221 /* timeout after 2, 3, 4, 5 seconds */
222 for (i = 2; i <= 5; i++)
223 {
224 if (retransmit)
225 {
226 DBG1(DBG_CFG, "retransmitting RADIUS %N (attempt %d)",
227 radius_message_code_names, request->get_code(request),
228 retransmit);
229 }
230 if (send(*fd, data.ptr, data.len, 0) != data.len)
231 {
232 DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
233 return NULL;
234 }
235 switch (receive_response(*fd, i*1000, request->get_identifier(request),
236 &response))
237 {
238 case SUCCESS:
239 break;
240 case OUT_OF_RES:
241 retransmit++;
242 continue;
243 default:
244 return NULL;
245 }
246 if (response->verify(response, request->get_authenticator(request),
247 this->secret, this->hasher, this->signer))
248 {
249 return response;
250 }
251 response->destroy(response);
252 return NULL;
253 }
254 DBG1(DBG_CFG, "RADIUS %N timed out after %d retransmits",
255 radius_message_code_names, request->get_code(request), retransmit - 1);
256 return NULL;
257 }
258
259 /**
260 * Decrypt a MS-MPPE-Send/Recv-Key
261 */
262 static chunk_t decrypt_mppe_key(private_radius_socket_t *this, u_int16_t salt,
263 chunk_t C, radius_message_t *request)
264 {
265 chunk_t decrypted;
266
267 decrypted = chunk_alloca(C.len);
268 if (!request->crypt(request, chunk_from_thing(salt), C, decrypted,
269 this->secret, this->hasher) ||
270 decrypted.ptr[0] >= decrypted.len)
271 { /* decryption failed? */
272 return chunk_empty;
273 }
274 /* remove truncation, first byte is key length */
275 return chunk_clone(chunk_create(decrypted.ptr + 1, decrypted.ptr[0]));
276 }
277
278 METHOD(radius_socket_t, decrypt_msk, chunk_t,
279 private_radius_socket_t *this, radius_message_t *request,
280 radius_message_t *response)
281 {
282 mppe_key_t *mppe_key;
283 enumerator_t *enumerator;
284 chunk_t data, send = chunk_empty, recv = chunk_empty;
285 int type;
286
287 enumerator = response->create_enumerator(response);
288 while (enumerator->enumerate(enumerator, &type, &data))
289 {
290 if (type == RAT_VENDOR_SPECIFIC && data.len > sizeof(mppe_key_t))
291 {
292 mppe_key = (mppe_key_t*)data.ptr;
293 if (ntohl(mppe_key->id) == PEN_MICROSOFT &&
294 mppe_key->length == data.len - sizeof(mppe_key->id))
295 {
296 data = chunk_create(mppe_key->key, data.len - sizeof(mppe_key_t));
297 if (mppe_key->type == MS_MPPE_SEND_KEY)
298 {
299 send = decrypt_mppe_key(this, mppe_key->salt, data, request);
300 }
301 if (mppe_key->type == MS_MPPE_RECV_KEY)
302 {
303 recv = decrypt_mppe_key(this, mppe_key->salt, data, request);
304 }
305 }
306 }
307 }
308 enumerator->destroy(enumerator);
309 if (send.ptr && recv.ptr)
310 {
311 return chunk_cat("mm", recv, send);
312 }
313 chunk_clear(&send);
314 chunk_clear(&recv);
315 return chunk_empty;
316 }
317
318 METHOD(radius_socket_t, destroy, void,
319 private_radius_socket_t *this)
320 {
321 DESTROY_IF(this->hasher);
322 DESTROY_IF(this->signer);
323 DESTROY_IF(this->rng);
324 if (this->auth_fd != -1)
325 {
326 close(this->auth_fd);
327 };
328 if (this->acct_fd != -1)
329 {
330 close(this->acct_fd);
331 }
332 free(this);
333 }
334
335 /**
336 * See header
337 */
338 radius_socket_t *radius_socket_create(char *address, u_int16_t auth_port,
339 u_int16_t acct_port, chunk_t secret)
340 {
341 private_radius_socket_t *this;
342
343 INIT(this,
344 .public = {
345 .request = _request,
346 .decrypt_msk = _decrypt_msk,
347 .destroy = _destroy,
348 },
349 .address = address,
350 .auth_port = auth_port,
351 .auth_fd = -1,
352 .acct_port = acct_port,
353 .acct_fd = -1,
354 .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
355 .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
356 .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK),
357 );
358
359 if (!this->hasher || !this->signer || !this->rng ||
360 !this->signer->set_key(this->signer, secret))
361 {
362 DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required");
363 destroy(this);
364 return NULL;
365 }
366 this->secret = secret;
367 /* we use a random identifier, helps if we restart often */
368 this->identifier = random();
369
370 return &this->public;
371 }