The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
ticket_stateless.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
5 * (at 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: 377e6b880f38e5236fda421b1065302ba4b18557 $
19 *
20 * @file tls/ticket_stateless.c
21 * @brief Stateless TLS session tickets, and the `encode session` and `decode session` sections
22 *
23 * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
25 */
26RCSID("$Id: 377e6b880f38e5236fda421b1065302ba4b18557 $")
27USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
28
29#ifdef WITH_TLS
30#define LOG_PREFIX "tls"
31#define _TLS_PRIVATE 1
32
33#include <freeradius-devel/internal/internal.h>
34#include <freeradius-devel/server/pair.h>
35#include <freeradius-devel/unlang/function.h>
36#include <freeradius-devel/unlang/subrequest.h>
37#include <freeradius-devel/util/debug.h>
38
39#include <freeradius-devel/protocol/tls/freeradius.h>
40
41#include "attrs.h"
42#include "base.h"
43#include "log.h"
44#include "strerror.h"
45#include "ticket.h"
46#include "verify.h"
47
48#include <openssl/ssl.h>
49#include <openssl/kdf.h>
50
51/** Resume after processing `encode session { ... }` or `decode session { ... }`
52 *
53 * Check the result and return success / fail depending.
54 */
55static unlang_action_t tls_ticket_stateless_resume(request_t *request, void *uctx)
56{
57 fr_tls_session_t *tls_session = talloc_get_type_abort(uctx, fr_tls_session_t);
59
60 fr_assert((tls_session->ticket == FR_TLS_TICKET_STATELESS_ENCODE_REQUESTED) ||
61 (tls_session->ticket == FR_TLS_TICKET_STATELESS_DECODE_REQUESTED));
62
63 vp = fr_pair_find_by_da(&request->reply_pairs, NULL, attr_tls_packet_type);
64 if (!vp || (vp->vp_uint32 != enum_tls_packet_type_success->vb_uint32)) {
65 tls_session->ticket = FR_TLS_TICKET_STATELESS_FAILED;
67 }
68
69 tls_session->ticket = FR_TLS_TICKET_STATELESS_SUCCESS;
70
72}
73
74/** Push `encode session { ... }` or `decode session { ... }`
75 *
76 * @param[in] request The current request.
77 * @param[in] tls_session The current TLS session.
78 * @param[in] packet_type Which of the two sections to run.
79 * @return
80 * - UNLANG_ACTION_PUSHED_CHILD on success.
81 * - UNLANG_ACTION_FAIL on failure.
82 */
83static unlang_action_t tls_ticket_stateless_push(request_t *request, fr_tls_session_t *tls_session,
84 uint32_t packet_type)
85{
86 fr_tls_conf_t *conf = tls_session->conf;
87 request_t *child;
89
90 fr_assert(conf->virtual_server);
91
92 MEM(child = tls_subrequest_alloc(request, packet_type, &tls_session->session_id));
93
94 fr_tls_session_extra_pairs_copy_to_child(child, tls_session);
95
96 ua = fr_tls_call_push(child, tls_ticket_stateless_resume, conf, tls_session, false);
97 if (ua == UNLANG_ACTION_FAIL) {
98 REDEBUG("(TLS) Failed calling TLS virtual server");
99 talloc_free(child);
100 return UNLANG_ACTION_FAIL;
101 }
102
103 return ua;
104}
105
106/** Push `encode session` or `decode session`, depending on what's needed.
107 *
108 * @param[in] request The current request.
109 * @param[in] tls_session The current TLS session.
110 * @return
111 * - UNLANG_ACTION_CALCULATE_RESULT - nothing was pending.
112 * - UNLANG_ACTION_PUSHED_CHILD - a section is running.
113 * - UNLANG_ACTION_FAIL - the frame could not be pushed.
114 */
115unlang_action_t fr_tls_ticket_stateless_pending_push(request_t *request, fr_tls_session_t *tls_session)
116{
117 switch (tls_session->ticket) {
118 case FR_TLS_TICKET_STATELESS_ENCODE_REQUESTED:
119 return tls_ticket_stateless_push(request, tls_session,
121
122 case FR_TLS_TICKET_STATELESS_DECODE_REQUESTED:
123 return tls_ticket_stateless_push(request, tls_session,
125
126 default:
128 }
129}
130
131/** Request `encode session { ... }`, and tell the handshake the section is pending
132 */
133static inline CC_HINT(always_inline) void tls_ticket_stateless_encode_request(fr_tls_session_t *tls_session)
134{
135 tls_session->ticket = FR_TLS_TICKET_STATELESS_ENCODE_REQUESTED;
136 TLS_PENDING_SET(tls_session, FR_TLS_PENDING_STATELESS_TICKET);
137}
138
139/** Request `decode session { ... }`, and tell the handshake the section is pending
140 */
141static inline CC_HINT(always_inline) void tls_ticket_stateless_decode_request(fr_tls_session_t *tls_session)
142{
143 tls_session->ticket = FR_TLS_TICKET_STATELESS_DECODE_REQUESTED;
144 TLS_PENDING_SET(tls_session, FR_TLS_PENDING_STATELESS_TICKET);
145}
146
147/** Pause the handshake for `encode session { ... }` or `decode session { ... }`, and read the result
148 *
149 * We can't run the interpreter inside of a callback, so the caller records
150 * what we want to do, and this function tells OpenSSL to pause its
151 * processing. We then return to session.c, which determines that there's
152 * work to do, pushes the section, runs it, and calls us again. That resumes
153 * after the ASYNC_pause_job() call.
154 *
155 * @param[in] request bound to the session.
156 * @param[in] tls_session the ticket belongs to.
157 * @param[in] name of the section, for the log.
158 * @param[in] error to record when the section does not return success.
159 * @return
160 * - true if the section ran and approved the session-state list.
161 * - false if it did not, or if it could not be run at all.
162 */
163static inline CC_HINT(always_inline)
164bool tls_ticket_stateless_section_run(request_t *request, fr_tls_session_t *tls_session,
165 char const *name, uint32_t error)
166{
167 /*
168 * Sections are only allowed during the handshake, as
169 * with certificate re-validation. See the FIXME in
170 * tls_ticket_stateless_app_data_get().
171 */
172 if (unlikely(!tls_session->can_pause)) {
173 fr_assert_msg("Unexpected call to %s. "
174 "tls_session_async_handshake_cont must be in call stack", __FUNCTION__);
175 tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
176 return false;
177 }
178
179 ASYNC_pause_job();
180
181 /*
182 * If the request was cancelled, reset the ticket state
183 * so that we don't do anything.
184 */
186 tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
187 return false;
188 }
189
190 if (tls_session->ticket != FR_TLS_TICKET_STATELESS_SUCCESS) {
191 REDEBUG("(TLS) `%s` did not return success", name);
192 fr_tls_session_error_add(request, error);
193 tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
194 return false;
195 }
196
197 tls_session->ticket = FR_TLS_TICKET_STATELESS_INIT;
198
199 return true;
200}
201
202/** Run `encode session { ... }` from inside an OpenSSL callback
203 *
204 * @param[in] request bound to the session.
205 * @param[in] tls_session the ticket belongs to.
206 * @return
207 * - true if the section ran and approved the session-state list.
208 * - false if it did not, or if it could not be run at all.
209 */
210static bool tls_ticket_stateless_encode_run(request_t *request, fr_tls_session_t *tls_session)
211{
212 fr_assert(tls_session->ticket == FR_TLS_TICKET_STATELESS_INIT);
213
214 tls_ticket_stateless_encode_request(tls_session);
215
216 return tls_ticket_stateless_section_run(request, tls_session, "encode session",
217 FR_ERROR_VALUE_ENCODE_SESSION_FAILED);
218}
219
220/** Run `decode session { ... }` from inside an OpenSSL callback
221 *
222 * @param[in] request bound to the session.
223 * @param[in] tls_session the ticket belongs to.
224 * @return
225 * - true if the section ran and approved the session-state list.
226 * - false if it did not, or if it could not be run at all.
227 */
228static bool tls_ticket_stateless_decode_run(request_t *request, fr_tls_session_t *tls_session)
229{
230 fr_assert(tls_session->ticket == FR_TLS_TICKET_STATELESS_INIT);
231
232 tls_ticket_stateless_decode_request(tls_session);
233
234 return tls_ticket_stateless_section_run(request, tls_session, "decode session",
235 FR_ERROR_VALUE_DECODE_SESSION_FAILED);
236}
237
238/** Disable stateless session tickets for a given TLS ctx
239 *
240 * @param[in] ctx to disable session tickets for.
241 * @param[in] stateful Are stateful tickets in use.
242 */
243void fr_tls_ticket_stateless_disable(SSL_CTX *ctx, bool stateful)
244{
245 long ctx_options = SSL_CTX_get_options(ctx);
246
247 /*
248 * Disable session tickets for older TLS versions
249 */
250 ctx_options |= SSL_OP_NO_TICKET;
251 SSL_CTX_set_options(ctx, ctx_options);
252
253 /*
254 * This controls the number of stateful or stateless
255 * tickets generated with TLS 1.3.
256 * Set to 1 ticket if stateful tickets are in use.
257 */
258 SSL_CTX_set_num_tickets(ctx, stateful ? 1 : 0);
259}
260
261/** Called when new tickets are being generated
262 *
263 * This adds additional application data to the session ticket to
264 * allow us to perform validation checks when the session is
265 * resumed.
266 */
267static int tls_ticket_stateless_app_data_set(SSL *ssl, void *arg)
268{
269 fr_tls_session_t *tls_session = fr_tls_session(ssl);
270 fr_tls_ticket_conf_t *tls_cache_conf = arg; /* Not talloced */
271 SSL_SESSION *sess;
273 fr_tls_conf_t *conf;
274 int ret;
275
276 /*
277 * Check to see if we have a request bound
278 * to the session. If we don't have a
279 * request there's no application data to
280 * add.
281 */
282 if (!fr_tls_session_request_bound(ssl)) return 1;
283
284 /*
285 * If the client doesn't support session tickets then there is no
286 * point in adding the application data - it won't get sent.
287 */
288 if (!tls_session->session_ticket_supported) return 1;
289
290 /*
291 * Encode the complete session state list
292 * as app data. Then, when the session is
293 * resumed, the session-state list is
294 * repopulated.
295 */
296 request = fr_tls_session_request(ssl);
297
298 /*
299 * Request was cancelled, don't do anything.
300 */
302
303 /*
304 * Fatal error - We definitely shouldn't be
305 * attempting to generate session tickets
306 * if it's not permitted.
307 */
308 if (!tls_session->allow_session_resumption ||
309 (!(tls_cache_conf->mode & FR_TLS_TICKET_STATELESS))) {
310 REDEBUG("(TLS) Generating session-tickets is not allowed");
311 fr_tls_session_error_add(request, FR_ERROR_VALUE_SESSION_TICKET_NOT_ALLOWED);
312 return 0;
313 }
314
315 sess = SSL_get_session(ssl);
316 if (!sess) {
317 REDEBUG("(TLS) Failed retrieving session in session generation callback");
318 return 0;
319 }
320
321 /*
322 * Run `encode session` to allow the admin to update the
323 * `session-state` list, before we encode it into a
324 * stateless session ticket.
325 */
326 conf = fr_tls_session_conf(ssl);
327
328 if (conf->encode_session && !tls_ticket_stateless_encode_run(request, tls_session)) {
329 REDEBUG("(TLS) Not generating a session-ticket");
330 return 0;
331 }
332
333 ret = tls_ticket_app_data_set(request, sess, &tls_session->session_id);
334 if (ret == 1) tls_session->session_ticket_created = true;
335
336 return ret;
337}
338
339/** Called when new tickets are being decoded
340 *
341 * This adds the session-state attributes back to the current request.
342 */
343static SSL_TICKET_RETURN tls_ticket_stateless_app_data_get(SSL *ssl, SSL_SESSION *sess,
344 UNUSED unsigned char const *keyname,
345 UNUSED size_t keyname_len,
346 SSL_TICKET_STATUS status,
347 void *arg)
348{
349 fr_tls_session_t *tls_session = fr_tls_session(ssl);
350 fr_tls_conf_t *conf = tls_session->conf;
351 fr_tls_ticket_conf_t *tls_cache_conf = arg; /* Not talloced */
352 request_t *request = NULL;
353
354 if (fr_tls_session_request_bound(ssl)) {
355 request = fr_tls_session_request(ssl);
356 if (unlang_request_is_cancelled(request)) return SSL_TICKET_RETURN_ABORT;
357 }
358
359 if (!tls_session->allow_session_resumption ||
360 (!(tls_cache_conf->mode & FR_TLS_TICKET_STATELESS))) {
361 ROPTIONAL(RDEBUG2, DEBUG2, "(TLS) Session resumption not enabled for this TLS session, "
362 "denying session resumption via session-ticket");
363 return SSL_TICKET_RETURN_IGNORE;
364 }
365
366 switch (status) {
367 case SSL_TICKET_EMPTY:
368 case SSL_TICKET_NO_DECRYPT:
369 case SSL_TICKET_FATAL_ERR_MALLOC:
370 case SSL_TICKET_FATAL_ERR_OTHER:
371 case SSL_TICKET_NONE:
372#ifdef STATIC_ANALYZER
373 default:
374#endif
375 return SSL_TICKET_RETURN_IGNORE_RENEW; /* Send a new ticket */
376
377 case SSL_TICKET_SUCCESS:
378 if (!request) return SSL_TICKET_RETURN_USE;
379 break;
380
381 case SSL_TICKET_SUCCESS_RENEW:
382 if (!request) return SSL_TICKET_RETURN_USE_RENEW;
383 break;
384 }
385
386 /*
387 * This restores the contents of &session-state[*]
388 * which hopefully still contains all the certificate
389 * pairs.
390 *
391 * Although the SSL_SESSION does contain a copy of
392 * the peer's certificate, it does not contain the
393 * peer's certificate chain, and so isn't reliable
394 * for performing re-validation.
395 *
396 * The session ticket includes a lifetime. But we didn't
397 * generate it, so we don't trust it. If the ticket has
398 * passed its lifetime, then we reject it and force full
399 * reauthentication.
400 */
401 if (!tls_ticket_session_resumable(request, &tls_session->session_id, conf, sess)) {
402 REDEBUG("(TLS) Session-ticket has too little life left, denying session resumption");
403 fr_tls_session_error_add(request, FR_ERROR_VALUE_SESSION_TICKET_EXPIRED);
404 return SSL_TICKET_RETURN_IGNORE_RENEW;
405 }
406
407 if (tls_ticket_app_data_get(request, sess, &tls_session->session_id) < 0) {
408 REDEBUG("(TLS) Denying session resumption via session-ticket");
409 return SSL_TICKET_RETURN_IGNORE_RENEW;
410 }
411
412 /*
413 * The session-state list is back. Give policy the chance to
414 * look at what the ticket carried before anything relies on
415 * it, certificate re-validation below included.
416 */
417 if (conf->decode_session && !tls_ticket_stateless_decode_run(request, tls_session)) {
418 REDEBUG("(TLS) Denying session resumption via session-ticket");
419 return SSL_TICKET_RETURN_IGNORE_RENEW;
420 }
421
422 if (conf->virtual_server && tls_session->verify_peer_cert) {
423 RDEBUG2("(TLS) Requesting certificate re-validation for session-ticket");
424 /*
425 * This sets the validation state of the tls_session
426 * so that when we call ASYNC_pause_job(), and execution
427 * jumps back to tls_session_async_handshake_cont
428 * (just under SSL_read())
429 * the code there knows what job it needs to push onto
430 * the unlang stack.
431 */
432 fr_tls_verify_resumed_request(tls_session);
433
434 /*
435 * Cache functions are only allowed during the handshake
436 * FIXME: With TLS 1.3 session tickets can be sent
437 * later... Technically every point where we call
438 * SSL_read() may need to be a yield point.
439 */
440 if (unlikely(!tls_session->can_pause)) {
441 fr_assert_msg("Unexpected call to %s. "
442 "tls_session_async_handshake_cont must be in call stack", __FUNCTION__);
443 return SSL_TICKET_RETURN_IGNORE_RENEW;
444 }
445
446 /*
447 * Jumps back to SSL_read() in session.c
448 *
449 * Be aware that if the request is cancelled
450 * whatever was meant to be done during the
451 * time we yielded may not have been completed.
452 */
453 ASYNC_pause_job();
454
455 /*
456 * If the request was cancelled get everything back into
457 * a known state.
458 */
460 fr_tls_verify_cert_reset(tls_session);
461 return SSL_TICKET_RETURN_ABORT;
462 }
463
464 /*
465 * If we couldn't validate the client certificate
466 * give the client the opportunity to send a new
467 * one, but _don't_ allow session resumption.
468 */
469 if (!fr_tls_verify_cert_result(tls_session)) {
470 RDEBUG2("(TLS) Certificate re-validation failed, denying session resumption via session-ticket");
471 return SSL_TICKET_RETURN_IGNORE_RENEW;
472 }
473 }
474
475 return (status == SSL_TICKET_SUCCESS_RENEW) ? SSL_TICKET_RETURN_USE_RENEW : SSL_TICKET_RETURN_USE;
476}
477
478/** Derive the session ticket key, and install the session ticket callbacks on an SSL_CTX
479 *
480 * The key the configuration gives is stretched to the length OpenSSL asks
481 * for with HKDF. The callbacks embed the session-state list in each session
482 * ticket the server issues, and extract the list again when a ticket is
483 * presented.
484 *
485 * @param[in] ctx to install the key and callbacks on.
486 * @param[in] cache_conf Session caching configuration, for the key and the callbacks.
487 * @return
488 * - 0 on success.
489 * - -1 on failure.
490 */
491int fr_tls_ticket_stateless_ctx_init(SSL_CTX *ctx, fr_tls_ticket_conf_t const *cache_conf)
492{
493 size_t key_len;
494 uint8_t *key_buff;
495 EVP_PKEY_CTX *pkey_ctx = NULL;
496
497 /*
498 * If keys is NULL, then OpenSSL returns the expected
499 * key length, which may be different across different
500 * flavours/versions of OpenSSL.
501 *
502 * We could calculate this in conf.c, but, if in future
503 * OpenSSL decides to use different key lengths based
504 * on other parameters in the ctx, that'd break.
505 */
506 key_len = SSL_CTX_set_tlsext_ticket_keys(ctx, NULL, 0);
507
508 if (unlikely((pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL)) == NULL)) {
509 fr_tls_strerror_printf(NULL);
510 PERROR("Failed initialising KDF");
511 kdf_error:
512 if (pkey_ctx) EVP_PKEY_CTX_free(pkey_ctx);
513 return -1;
514 }
515 if (unlikely(EVP_PKEY_derive_init(pkey_ctx) != 1)) {
516 fr_tls_strerror_printf(NULL);
517 PERROR("Failed initialising KDF derivation ctx");
518 goto kdf_error;
519 }
520 if (unlikely(EVP_PKEY_CTX_set_hkdf_md(pkey_ctx, UNCONST(struct evp_md_st *, EVP_sha256())) != 1)) {
521 fr_tls_strerror_printf(NULL);
522 PERROR("Failed setting KDF MD");
523 goto kdf_error;
524 }
525 if (unlikely(EVP_PKEY_CTX_set1_hkdf_key(pkey_ctx,
526 UNCONST(unsigned char *, cache_conf->session_ticket_key),
527 talloc_array_length(cache_conf->session_ticket_key)) != 1)) {
528 fr_tls_strerror_printf(NULL);
529 PERROR("Failed setting KDF key");
530 goto kdf_error;
531 }
532 if (unlikely(EVP_PKEY_CTX_add1_hkdf_info(pkey_ctx,
533 UNCONST(unsigned char *, "freeradius-session-ticket"),
534 sizeof("freeradius-session-ticket") - 1) != 1)) {
535 fr_tls_strerror_printf(NULL);
536 PERROR("Failed setting KDF label");
537 goto kdf_error;
538 }
539
540 /*
541 * SSL_CTX_set_tlsext_ticket_keys memcpys its
542 * inputs so this is just a temporary buffer.
543 */
544 MEM(key_buff = talloc_array(NULL, uint8_t, key_len));
545 if (EVP_PKEY_derive(pkey_ctx, key_buff, &key_len) != 1) {
546 fr_tls_strerror_printf(NULL);
547 PERROR("Failed deriving session ticket key");
548
549 key_buff_error:
550 talloc_free(key_buff);
551 goto kdf_error;
552 }
553 EVP_PKEY_CTX_free(pkey_ctx);
554 pkey_ctx = NULL;
555
556 fr_assert(talloc_array_length(key_buff) == key_len);
557
558 /*
559 * Ensure the same keys are used across all threads
560 */
561 if (SSL_CTX_set_tlsext_ticket_keys(ctx,
562 key_buff, key_len) != 1) {
563 fr_tls_strerror_printf(NULL);
564 PERROR("Failed setting session ticket keys");
565 goto key_buff_error;
566 }
567
568 DEBUG3("Derived session-ticket-key:");
569 HEXDUMP3(key_buff, key_len, NULL);
570 TALLOC_FREE(key_buff);
571
572 /*
573 * These callbacks embed and extract the
574 * session-state list from the session-ticket.
575 */
576 if (unlikely(SSL_CTX_set_session_ticket_cb(ctx,
577 tls_ticket_stateless_app_data_set,
578 tls_ticket_stateless_app_data_get,
579 UNCONST(fr_tls_ticket_conf_t *, cache_conf)) != 1)) {
580 fr_tls_strerror_printf(NULL);
581 PERROR("Failed setting session ticket callbacks");
582 return -1;
583 }
584
585 /*
586 * Stateless resumption is enabled by default when
587 * the TLS ctx is created, but OpenSSL sends too
588 * many session tickets by default (2), and we only
589 * need one.
590 */
591 SSL_CTX_set_num_tickets(ctx, 1);
592
593 return 0;
594}
595#endif /* WITH_TLS */
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_FAIL
Encountered an unexpected error.
Definition action.h:36
@ UNLANG_ACTION_CALCULATE_RESULT
Calculate a new section rlm_rcode_t value.
Definition action.h:38
static uint8_t request[4]
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:203
#define USES_APPLE_DEPRECATED_API
Definition build.h:590
#define RCSID(id)
Definition build.h:603
#define unlikely(_x)
Definition build.h:486
#define UNUSED
Definition build.h:415
#define fr_assert_msg(_x, _msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition debug.h:248
#define MEM(x)
Definition debug.h:38
talloc_free(hp)
bool unlang_request_is_cancelled(request_t const *request)
Return whether a request has been cancelled.
Definition interpret.c:1971
#define PERROR(_fmt,...)
Definition log.h:233
#define DEBUG3(_fmt,...)
Definition log.h:271
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition log.h:545
#define HEXDUMP3(_data, _len, _fmt,...)
Definition log.h:740
HIDDEN fr_dict_attr_t const * attr_tls_packet_type
fr_value_box_t const * enum_tls_packet_type_success
fr_value_box_t const * enum_tls_packet_type_decode_session
fr_value_box_t const * enum_tls_packet_type_encode_session
unsigned int uint32_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:707
#define fr_assert(_expr)
Definition rad_assert.h:37
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
#define DEBUG2(fmt,...)
static rs_t * conf
Definition radsniff.c:52
static char const * name
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68