2 * Copyright (C) 2010 Martin Willi
3 * Copyright (C) 2010 revosec AG
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>.
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
16 #include "radius_socket.h"
17 #include "radius_mppe.h"
25 typedef struct private_radius_socket_t private_radius_socket_t
;
28 * Private data of an radius_socket_t object.
30 struct private_radius_socket_t
{
33 * Public radius_socket_t interface.
35 radius_socket_t
public;
38 * Server port for authentication
43 * socket file descriptor for authentication
48 * Server port for accounting
53 * socket file descriptor for accounting
63 * current RADIUS identifier
68 * hasher to use for response verification
73 * HMAC-MD5 signer to build Message-Authenticator attribute
78 * random number generator for RADIUS request authenticator
89 * Check or establish RADIUS connection
91 static bool check_connection(private_radius_socket_t
*this,
92 int *fd
, u_int16_t port
)
98 server
= host_create_from_dns(this->address
, AF_UNSPEC
, port
);
101 DBG1(DBG_CFG
, "resolving RADIUS server address '%s' failed",
105 *fd
= socket(server
->get_family(server
), SOCK_DGRAM
, IPPROTO_UDP
);
108 DBG1(DBG_CFG
, "opening RADIUS socket for %#H failed: %s",
109 server
, strerror(errno
));
110 server
->destroy(server
);
113 if (connect(*fd
, server
->get_sockaddr(server
),
114 *server
->get_sockaddr_len(server
)) < 0)
116 DBG1(DBG_CFG
, "connecting RADIUS socket to %#H failed: %s",
117 server
, strerror(errno
));
118 server
->destroy(server
);
123 server
->destroy(server
);
128 METHOD(radius_socket_t
, request
, radius_message_t
*,
129 private_radius_socket_t
*this, radius_message_t
*request
)
136 if (request
->get_code(request
) == RMC_ACCOUNTING_REQUEST
)
139 port
= this->acct_port
;
144 port
= this->auth_port
;
148 /* set Message Identifier */
149 request
->set_identifier(request
, this->identifier
++);
150 /* sign the request */
151 request
->sign(request
, NULL
, this->secret
, this->hasher
, this->signer
,
154 if (!check_connection(this, fd
, port
))
159 data
= request
->get_encoding(request
);
160 DBG3(DBG_CFG
, "%B", &data
);
162 /* timeout after 2, 3, 4, 5 seconds */
163 for (i
= 2; i
<= 5; i
++)
165 radius_message_t
*response
;
166 bool retransmit
= FALSE
;
172 if (send(*fd
, data
.ptr
, data
.len
, 0) != data
.len
)
174 DBG1(DBG_CFG
, "sending RADIUS message failed: %s", strerror(errno
));
184 res
= select((*fd
) + 1, &fds
, NULL
, NULL
, &tv
);
185 /* TODO: updated tv to time not waited. Linux does this for us. */
188 DBG1(DBG_CFG
, "waiting for RADIUS message failed: %s",
194 DBG1(DBG_CFG
, "retransmitting RADIUS message");
198 res
= recv(*fd
, buf
, sizeof(buf
), MSG_DONTWAIT
);
201 DBG1(DBG_CFG
, "receiving RADIUS message failed: %s",
205 response
= radius_message_parse(chunk_create(buf
, res
));
208 if (response
->verify(response
,
209 request
->get_authenticator(request
), this->secret
,
210 this->hasher
, this->signer
))
214 response
->destroy(response
);
216 DBG1(DBG_CFG
, "received invalid RADIUS message, ignored");
223 DBG1(DBG_CFG
, "RADIUS server is not responding");
228 * Decrypt a MS-MPPE-Send/Recv-Key
230 static chunk_t
decrypt_mppe_key(private_radius_socket_t
*this, u_int16_t salt
,
231 chunk_t C
, radius_message_t
*request
)
233 chunk_t A
, R
, P
, seed
;
237 * From RFC2548 (encryption):
238 * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
239 * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
241 * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
244 if (C
.len
% HASH_SIZE_MD5
|| C
.len
< HASH_SIZE_MD5
)
249 A
= chunk_create((u_char
*)&salt
, sizeof(salt
));
250 R
= chunk_create(request
->get_authenticator(request
), HASH_SIZE_MD5
);
251 P
= chunk_alloca(C
.len
);
255 seed
= chunk_cata("cc", R
, A
);
257 while (c
< C
.ptr
+ C
.len
)
259 /* b(i) = MD5(S + c(i-1)) */
260 this->hasher
->get_hash(this->hasher
, this->secret
, NULL
);
261 this->hasher
->get_hash(this->hasher
, seed
, p
);
263 /* p(i) = b(i) xor c(1) */
264 memxor(p
, c
, HASH_SIZE_MD5
);
266 /* prepare next round */
267 seed
= chunk_create(c
, HASH_SIZE_MD5
);
272 /* remove truncation, first byte is key length */
274 { /* decryption failed? */
277 return chunk_clone(chunk_create(P
.ptr
+ 1, *P
.ptr
));
280 METHOD(radius_socket_t
, decrypt_msk
, chunk_t
,
281 private_radius_socket_t
*this, radius_message_t
*request
,
282 radius_message_t
*response
)
284 mppe_key_t
*mppe_key
;
285 enumerator_t
*enumerator
;
286 chunk_t data
, send
= chunk_empty
, recv
= chunk_empty
;
289 enumerator
= response
->create_enumerator(response
);
290 while (enumerator
->enumerate(enumerator
, &type
, &data
))
292 if (type
== RAT_VENDOR_SPECIFIC
&& data
.len
> sizeof(mppe_key_t
))
294 mppe_key
= (mppe_key_t
*)data
.ptr
;
295 if (ntohl(mppe_key
->id
) == PEN_MICROSOFT
&&
296 mppe_key
->length
== data
.len
- sizeof(mppe_key
->id
))
298 data
= chunk_create(mppe_key
->key
, data
.len
- sizeof(mppe_key_t
));
299 if (mppe_key
->type
== MS_MPPE_SEND_KEY
)
301 send
= decrypt_mppe_key(this, mppe_key
->salt
, data
, request
);
303 if (mppe_key
->type
== MS_MPPE_RECV_KEY
)
305 recv
= decrypt_mppe_key(this, mppe_key
->salt
, data
, request
);
310 enumerator
->destroy(enumerator
);
311 if (send
.ptr
&& recv
.ptr
)
313 return chunk_cat("mm", recv
, send
);
320 METHOD(radius_socket_t
, destroy
, void,
321 private_radius_socket_t
*this)
323 DESTROY_IF(this->hasher
);
324 DESTROY_IF(this->signer
);
325 DESTROY_IF(this->rng
);
326 if (this->auth_fd
!= -1)
328 close(this->auth_fd
);
330 if (this->acct_fd
!= -1)
332 close(this->acct_fd
);
340 radius_socket_t
*radius_socket_create(char *address
, u_int16_t auth_port
,
341 u_int16_t acct_port
, chunk_t secret
)
343 private_radius_socket_t
*this;
348 .decrypt_msk
= _decrypt_msk
,
352 .auth_port
= auth_port
,
354 .acct_port
= acct_port
,
356 .hasher
= lib
->crypto
->create_hasher(lib
->crypto
, HASH_MD5
),
357 .signer
= lib
->crypto
->create_signer(lib
->crypto
, AUTH_HMAC_MD5_128
),
358 .rng
= lib
->crypto
->create_rng(lib
->crypto
, RNG_WEAK
),
361 if (!this->hasher
|| !this->signer
|| !this->rng
)
363 DBG1(DBG_CFG
, "RADIUS initialization failed, HMAC/MD5/RNG required");
367 this->secret
= secret
;
368 this->signer
->set_key(this->signer
, secret
);
369 /* we use a random identifier, helps if we restart often */
370 this->identifier
= random();
372 return &this->public;