The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
session.c
Go to the documentation of this file.
1/*
2 * This program is 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: 61ed3c39f599a52f4f8def33fed7d09ea7b2e1d2 $
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
75 in_request = request_data_get(request, NULL, REQUEST_DATA_EAP_SESSION);
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
85 (void) request_data_get(request, NULL, REQUEST_DATA_EAP_SESSION);
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 eap_session = request_data_reference(request, NULL, REQUEST_DATA_EAP_SESSION);
169 if (!eap_session) return;
170
171 /*
172 * Don't print "session didn't finish".
173 */
174 eap_session->finished = true;
175
176 eap_session_destroy(&eap_session);
177}
178
179/** Freeze an #eap_session_t so that it can continue later
180 *
181 * Sets the request and pointer to the eap_session to NULL. Primarily here to help track
182 * the lifecycle of an #eap_session_t.
183 *
184 * The actual freezing/thawing and management (ensuring it's available during multiple
185 * rounds of EAP) of the #eap_session_t associated with REQUEST_DATA_EAP_SESSION, is
186 * done by the state API.
187 *
188 * @note must be called before mod_* functions in rlm_eap return.
189 *
190 * @see eap_session_continue
191 * @see eap_session_thaw
192 * @see eap_session_destroy
193 *
194 * @param eap_session to freeze.
195 */
197{
198 if (!*eap_session) return;
199
200 fr_assert((*eap_session)->request);
201 (*eap_session)->request = NULL;
202 *eap_session = NULL;
203}
204
205/** Thaw an eap_session_t so it can be continued
206 *
207 * Retrieve an #eap_session_t from the request data, and set relevant fields. Primarily
208 * here to help track the lifecycle of an #eap_session_t.
209 *
210 * The actual freezing/thawing and management (ensuring it's available during multiple
211 * rounds of EAP) of the #eap_session_t associated with REQUEST_DATA_EAP_SESSION, is
212 * done by the state API.
213 *
214 * @note #eap_session_continue should be used instead if ingesting an #eap_packet_raw_t.
215 *
216 * @see eap_session_continue
217 * @see eap_session_freeze
218 * @see eap_session_destroy
219 *
220 * @param request to retrieve session from.
221 * @return
222 * - The #eap_session_t associated with this request.
223 * MUST be freed with #eap_session_destroy if being disposed of, OR
224 * MUST be re-frozen with #eap_session_freeze if the authentication session will
225 * continue when a future request is received.
226 * - NULL if no #eap_session_t associated with this request.
227 */
229{
230 eap_session_t *eap_session;
231
232 eap_session = request_data_reference(request, NULL, REQUEST_DATA_EAP_SESSION);
233 if (!eap_session) return NULL;
234
235 if (!fr_cond_assert(eap_session->inst)) return NULL;
236
237 fr_assert(!eap_session->request); /* If triggered, something didn't freeze the session */
238 eap_session->request = request;
239 eap_session->updated = request->packet->timestamp;
240
241 return eap_session;
242}
243
244/** Extract the EAP identity from EAP-Identity-Response packets
245 *
246 * @param[in] request The current request.
247 * @param[in] eap_session EAP-Session to associate identity with.
248 * @param[in] eap_packet To extract the identity from.
249 * @return
250 * - The user's EAP-Identity.
251 * - or NULL on error.
252 */
253static char *eap_identity(request_t *request, eap_session_t *eap_session, eap_packet_raw_t *eap_packet)
254{
255 size_t len;
256
257 if (!eap_packet ||
258 (eap_packet->code != FR_EAP_CODE_RESPONSE) ||
259 (eap_packet->data[0] != FR_EAP_METHOD_IDENTITY)) return NULL;
260
261 len = talloc_array_length((uint8_t *) eap_packet);
262
263 /*
264 * Note: The minimum length here is 5.
265 * Previous versions of FreeRADIUS limited the length to 6 and
266 * checked for data[0] != \0.
267 *
268 * This was incorrect, and broke encrypted pseudonyms in EAP-SIM/AKA.
269 *
270 * RFC 3748 states - If the Identity is unknown, the
271 * Identity Response field should be zero bytes in length. The
272 * Identity Response field MUST NOT be null terminated. In all
273 * cases, the length of the Type-Data field is derived from the
274 * Length field of the Request/Response packet.
275 *
276 * Code (1) + Identifier (1) + Length (2) + Type (1) = 5.
277 *
278 * The maximum value is not bounded by the RFC. The eap_is_valid()
279 * function called before eap_identity(), checks that the length
280 * field does not overrun the available data.
281 *
282 * In some EAP methods, the identity may be encrypted, and padded
283 * out to the block size of the encryption method. These identities
284 * may contain nuls, and may be much larger than humanly readable
285 * identities.
286 *
287 * The identity value *MUST NOT* be artificially limited or truncated
288 * here.
289 */
290 if (len <= EAP_HEADER_LEN) {
291 REDEBUG("EAP-Identity length field too short, expected >= 5, got %zu", len);
292 return NULL;
293 }
294
295 /*
296 * If the length is 5, then a buffer with a length of 1 is
297 * created with a \0 byte.
298 */
299 return talloc_bstrndup(eap_session, (char *)&eap_packet->data[1], len - 5);
300}
301
302/** Ingest an eap_packet into a thawed or newly allocated session
303 *
304 * If eap_packet is an Identity-Response then allocate a new eap_session and fill the identity.
305 *
306 * If eap_packet is not an identity response, retrieve the pre-existing eap_session_t from request
307 * data.
308 *
309 * If no User-Name attribute is present in the request, one will be created from the
310 * Identity-Response received when the eap_session was allocated.
311 *
312 * @see eap_session_freeze
313 * @see eap_session_thaw
314 * @see eap_session_destroy
315 *
316 * @param[in] instance of rlm_eap that created the session.
317 * @param[in] eap_packet_p extracted from the RADIUS Access-Request.
318 * Consumed or freed by this function.
319 * Do not access after calling this function.
320 * Is a **so the packet pointer can be
321 * set to NULL.
322 * @param[in] request The current request.
323 * @return
324 * - A newly allocated eap_session_t, or the one associated with the current request.
325 * MUST be freed with #eap_session_destroy if being disposed of, OR
326 * MUST be re-frozen with #eap_session_freeze if the authentication session will
327 * continue when a future request is received.
328 * - NULL on error, any existing sessions will be destroyed.
329 */
330eap_session_t *eap_session_continue(void const *instance, eap_packet_raw_t **eap_packet_p, request_t *request)
331{
332 eap_session_t *eap_session = NULL;
333 eap_packet_raw_t *eap_packet;
334 fr_pair_t *user;
335
336 eap_packet = *eap_packet_p;
337
338 /*
339 * RFC 3579 - Once EAP has been negotiated, the NAS SHOULD
340 * send an initial EAP-Request message to the authenticating
341 * peer. This will typically be an EAP-Request/Identity,
342 * although it could be an EAP-Request for an authentication
343 * method (Types 4 and greater).
344 *
345 * This means that if there is no State attribute, we should
346 * consider this as the start of a new session.
347 */
348 eap_session = eap_session_thaw(request);
349 if (!eap_session) {
350 eap_session = eap_session_alloc(request);
351 if (!eap_session) {
352 talloc_free(*eap_packet_p);
353 *eap_packet_p = NULL;
354 return NULL;
355 }
356 eap_session->inst = instance;
357
358 if (RDEBUG_ENABLED4) {
359 RDEBUG4("New EAP session - eap_session_t %p", eap_session);
360 } else {
361 RDEBUG2("New EAP session started");
362 }
363
364 /*
365 * All fields in the eap_session are set to zero.
366 */
367 switch (eap_packet->data[0]) {
369 eap_session->identity = eap_identity(request, eap_session, eap_packet);
370 if (!eap_session->identity) {
371 REDEBUG("Invalid identity response");
372 error:
373 eap_session_destroy(&eap_session);
374 talloc_free(*eap_packet_p);
375 *eap_packet_p = NULL;
376 return NULL;
377 }
378
379 /*
380 * Sometimes we need the hex stream to determine where
381 * random junk is coming from.
382 */
383 RHEXDUMP3((uint8_t *const)eap_session->identity,
384 talloc_array_length(eap_session->identity) - 1,
385 "EAP Identity Response - \"%pV\"",
386 fr_box_strvalue_len(eap_session->identity,
387 talloc_array_length(eap_session->identity) - 1));
388 break;
389
393 REDEBUG("Initial EAP method %s(%u) invalid",
394 eap_type2name(eap_packet->data[0]), eap_packet->data[0]);
395 goto error;
396
397 /*
398 * Initialise a zero length identity, as we've
399 * not been provided with one at the start of the
400 * EAP method.
401 */
402 default:
403 eap_session->identity = talloc_bstrndup(eap_session, "", 0);
404 break;
405 }
406 /*
407 * Continue a previously started EAP-Session
408 */
409 } else {
410 if (RDEBUG_ENABLED4) {
411 RDEBUG4("Continuing EAP session - eap_session_t %p", eap_session);
412 } else {
413 RDEBUG2("Continuing EAP session");
414 }
415
416 (void) talloc_get_type_abort(eap_session, eap_session_t);
417 eap_session->rounds++;
418 if (eap_session->rounds >= 50) {
419 RERROR("Failing EAP session due to too many round trips");
420 goto error;
421 }
422
423 /*
424 * Even more paranoia. Without this, some weird
425 * clients could do crazy things.
426 *
427 * It's ok to send EAP sub-type NAK in response
428 * to a request for a particular type, but it's NOT
429 * OK to blindly return data for another type.
430 */
431 if ((eap_packet->data[0] != FR_EAP_METHOD_NAK) &&
432 (eap_packet->data[0] != eap_session->type)) {
433 RERROR("Response appears to match a previous request, but the EAP type is wrong");
434 RERROR("We expected EAP type %s, but received type %s",
435 eap_type2name(eap_session->type),
436 eap_type2name(eap_packet->data[0]));
437 RERROR("Your Supplicant or NAS is probably broken");
438 goto error;
439 }
440 }
441
442 /*
443 * RFC3579 In order to permit non-EAP aware RADIUS proxies to forward the
444 * Access-Request packet, if the NAS initially sends an
445 * EAP-Request/Identity message to the peer, the NAS MUST copy the
446 * contents of the Type-Data field of the EAP-Response/Identity received
447 * from the peer into the User-Name attribute and MUST include the
448 * Type-Data field of the EAP-Response/Identity in the User-Name
449 * attribute in every subsequent Access-Request.
450 */
451 user = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
452 if (!user) {
453 /*
454 * NAS did not set the User-Name
455 * attribute, so we set it here and
456 * prepend it to the beginning of the
457 * request vps so that autz's work
458 * correctly
459 */
460 RDEBUG2("Broken NAS did not set User-Name, setting from EAP Identity");
462 fr_pair_value_bstrdup_buffer(user, eap_session->identity, true);
463 /*
464 * The RFC 3579 is pretty unambiguous, the main issue is that the EAP Identity Response
465 * can be significantly longer than 253 bytes (the maximum RADIUS
466 * attribute length), and the RFC is silent about what happens then.
467 *
468 * The behaviour seen in the wild, is that the NAS will use the Mac-Address
469 * of the connecting device as the User-name, and send the Identity in full,
470 * so if the EAP identity is longer than the max RADIUS attribute length
471 * then ignore mismatches.
472 */
473 } else if ((talloc_array_length(eap_session->identity) - 1) <= RADIUS_MAX_STRING_LENGTH) {
474 /*
475 * A little more paranoia. If the NAS
476 * *did* set the User-Name, and it doesn't
477 * match the identity, (i.e. If they
478 * change their User-Name part way through
479 * the EAP transaction), then reject the
480 * request as the NAS is doing something
481 * funny.
482 */
483 if (talloc_memcmp_bstr(eap_session->identity, user->vp_strvalue) != 0) {
484 REDEBUG("Identity from EAP Identity-Response \"%s\" does not match User-Name attribute \"%s\"",
485 eap_session->identity, user->vp_strvalue);
486 goto error;
487 }
488 }
489
490 eap_session->this_round = eap_round_build(eap_session, eap_packet_p);
491 if (!eap_session->this_round) {
492 REDEBUG("Failed allocating memory for round");
493 goto error;
494 }
495
496 return eap_session;
497}
#define unlikely(_x)
Definition build.h:383
eap_round_t * eap_round_build(eap_session_t *eap_session, eap_packet_raw_t **eap_packet_p)
Definition compose.c:567
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:131
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:41
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_CODE_RESPONSE
Definition types.h:38
uint8_t code
Definition types.h:122
#define EAP_HEADER_LEN
Definition types.h:34
@ 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
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:228
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:330
void eap_session_discard(request_t *request)
discard and 'destroy' an EAP session and disassociate it from the current request
Definition session.c:164
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:253
void eap_session_freeze(eap_session_t **eap_session)
Freeze an eap_session_t so that it can continue later.
Definition session.c:196
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
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:72
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:540
#define RWDEBUG(fmt,...)
Definition log.h:373
#define RERROR(fmt,...)
Definition log.h:310
#define DEBUG4(_fmt,...)
Definition log.h:267
#define DEBUG_ENABLED
True if global debug level 1 messages are enabled.
Definition log.h:257
#define RHEXDUMP3(_data, _len, _fmt,...)
Definition log.h:717
#define RDEBUG4(fmt,...)
Definition log.h:356
#define RDEBUG_ENABLED4
True if request debug level 1-4 messages are enabled.
Definition log.h:348
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:705
VQP attributes.
#define fr_assert(_expr)
Definition rad_assert.h:38
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)
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition state_test.c:8
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:589
int talloc_memcmp_bstr(char const *a, char const *b)
Compares two talloced char arrays with memcmp.
Definition talloc.c:822
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:590
#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
#define fr_box_strvalue_len(_val, _len)
Definition value.h:309