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
,
163 if (!check_connection(this, fd
, port
))
168 data
= request
->get_encoding(request
);
169 DBG3(DBG_CFG
, "%B", &data
);
171 /* timeout after 2, 3, 4, 5 seconds */
172 for (i
= 2; i
<= 5; i
++)
174 radius_message_t
*response
;
175 bool retransmit
= FALSE
;
181 if (send(*fd
, data
.ptr
, data
.len
, 0) != data
.len
)
183 DBG1(DBG_CFG
, "sending RADIUS message failed: %s", strerror(errno
));
193 res
= select((*fd
) + 1, &fds
, NULL
, NULL
, &tv
);
194 /* TODO: updated tv to time not waited. Linux does this for us. */
197 DBG1(DBG_CFG
, "waiting for RADIUS message failed: %s",
203 DBG1(DBG_CFG
, "retransmitting RADIUS message");
207 res
= recv(*fd
, buf
, sizeof(buf
), MSG_DONTWAIT
);
210 DBG1(DBG_CFG
, "receiving RADIUS message failed: %s",
214 response
= radius_message_parse(chunk_create(buf
, res
));
217 if (response
->verify(response
,
218 request
->get_authenticator(request
), this->secret
,
219 this->hasher
, this->signer
))
223 response
->destroy(response
);
225 DBG1(DBG_CFG
, "received invalid RADIUS message, ignored");
232 DBG1(DBG_CFG
, "RADIUS server is not responding");
237 * Decrypt a MS-MPPE-Send/Recv-Key
239 static chunk_t
decrypt_mppe_key(private_radius_socket_t
*this, u_int16_t salt
,
240 chunk_t C
, radius_message_t
*request
)
242 chunk_t A
, R
, P
, seed
;
246 * From RFC2548 (encryption):
247 * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1)
248 * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2)
250 * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i)
253 if (C
.len
% HASH_SIZE_MD5
|| C
.len
< HASH_SIZE_MD5
)
258 A
= chunk_create((u_char
*)&salt
, sizeof(salt
));
259 R
= chunk_create(request
->get_authenticator(request
), HASH_SIZE_MD5
);
260 P
= chunk_alloca(C
.len
);
264 seed
= chunk_cata("cc", R
, A
);
266 while (c
< C
.ptr
+ C
.len
)
268 /* b(i) = MD5(S + c(i-1)) */
269 this->hasher
->get_hash(this->hasher
, this->secret
, NULL
);
270 this->hasher
->get_hash(this->hasher
, seed
, p
);
272 /* p(i) = b(i) xor c(1) */
273 memxor(p
, c
, HASH_SIZE_MD5
);
275 /* prepare next round */
276 seed
= chunk_create(c
, HASH_SIZE_MD5
);
281 /* remove truncation, first byte is key length */
283 { /* decryption failed? */
286 return chunk_clone(chunk_create(P
.ptr
+ 1, *P
.ptr
));
289 METHOD(radius_socket_t
, decrypt_msk
, chunk_t
,
290 private_radius_socket_t
*this, radius_message_t
*request
,
291 radius_message_t
*response
)
299 } __attribute__((packed
)) *mppe_key
;
300 enumerator_t
*enumerator
;
301 chunk_t data
, send
= chunk_empty
, recv
= chunk_empty
;
304 enumerator
= response
->create_enumerator(response
);
305 while (enumerator
->enumerate(enumerator
, &type
, &data
))
307 if (type
== RAT_VENDOR_SPECIFIC
&&
308 data
.len
> sizeof(*mppe_key
))
310 mppe_key
= (void*)data
.ptr
;
311 if (ntohl(mppe_key
->id
) == VENDOR_ID_MICROSOFT
&&
312 mppe_key
->length
== data
.len
- sizeof(mppe_key
->id
))
314 data
= chunk_create(mppe_key
->key
, data
.len
- sizeof(*mppe_key
));
315 if (mppe_key
->type
== MS_MPPE_SEND_KEY
)
317 send
= decrypt_mppe_key(this, mppe_key
->salt
, data
, request
);
319 if (mppe_key
->type
== MS_MPPE_RECV_KEY
)
321 recv
= decrypt_mppe_key(this, mppe_key
->salt
, data
, request
);
326 enumerator
->destroy(enumerator
);
327 if (send
.ptr
&& recv
.ptr
)
329 return chunk_cat("mm", recv
, send
);
336 METHOD(radius_socket_t
, destroy
, void,
337 private_radius_socket_t
*this)
339 DESTROY_IF(this->hasher
);
340 DESTROY_IF(this->signer
);
341 DESTROY_IF(this->rng
);
342 if (this->auth_fd
!= -1)
344 close(this->auth_fd
);
346 if (this->acct_fd
!= -1)
348 close(this->acct_fd
);
356 radius_socket_t
*radius_socket_create(char *address
, u_int16_t auth_port
,
357 u_int16_t acct_port
, chunk_t secret
)
359 private_radius_socket_t
*this;
364 .decrypt_msk
= _decrypt_msk
,
368 .auth_port
= auth_port
,
370 .acct_port
= acct_port
,
374 this->hasher
= lib
->crypto
->create_hasher(lib
->crypto
, HASH_MD5
);
375 this->signer
= lib
->crypto
->create_signer(lib
->crypto
, AUTH_HMAC_MD5_128
);
376 this->rng
= lib
->crypto
->create_rng(lib
->crypto
, RNG_WEAK
);
377 if (!this->hasher
|| !this->signer
|| !this->rng
)
379 DBG1(DBG_CFG
, "RADIUS initialization failed, HMAC/MD5/RNG required");
383 this->secret
= secret
;
384 this->signer
->set_key(this->signer
, secret
);
385 /* we use a random identifier, helps if we restart often */
386 this->identifier
= random();
388 return &this->public;