The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
session.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 1c9ea4ff54994cfbfa3cac55f0ccc522df8b8df1 $
19 * @file lib/eap/session.h
20 * @brief EAP session management.
21 *
22 * @copyright 2019 The FreeRADIUS server project
23 */
24#include <freeradius-devel/server/pair.h>
25#include <freeradius-devel/radius/radius.h>
26#include <freeradius-devel/util/nbo.h>
27
28#include "attrs.h"
29#include "compose.h"
30
31static int _eap_session_free(eap_session_t *eap_session)
32{
33 request_t *request = eap_session->request;
34
35 if (eap_session->identity) {
36 talloc_free(eap_session->identity);
37 eap_session->identity = NULL;
38 }
39
40#ifdef WITH_VERIFY_PTR
41 if (eap_session->prev_round) (void)fr_cond_assert(talloc_parent(eap_session->prev_round) == eap_session);
42 if (eap_session->this_round) (void)fr_cond_assert(talloc_parent(eap_session->this_round) == eap_session);
43#endif
44
45 /*
46 * Give helpful debug messages if:
47 *
48 * we're debugging TLS sessions, which don't finish,
49 * and which aren't deleted early due to a likely RADIUS
50 * retransmit which nukes our ID, and therefore our state.
51 */
52 if (((request && RDEBUG_ENABLED) || (!request && DEBUG_ENABLED)) &&
53 (eap_session->tls && !eap_session->finished && fr_time_delta_gt(fr_time_sub(fr_time(), eap_session->updated), fr_time_delta_from_sec(3)))) {
54 ROPTIONAL(RWDEBUG, WARN, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
55 ROPTIONAL(RWDEBUG, WARN, "!! EAP session %016" PRIxPTR " did not finish! !!",
56 (uintptr_t)eap_session);
57 ROPTIONAL(RWDEBUG, WARN, "!! See https://www.freeradius.org/documentation/freeradius-server/4.0.0/trouble-shooting/client.html !!");
58 ROPTIONAL(RWDEBUG, WARN, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
59 }
60
61 ROPTIONAL(RDEBUG4, DEBUG4, "Freeing eap_session_t %p", eap_session);
62
63 /*
64 * Frozen state...
65 */
66 if (!request) return 0;
67
68 /*
69 * Thawed state
70 */
71#ifndef NDEBUG
72 {
73 eap_session_t *in_request;
74
76
77 /*
78 * Additional sanity check. Either there's no eap_session
79 * associated with the request, or it matches the one we're
80 * about to free.
81 */
82 fr_assert(!in_request || (eap_session == in_request));
83 }
84#else
86#endif
87
88 return 0;
89}
90
91/** Allocate a new eap_session_t
92 *
93 * Allocates a new eap_session_t, and inserts it into the REQUEST_DATA_EAP_SESSION index
94 * of the request.
95 *
96 * @note The eap_session_t will remove itself from the #REQUEST_DATA_EAP_SESSION index
97 * if it is freed. This is to simplify management of the request data entry.
98 *
99 * @param[in] request That generated this eap_session_t.
100 * @return
101 * - A new #eap_session_t on success.
102 * - NULL on failure.
103 */
105{
106 eap_session_t *eap_session;
107
108 eap_session = talloc_zero(NULL, eap_session_t);
109 if (unlikely(!eap_session)) {
110 ERROR("Failed allocating eap_session");
111 return NULL;
112 }
113 eap_session->request = request;
114 eap_session->updated = request->packet->timestamp;
115
116 talloc_set_destructor(eap_session, _eap_session_free);
117
118 /*
119 * If the index is removed by something else
120 * like the state being cleaned up, then we
121 * still want the eap_session to be freed, which
122 * is why we set free_opaque to true.
123 *
124 * We must pass a NULL pointer to associate the
125 * the EAP_SESSION data with, else we'll break
126 * tunneled EAP, where the inner EAP module is
127 * a different instance to the outer one.
128 *
129 * We add this first so that eap_session_destroy
130 */
132 eap_session, true, true, true);
133
134 return eap_session;
135}
136
137/** 'destroy' an EAP session and disassociate it from the current request
138 *
139 * @note This could be done in the eap_session_t destructor (and was done previously)
140 * but this made the code too hard to follow, and too fragile.
141 *
142 * @see eap_session_continue
143 * @see eap_session_freeze
144 * @see eap_session_thaw
145 *
146 * @param eap_session to destroy (disassociate and free).
147 */
149{
150 if (!*eap_session) return;
151
152 talloc_free(*eap_session);
153 *eap_session = NULL;
154}
155
156/** discard and 'destroy' an EAP session and disassociate it from the current request
157 *
158 * @see eap_session_continue
159 * @see eap_session_freeze
160 * @see eap_session_thaw
161 *
162 * @param request to destroy (disassociate and free).
163 */
165{
166 eap_session_t *eap_session;
167
168 /*
169 * Unlink the request_data_t entry as we pull the
170 * eap_session out. Using _reference here would leave
171 * the entry in place with rd->opaque pointing at the
172 * chunk we're about to free, and the destructor's
173 * own request_data_get cleanup is skipped when the
174 * session is frozen (request == NULL).
175 */
177 if (!eap_session) return;
178
179 /*
180 * Don't print "session didn't finish".
181 */
182 eap_session->finished = true;
183
184 eap_session_destroy(&eap_session);
185}
186
187/** Freeze an #eap_session_t so that it can continue later
188 *
189 * Sets the request and pointer to the eap_session to NULL. Primarily here to help track
190 * the lifecycle of an #eap_session_t.
191 *
192 * The actual freezing/thawing and management (ensuring it's available during multiple
193 * rounds of EAP) of the #eap_session_t associated with REQUEST_DATA_EAP_SESSION, is
194 * done by the state API.
195 *
196 * @note must be called before mod_* functions in rlm_eap return.
197 *
198 * @see eap_session_continue
199 * @see eap_session_thaw
200 * @see eap_session_destroy
201 *
202 * @param eap_session to freeze.
203 */
205{
206 if (!*eap_session) return;
207
208 fr_assert((*eap_session)->request);
209 (*eap_session)->request = NULL;
210 *eap_session = NULL;
211}
212
213/** Check whether a request runs inside the tunnel of another EAP method
214 *
215 * A tunneled EAP method (PEAP, TTLS, FAST, TEAP) should not nest
216 * inside of another tunneled method.
217 *
218 * @param[in] request to check.
219 * @return
220 * - true if any ancestor has an #eap_session_t with #eap_session_t::tunnel set.
221 * - false otherwise.
222 */
224{
226
227 for (parent = request->parent; parent; parent = parent->parent) {
229
230 if (outer && outer->tunnel) return true;
231 }
232
233 return false;
234}
235
236/** Thaw an eap_session_t so it can be continued
237 *
238 * Retrieve an #eap_session_t from the request data, and set relevant fields. Primarily
239 * here to help track the lifecycle of an #eap_session_t.
240 *
241 * The actual freezing/thawing and management (ensuring it's available during multiple
242 * rounds of EAP) of the #eap_session_t associated with REQUEST_DATA_EAP_SESSION, is
243 * done by the state API.
244 *
245 * @note #eap_session_continue should be used instead if ingesting an #eap_packet_raw_t.
246 *
247 * @see eap_session_continue
248 * @see eap_session_freeze
249 * @see eap_session_destroy
250 *
251 * @param request to retrieve session from.
252 * @return
253 * - The #eap_session_t associated with this request.
254 * MUST be freed with #eap_session_destroy if being disposed of, OR
255 * MUST be re-frozen with #eap_session_freeze if the authentication session will
256 * continue when a future request is received.
257 * - NULL if no #eap_session_t associated with this request.
258 */
260{
261 eap_session_t *eap_session;
262
264 if (!eap_session) return NULL;
265
266 if (!fr_cond_assert(eap_session->inst)) return NULL;
267
268 fr_assert(!eap_session->request); /* If triggered, something didn't freeze the session */
269 eap_session->request = request;
270 eap_session->updated = request->packet->timestamp;
271
272 return eap_session;
273}
274
275/** Extract the EAP identity from EAP-Identity-Response packets
276 *
277 * @param[in] request The current request.
278 * @param[in] eap_session EAP-Session to associate identity with.
279 * @param[in] eap_packet To extract the identity from.
280 * @return
281 * - The user's EAP-Identity.
282 * - or NULL on error.
283 */
284static char *eap_identity(request_t *request, eap_session_t *eap_session, eap_packet_raw_t *eap_packet)
285{
286 size_t len;
287
288 if (!eap_packet ||
289 (eap_packet->code != FR_EAP_CODE_RESPONSE) ||
290 (eap_packet->data[0] != FR_EAP_METHOD_IDENTITY)) return NULL;
291
292 len = talloc_array_length((uint8_t *) eap_packet);
293
294 /*
295 * Note: The minimum length here is 5.
296 * Previous versions of FreeRADIUS limited the length to 6 and
297 * checked for data[0] != \0.
298 *
299 * This was incorrect, and broke encrypted pseudonyms in EAP-SIM/AKA.
300 *
301 * RFC 3748 states - If the Identity is unknown, the
302 * Identity Response field should be zero bytes in length. The
303 * Identity Response field MUST NOT be null terminated. In all
304 * cases, the length of the Type-Data field is derived from the
305 * Length field of the Request/Response packet.
306 *
307 * Code (1) + Identifier (1) + Length (2) + Type (1) = 5.
308 *
309 * The maximum value is not bounded by the RFC. The eap_is_valid()
310 * function called before eap_identity(), checks that the length
311 * field does not overrun the available data.
312 *
313 * In some EAP methods, the identity may be encrypted, and padded
314 * out to the block size of the encryption method. These identities
315 * may contain nuls, and may be much larger than humanly readable
316 * identities.
317 *
318 * The identity value *MUST NOT* be artificially limited or truncated
319 * here.
320 */
321 if (len <= EAP_HEADER_LEN) {
322 REDEBUG("EAP-Identity length field too short, expected >= 5, got %zu", len);
323 return NULL;
324 }
325
326 /*
327 * If the length is 5, then a buffer with a length of 1 is
328 * created with a \0 byte.
329 */
330 return talloc_bstrndup(eap_session, (char *)&eap_packet->data[1], len - 5);
331}
332
333/** Ingest an eap_packet into a thawed or newly allocated session
334 *
335 * If eap_packet is an Identity-Response then allocate a new eap_session and fill the identity.
336 *
337 * If eap_packet is not an identity response, retrieve the pre-existing eap_session_t from request
338 * data.
339 *
340 * If no User-Name attribute is present in the request, one will be created from the
341 * Identity-Response received when the eap_session was allocated.
342 *
343 * @see eap_session_freeze
344 * @see eap_session_thaw
345 * @see eap_session_destroy
346 *
347 * @param[in] instance of rlm_eap that created the session.
348 * @param[in] eap_packet_p extracted from the RADIUS Access-Request.
349 * Consumed or freed by this function.
350 * Do not access after calling this function.
351 * Is a **so the packet pointer can be
352 * set to NULL.
353 * @param[in] request The current request.
354 * @return
355 * - A newly allocated eap_session_t, or the one associated with the current request.
356 * MUST be freed with #eap_session_destroy if being disposed of, OR
357 * MUST be re-frozen with #eap_session_freeze if the authentication session will
358 * continue when a future request is received.
359 * - NULL on error, any existing sessions will be destroyed.
360 */
362{
363 eap_session_t *eap_session = NULL;
364 eap_packet_raw_t *eap_packet;
365 fr_pair_t *user;
366
367 eap_packet = *eap_packet_p;
368
369 /*
370 * RFC 3579 - Once EAP has been negotiated, the NAS SHOULD
371 * send an initial EAP-Request message to the authenticating
372 * peer. This will typically be an EAP-Request/Identity,
373 * although it could be an EAP-Request for an authentication
374 * method (Types 4 and greater).
375 *
376 * This means that if there is no State attribute, we should
377 * consider this as the start of a new session.
378 */
379 eap_session = eap_session_thaw(request);
380 if (!eap_session) {
381 eap_session = eap_session_alloc(request);
382 if (!eap_session) {
383 talloc_free(*eap_packet_p);
384 *eap_packet_p = NULL;
385 return NULL;
386 }
387 eap_session->inst = instance;
388
389 if (RDEBUG_ENABLED4) {
390 RDEBUG4("New EAP session - eap_session_t %p", eap_session);
391 } else {
392 RDEBUG2("New EAP session started");
393 }
394
395 /*
396 * All fields in the eap_session are set to zero.
397 */
398 switch (eap_packet->data[0]) {
400 eap_session->identity = eap_identity(request, eap_session, eap_packet);
401 if (!eap_session->identity) {
402 REDEBUG("Invalid identity response");
403 error:
404 eap_session_destroy(&eap_session);
405 talloc_free(*eap_packet_p);
406 *eap_packet_p = NULL;
407 return NULL;
408 }
409
410 /*
411 * Sometimes we need the hex stream to determine where
412 * random junk is coming from.
413 */
414 RHEXDUMP3((uint8_t *const)eap_session->identity,
415 talloc_strlen(eap_session->identity),
416 "EAP Identity Response - \"%pV\"",
417 fr_box_strvalue_len(eap_session->identity,
418 talloc_strlen(eap_session->identity)));
419 break;
420
424 REDEBUG("Initial EAP method %s(%u) invalid",
425 eap_type2name((eap_type_t)eap_packet->data[0]), eap_packet->data[0]);
426 goto error;
427
428 /*
429 * Initialise a zero length identity, as we've
430 * not been provided with one at the start of the
431 * EAP method.
432 */
433 default:
434 eap_session->identity = talloc_bstrndup(eap_session, "", 0);
435 break;
436 }
437 /*
438 * Continue a previously started EAP-Session
439 */
440 } else {
441 if (RDEBUG_ENABLED4) {
442 RDEBUG4("Continuing EAP session - eap_session_t %p", eap_session);
443 } else {
444 RDEBUG2("Continuing EAP session");
445 }
446
447 (void) talloc_get_type_abort(eap_session, eap_session_t);
448 eap_session->rounds++;
449 if (eap_session->rounds >= 50) {
450 RERROR("Failing EAP session due to too many round trips");
451 goto error;
452 }
453
454 /*
455 * Even more paranoia. Without this, some weird
456 * clients could do crazy things.
457 *
458 * It's ok to send EAP sub-type NAK in response
459 * to a request for a particular type, but it's NOT
460 * OK to blindly return data for another type.
461 */
462 if ((eap_packet->data[0] != FR_EAP_METHOD_NAK) &&
463 (eap_packet->data[0] != eap_session->type)) {
464 RERROR("Response appears to match a previous request, but the EAP type is wrong");
465 RERROR("We expected EAP type %s, but received type %s",
466 eap_type2name(eap_session->type),
467 eap_type2name((eap_type_t)eap_packet->data[0]));
468 RERROR("Your Supplicant or NAS is probably broken");
469 goto error;
470 }
471 }
472
473 /*
474 * RFC3579 In order to permit non-EAP aware RADIUS proxies to forward the
475 * Access-Request packet, if the NAS initially sends an
476 * EAP-Request/Identity message to the peer, the NAS MUST copy the
477 * contents of the Type-Data field of the EAP-Response/Identity received
478 * from the peer into the User-Name attribute and MUST include the
479 * Type-Data field of the EAP-Response/Identity in the User-Name
480 * attribute in every subsequent Access-Request.
481 */
482 user = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
483 if (!user) {
484 /*
485 * NAS did not set the User-Name
486 * attribute, so we set it here and
487 * prepend it to the beginning of the
488 * request vps so that autz's work
489 * correctly
490 */
491 RDEBUG2("Broken NAS did not set User-Name, setting from EAP Identity");
493 fr_pair_value_bstrdup_buffer(user, eap_session->identity, true);
494 /*
495 * The RFC 3579 is pretty unambiguous, the main issue is that the EAP Identity Response
496 * can be significantly longer than 253 bytes (the maximum RADIUS
497 * attribute length), and the RFC is silent about what happens then.
498 *
499 * The behaviour seen in the wild, is that the NAS will use the Mac-Address
500 * of the connecting device as the User-name, and send the Identity in full,
501 * so if the EAP identity is longer than the max RADIUS attribute length
502 * then ignore mismatches.
503 */
504 } else if ((talloc_strlen(eap_session->identity)) <= RADIUS_MAX_STRING_LENGTH) {
505 /*
506 * A little more paranoia. If the NAS
507 * *did* set the User-Name, and it doesn't
508 * match the identity, (i.e. If they
509 * change their User-Name part way through
510 * the EAP transaction), then reject the
511 * request as the NAS is doing something
512 * funny.
513 */
514 if (talloc_memcmp_bstr(eap_session->identity, user->vp_strvalue) != 0) {
515 REDEBUG("Identity from EAP Identity-Response \"%s\" does not match User-Name attribute \"%s\"",
516 eap_session->identity, user->vp_strvalue);
517 goto error;
518 }
519 }
520
521 eap_session->this_round = eap_round_build(eap_session, eap_packet_p);
522 if (!eap_session->this_round) {
523 REDEBUG("Failed allocating memory for round");
524 goto error;
525 }
526
527 return eap_session;
528}
static uint8_t request[4]
#define unlikely(_x)
Definition build.h:486
eap_round_t * eap_round_build(eap_session_t *eap_session, eap_packet_raw_t **eap_packet_p)
Definition compose.c:566
EAP packet composition.
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:177
#define MEM(x)
Definition debug.h:38
#define ERROR(fmt,...)
Definition dhcpclient.c:40
char const * eap_type2name(eap_type_t method)
Return an EAP-name for a particular type.
Definition types.c:54
uint8_t data[]
Definition types.h:125
@ FR_EAP_METHOD_NAK
Definition types.h:48
@ FR_EAP_METHOD_NOTIFICATION
Definition types.h:47
@ FR_EAP_METHOD_IDENTITY
Definition types.h:46
@ FR_EAP_METHOD_INVALID
Definition types.h:45
enum eap_type eap_type_t
uint8_t code
Definition types.h:122
#define EAP_HEADER_LEN
Definition types.h:34
@ FR_EAP_CODE_RESPONSE
Definition types.h:38
Structure to represent packet format of eap on wire
Definition types.h:121
talloc_free(hp)
static int _eap_session_free(eap_session_t *eap_session)
Definition session.c:31
eap_session_t * eap_session_thaw(request_t *request)
Thaw an eap_session_t so it can be continued.
Definition session.c:259
eap_session_t * eap_session_continue(void const *instance, eap_packet_raw_t **eap_packet_p, request_t *request)
Ingest an eap_packet into a thawed or newly allocated session.
Definition session.c:361
void eap_session_discard(request_t *request)
discard and 'destroy' an EAP session and disassociate it from the current request
Definition session.c:164
bool eap_session_in_tunnel(request_t const *request)
Check whether a request runs inside the tunnel of another EAP method.
Definition session.c:223
static char * eap_identity(request_t *request, eap_session_t *eap_session, eap_packet_raw_t *eap_packet)
Extract the EAP identity from EAP-Identity-Response packets.
Definition session.c:284
void eap_session_freeze(eap_session_t **eap_session)
Freeze an eap_session_t so that it can continue later.
Definition session.c:204
static eap_session_t * eap_session_alloc(request_t *request)
Allocate a new eap_session_t.
Definition session.c:104
void eap_session_destroy(eap_session_t **eap_session)
'destroy' an EAP session and disassociate it from the current request
Definition session.c:148
char * identity
NAI (User-Name) from EAP-Identity.
Definition session.h:56
static eap_session_t * eap_session_get(request_t *request)
Definition session.h:86
bool tunnel
copied from #fr_rlm_eap_submodule_t
Definition session.h:72
bool tls
Whether EAP method uses TLS.
Definition session.h:71
eap_type_t type
EAP method number.
Definition session.h:50
request_t * request
Current request.
Definition session.h:52
void const * inst
Instance of the eap module this session was created by.
Definition session.h:49
eap_round_t * this_round
The EAP response we're processing, and the EAP request we're building.
Definition session.h:60
eap_round_t * prev_round
Previous response/request pair.
Definition session.h:58
#define REQUEST_DATA_EAP_SESSION
Definition session.h:35
bool finished
Whether we consider this session complete.
Definition session.h:73
int rounds
How many roundtrips have occurred this session.
Definition session.h:67
fr_time_t updated
The last time we received a packet for this EAP session.
Definition session.h:69
Tracks the progress of a single session of any EAP method.
Definition session.h:41
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition log.h:545
#define RWDEBUG(fmt,...)
Definition log.h:378
#define RERROR(fmt,...)
Definition log.h:315
#define DEBUG4(_fmt,...)
Definition log.h:272
#define DEBUG_ENABLED
True if global debug level 1 messages are enabled.
Definition log.h:262
#define RHEXDUMP3(_data, _len, _fmt,...)
Definition log.h:724
#define RDEBUG4(fmt,...)
Definition log.h:361
#define RDEBUG_ENABLED4
True if request debug level 1-4 messages are enabled.
Definition log.h:353
#define fr_time()
Definition event.c:58
unsigned char uint8_t
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:707
VQP attributes.
#define fr_assert(_expr)
Definition rad_assert.h:37
static fr_dict_attr_t const * attr_user_name
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
#define RDEBUG_ENABLED()
#define WARN(fmt,...)
#define RADIUS_MAX_STRING_LENGTH
Definition radius.h:35
void * request_data_reference(request_t *request, void const *unique_ptr, int unique_int)
Get opaque data from a request without removing it.
void * request_data_get(request_t *request, void const *unique_ptr, int unique_int)
Get opaque data from a request.
#define request_data_talloc_add(_request, _unique_ptr, _unique_int, _type, _opaque, _free_on_replace, _free_on_parent, _persist)
Add opaque data to a request_t.
#define pair_append_request(_attr, _da)
Allocate and append a fr_pair_t to the request list.
Definition pair.h:37
fr_pair_value_bstrdup_buffer(vp, eap_session->identity, true)
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
Definition talloc.c:693
int talloc_memcmp_bstr(char const *a, char const *b)
Compares two talloced char arrays with memcmp.
Definition talloc.c:869
static size_t talloc_strlen(char const *s)
Returns the length of a talloc array containing a string.
Definition talloc.h:143
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:599
#define fr_time_sub(_a, _b)
Subtract one time from another.
Definition time.h:229
#define fr_time_delta_gt(_a, _b)
Definition time.h:283
static fr_slen_t parent
Definition pair.h:864
#define fr_box_strvalue_len(_val, _len)
Definition value.h:340
static fr_sbuff_err_t char size_t * len
Definition value.h:1061