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