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