The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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 
31 static 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  */
230 static 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  */
307 eap_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 
369  case FR_EAP_METHOD_NAK:
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");
438  MEM(pair_append_request(&user, attr_user_name) >= 0);
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:378
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:137
#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
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
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 eap_session_t * eap_session_alloc(request_t *request)
Allocate a new eap_session_t.
Definition: session.c:104
void eap_session_freeze(eap_session_t **eap_session)
Freeze an eap_session_t so that it can continue later.
Definition: session.c:173
void eap_session_destroy(eap_session_t **eap_session)
'destroy' an EAP session and disassociate it from the current request
Definition: session.c:148
eap_session_t * eap_session_thaw(request_t *request)
Thaw an eap_session_t so it can be continued.
Definition: session.c:205
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
Definition: merged_model.c:31
unsigned char uint8_t
Definition: merged_model.c:30
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:688
VQP attributes.
static fr_dict_attr_t const * attr_user_name
Definition: radclient-ng.c:125
#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:34
void * request_data_get(request_t *request, void const *unique_ptr, int unique_int)
Get opaque data from a request.
Definition: request_data.c:292
void * request_data_reference(request_t *request, void const *unique_ptr, int unique_int)
Get opaque data from a request without removing it.
Definition: request_data.c:339
#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.
Definition: request_data.h:86
#define pair_append_request(_attr, _da)
Allocate and append a fr_pair_t to the request list.
Definition: pair.h:37
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
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:452
int talloc_memcmp_bstr(char const *a, char const *b)
Compares two talloced char arrays with memcmp.
Definition: talloc.c:684
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition: time.h:588
#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:281
#define fr_box_strvalue_len(_val, _len)
Definition: value.h:279