Add a return value to radius_message_t.sign()
[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 <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 METHOD(radius_socket_t, request, radius_message_t*,
129 private_radius_socket_t *this, radius_message_t *request)
130 {
131 chunk_t data;
132 int i, *fd;
133 u_int16_t port;
134 rng_t *rng = NULL;
135
136 if (request->get_code(request) == RMC_ACCOUNTING_REQUEST)
137 {
138 fd = &this->acct_fd;
139 port = this->acct_port;
140 }
141 else
142 {
143 fd = &this->auth_fd;
144 port = this->auth_port;
145 rng = this->rng;
146 }
147
148 /* set Message Identifier */
149 request->set_identifier(request, this->identifier++);
150 /* sign the request */
151 if (!request->sign(request, NULL, this->secret, this->hasher, this->signer,
152 rng, rng != NULL))
153 {
154 return NULL;
155 }
156
157 if (!check_connection(this, fd, port))
158 {
159 return NULL;
160 }
161
162 data = request->get_encoding(request);
163 DBG3(DBG_CFG, "%B", &data);
164
165 /* timeout after 2, 3, 4, 5 seconds */
166 for (i = 2; i <= 5; i++)
167 {
168 radius_message_t *response;
169 bool retransmit = FALSE;
170 struct timeval tv;
171 char buf[4096];
172 fd_set fds;
173 int res;
174
175 if (send(*fd, data.ptr, data.len, 0) != data.len)
176 {
177 DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
178 return NULL;
179 }
180 tv.tv_sec = i;
181 tv.tv_usec = 0;
182
183 while (TRUE)
184 {
185 FD_ZERO(&fds);
186 FD_SET(*fd, &fds);
187 res = select((*fd) + 1, &fds, NULL, NULL, &tv);
188 /* TODO: updated tv to time not waited. Linux does this for us. */
189 if (res < 0)
190 { /* failed */
191 DBG1(DBG_CFG, "waiting for RADIUS message failed: %s",
192 strerror(errno));
193 break;
194 }
195 if (res == 0)
196 { /* timeout */
197 DBG1(DBG_CFG, "retransmitting RADIUS message");
198 retransmit = TRUE;
199 break;
200 }
201 res = recv(*fd, buf, sizeof(buf), MSG_DONTWAIT);
202 if (res <= 0)
203 {
204 DBG1(DBG_CFG, "receiving RADIUS message failed: %s",
205 strerror(errno));
206 break;
207 }
208 response = radius_message_parse(chunk_create(buf, res));
209 if (response)
210 {
211 if (response->verify(response,
212 request->get_authenticator(request), this->secret,
213 this->hasher, this->signer))
214 {
215 return response;
216 }
217 response->destroy(response);
218 }
219 DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
220 }
221 if (!retransmit)
222 {
223 break;
224 }
225 }
226 DBG1(DBG_CFG, "RADIUS server is not responding");
227 return NULL;
228 }
229
230 /**
231 * Decrypt a MS-MPPE-Send/Recv-Key
232 */
233 static chunk_t decrypt_mppe_key(private_radius_socket_t *this, u_int16_t salt,
234 chunk_t C, radius_message_t *request)
235 {
236 chunk_t A, R, P, seed;
237 u_char *c, *p;
238
239 /**
240 * From RFC2548 (encryption):
241 * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
242 * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
243 * . . .
244 * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
245 */
246
247 if (C.len % HASH_SIZE_MD5 || C.len < HASH_SIZE_MD5)
248 {
249 return chunk_empty;
250 }
251
252 A = chunk_create((u_char*)&salt, sizeof(salt));
253 R = chunk_create(request->get_authenticator(request), HASH_SIZE_MD5);
254 P = chunk_alloca(C.len);
255 p = P.ptr;
256 c = C.ptr;
257
258 seed = chunk_cata("cc", R, A);
259
260 while (c < C.ptr + C.len)
261 {
262 /* b(i) = MD5(S + c(i-1)) */
263 this->hasher->get_hash(this->hasher, this->secret, NULL);
264 this->hasher->get_hash(this->hasher, seed, p);
265
266 /* p(i) = b(i) xor c(1) */
267 memxor(p, c, HASH_SIZE_MD5);
268
269 /* prepare next round */
270 seed = chunk_create(c, HASH_SIZE_MD5);
271 c += HASH_SIZE_MD5;
272 p += HASH_SIZE_MD5;
273 }
274
275 /* remove truncation, first byte is key length */
276 if (*P.ptr >= P.len)
277 { /* decryption failed? */
278 return chunk_empty;
279 }
280 return chunk_clone(chunk_create(P.ptr + 1, *P.ptr));
281 }
282
283 METHOD(radius_socket_t, decrypt_msk, chunk_t,
284 private_radius_socket_t *this, radius_message_t *request,
285 radius_message_t *response)
286 {
287 mppe_key_t *mppe_key;
288 enumerator_t *enumerator;
289 chunk_t data, send = chunk_empty, recv = chunk_empty;
290 int type;
291
292 enumerator = response->create_enumerator(response);
293 while (enumerator->enumerate(enumerator, &type, &data))
294 {
295 if (type == RAT_VENDOR_SPECIFIC && data.len > sizeof(mppe_key_t))
296 {
297 mppe_key = (mppe_key_t*)data.ptr;
298 if (ntohl(mppe_key->id) == PEN_MICROSOFT &&
299 mppe_key->length == data.len - sizeof(mppe_key->id))
300 {
301 data = chunk_create(mppe_key->key, data.len - sizeof(mppe_key_t));
302 if (mppe_key->type == MS_MPPE_SEND_KEY)
303 {
304 send = decrypt_mppe_key(this, mppe_key->salt, data, request);
305 }
306 if (mppe_key->type == MS_MPPE_RECV_KEY)
307 {
308 recv = decrypt_mppe_key(this, mppe_key->salt, data, request);
309 }
310 }
311 }
312 }
313 enumerator->destroy(enumerator);
314 if (send.ptr && recv.ptr)
315 {
316 return chunk_cat("mm", recv, send);
317 }
318 chunk_clear(&send);
319 chunk_clear(&recv);
320 return chunk_empty;
321 }
322
323 METHOD(radius_socket_t, destroy, void,
324 private_radius_socket_t *this)
325 {
326 DESTROY_IF(this->hasher);
327 DESTROY_IF(this->signer);
328 DESTROY_IF(this->rng);
329 if (this->auth_fd != -1)
330 {
331 close(this->auth_fd);
332 };
333 if (this->acct_fd != -1)
334 {
335 close(this->acct_fd);
336 }
337 free(this);
338 }
339
340 /**
341 * See header
342 */
343 radius_socket_t *radius_socket_create(char *address, u_int16_t auth_port,
344 u_int16_t acct_port, chunk_t secret)
345 {
346 private_radius_socket_t *this;
347
348 INIT(this,
349 .public = {
350 .request = _request,
351 .decrypt_msk = _decrypt_msk,
352 .destroy = _destroy,
353 },
354 .address = address,
355 .auth_port = auth_port,
356 .auth_fd = -1,
357 .acct_port = acct_port,
358 .acct_fd = -1,
359 .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
360 .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
361 .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK),
362 );
363
364 if (!this->hasher || !this->signer || !this->rng)
365 {
366 DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required");
367 destroy(this);
368 return NULL;
369 }
370 this->secret = secret;
371 this->signer->set_key(this->signer, secret);
372 /* we use a random identifier, helps if we restart often */
373 this->identifier = random();
374
375 return &this->public;
376 }