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"
24 * Vendor-Id of Microsoft specific attributes
26 #define VENDOR_ID_MICROSOFT 311
29 * Microsoft specific vendor attributes
31 #define MS_MPPE_SEND_KEY 16
32 #define MS_MPPE_RECV_KEY 17
34 typedef struct private_radius_socket_t private_radius_socket_t
;
37 * Private data of an radius_socket_t object.
39 struct private_radius_socket_t
{
42 * Public radius_socket_t interface.
44 radius_socket_t
public;
47 * Server port for authentication
52 * socket file descriptor for authentication
57 * Server port for accounting
62 * socket file descriptor for accounting
72 * current RADIUS identifier
77 * hasher to use for response verification
82 * HMAC-MD5 signer to build Message-Authenticator attribute
87 * random number generator for RADIUS request authenticator
98 * Check or establish RADIUS connection
100 static bool check_connection(private_radius_socket_t
*this,
101 int *fd
, u_int16_t port
)
107 server
= host_create_from_dns(this->address
, AF_UNSPEC
, port
);
110 DBG1(DBG_CFG
, "resolving RADIUS server address '%s' failed",
114 *fd
= socket(server
->get_family(server
), SOCK_DGRAM
, IPPROTO_UDP
);
117 DBG1(DBG_CFG
, "opening RADIUS socket for %#H failed: %s",
118 server
, strerror(errno
));
119 server
->destroy(server
);
122 if (connect(*fd
, server
->get_sockaddr(server
),
123 *server
->get_sockaddr_len(server
)) < 0)
125 DBG1(DBG_CFG
, "connecting RADIUS socket to %#H failed: %s",
126 server
, strerror(errno
));
127 server
->destroy(server
);
132 server
->destroy(server
);
137 METHOD(radius_socket_t
, request
, radius_message_t
*,
138 private_radius_socket_t
*this, radius_message_t
*request
)
145 if (request
->get_code(request
) == RMC_ACCOUNTING_REQUEST
)
148 port
= this->acct_port
;
153 port
= this->auth_port
;
157 /* set Message Identifier */
158 request
->set_identifier(request
, this->identifier
++);
159 /* sign the request */
160 request
->sign(request
, NULL
, this->secret
, this->hasher
, this->signer
, rng
);
162 if (!check_connection(this, fd
, port
))
167 data
= request
->get_encoding(request
);
168 /* timeout after 2, 3, 4, 5 seconds */
169 for (i
= 2; i
<= 5; i
++)
171 radius_message_t
*response
;
172 bool retransmit
= FALSE
;
178 if (send(*fd
, data
.ptr
, data
.len
, 0) != data
.len
)
180 DBG1(DBG_CFG
, "sending RADIUS message failed: %s", strerror(errno
));
190 res
= select((*fd
) + 1, &fds
, NULL
, NULL
, &tv
);
191 /* TODO: updated tv to time not waited. Linux does this for us. */
194 DBG1(DBG_CFG
, "waiting for RADIUS message failed: %s",
200 DBG1(DBG_CFG
, "retransmitting RADIUS message");
204 res
= recv(*fd
, buf
, sizeof(buf
), MSG_DONTWAIT
);
207 DBG1(DBG_CFG
, "receiving RADIUS message failed: %s",
211 response
= radius_message_parse(chunk_create(buf
, res
));
214 if (response
->verify(response
,
215 request
->get_authenticator(request
), this->secret
,
216 this->hasher
, this->signer
))
220 response
->destroy(response
);
222 DBG1(DBG_CFG
, "received invalid RADIUS message, ignored");
229 DBG1(DBG_CFG
, "RADIUS server is not responding");
234 * Decrypt a MS-MPPE-Send/Recv-Key
236 static chunk_t
decrypt_mppe_key(private_radius_socket_t
*this, u_int16_t salt
,
237 chunk_t C
, radius_message_t
*request
)
239 chunk_t A
, R
, P
, seed
;
243 * From RFC2548 (encryption):
244 * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
245 * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
247 * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
250 if (C
.len
% HASH_SIZE_MD5
|| C
.len
< HASH_SIZE_MD5
)
255 A
= chunk_create((u_char
*)&salt
, sizeof(salt
));
256 R
= chunk_create(request
->get_authenticator(request
), HASH_SIZE_MD5
);
257 P
= chunk_alloca(C
.len
);
261 seed
= chunk_cata("cc", R
, A
);
263 while (c
< C
.ptr
+ C
.len
)
265 /* b(i) = MD5(S + c(i-1)) */
266 this->hasher
->get_hash(this->hasher
, this->secret
, NULL
);
267 this->hasher
->get_hash(this->hasher
, seed
, p
);
269 /* p(i) = b(i) xor c(1) */
270 memxor(p
, c
, HASH_SIZE_MD5
);
272 /* prepare next round */
273 seed
= chunk_create(c
, HASH_SIZE_MD5
);
278 /* remove truncation, first byte is key length */
280 { /* decryption failed? */
283 return chunk_clone(chunk_create(P
.ptr
+ 1, *P
.ptr
));
286 METHOD(radius_socket_t
, decrypt_msk
, chunk_t
,
287 private_radius_socket_t
*this, radius_message_t
*request
,
288 radius_message_t
*response
)
296 } __attribute__((packed
)) *mppe_key
;
297 enumerator_t
*enumerator
;
298 chunk_t data
, send
= chunk_empty
, recv
= chunk_empty
;
301 enumerator
= response
->create_enumerator(response
);
302 while (enumerator
->enumerate(enumerator
, &type
, &data
))
304 if (type
== RAT_VENDOR_SPECIFIC
&&
305 data
.len
> sizeof(*mppe_key
))
307 mppe_key
= (void*)data
.ptr
;
308 if (ntohl(mppe_key
->id
) == VENDOR_ID_MICROSOFT
&&
309 mppe_key
->length
== data
.len
- sizeof(mppe_key
->id
))
311 data
= chunk_create(mppe_key
->key
, data
.len
- sizeof(*mppe_key
));
312 if (mppe_key
->type
== MS_MPPE_SEND_KEY
)
314 send
= decrypt_mppe_key(this, mppe_key
->salt
, data
, request
);
316 if (mppe_key
->type
== MS_MPPE_RECV_KEY
)
318 recv
= decrypt_mppe_key(this, mppe_key
->salt
, data
, request
);
323 enumerator
->destroy(enumerator
);
324 if (send
.ptr
&& recv
.ptr
)
326 return chunk_cat("mm", recv
, send
);
333 METHOD(radius_socket_t
, destroy
, void,
334 private_radius_socket_t
*this)
336 DESTROY_IF(this->hasher
);
337 DESTROY_IF(this->signer
);
338 DESTROY_IF(this->rng
);
339 if (this->auth_fd
!= -1)
341 close(this->auth_fd
);
343 if (this->acct_fd
!= -1)
345 close(this->acct_fd
);
353 radius_socket_t
*radius_socket_create(char *address
, u_int16_t auth_port
,
354 u_int16_t acct_port
, chunk_t secret
)
356 private_radius_socket_t
*this;
361 .decrypt_msk
= _decrypt_msk
,
365 .auth_port
= auth_port
,
367 .acct_port
= acct_port
,
371 this->hasher
= lib
->crypto
->create_hasher(lib
->crypto
, HASH_MD5
);
372 this->signer
= lib
->crypto
->create_signer(lib
->crypto
, AUTH_HMAC_MD5_128
);
373 this->rng
= lib
->crypto
->create_rng(lib
->crypto
, RNG_WEAK
);
374 if (!this->hasher
|| !this->signer
|| !this->rng
)
376 DBG1(DBG_CFG
, "RADIUS initialization failed, HMAC/MD5/RNG required");
380 this->secret
= secret
;
381 this->signer
->set_key(this->signer
, secret
);
382 /* we use a random identifier, helps if we restart often */
383 this->identifier
= random();
385 return &this->public;