The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
session.h
Go to the documentation of this file.
1#pragma once
2/*
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
16 */
17#ifdef WITH_TLS
18/**
19 * $Id: ee1042d64b3026e0fd14ae089dd1104398ca435c $
20 *
21 * @file lib/tls/session.h
22 * @brief Structures for session-resumption management.
23 *
24 * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
25 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
26 */
27RCSIDH(session_h, "$Id: ee1042d64b3026e0fd14ae089dd1104398ca435c $")
28
29#include "openssl_user_macros.h"
30
31#include <openssl/ssl.h>
32#include <openssl/err.h>
33
34typedef struct fr_tls_session_s fr_tls_session_t;
35
36#include <freeradius-devel/server/request.h>
37#include <freeradius-devel/util/dbuff.h>
38
39#include "bio.h"
40#include "ticket.h"
41#include "conf.h"
42#include "index.h"
43#include "verify.h"
44
45#ifdef __cplusplus
46extern "C" {
47#endif
48
49/*
50 * A single TLS record carries at most SSL3_RT_MAX_PLAIN_LENGTH
51 * octets of plaintext, which OpenSSL defines as 16384. A TLS
52 * message may span multiple TLS records, and a TLS certificate
53 * message may in principle be as long as 16MB.
54 *
55 * However, note that in order to protect against reassembly
56 * lockup and denial of service attacks, it may be desirable for
57 * an implementation to set a maximum size for one such group of
58 * TLS messages.
59 *
60 * The TLS Message Length field is four octets, and provides the
61 * total length of the TLS message or set of messages that is
62 * being fragmented, which simplifies buffer allocation.
63 *
64 * A TLS record adds data on top of the application-layer
65 * plaintext: the TLS header (5 octets); encryption overhead
66 * (SSL3_RT_MAX_ENCRYPTED_OVERHEAD which is 256 octets of padding
67 * plus a 64 octet MAC); where OpenSSL was built with
68 * compression 1024 octets.
69 *
70 * SSL3_RT_MAX_PACKET_SIZE therefore depends on the local OpenSSL
71 * build, and build flags. Generally 17733 with compression,
72 * 16709 without. We round up on general principle.
73 */
74#define FR_TLS_MAX_PACKET_SIZE ((SSL3_RT_MAX_PACKET_SIZE + 255) & ~255)
75
76/*
77 * Cap the maximum number of handshakes that we receive in a row.
78 * If we don't make progress, then either the certificates are
79 * enormous, or the TLS chunks are deliberately small, or the
80 * other end is trying to catch us in an infinite ACK / ACK loop.
81 *
82 * The EAP code also caps the number of rounds it does, but there
83 * are non-TLS EAP methods which can use multiple rounds. We
84 * need both limits in order to catch all corner cases. Note
85 * also that TLS-based EAP methods will ACK each _fragment_ of a
86 * TLS record. So one TLS "round" could map to multple EAP
87 * "rounds".
88 */
89#define FR_TLS_MAX_ROUNDS 50U
90
91/*
92 * FIXME: Dynamic allocation of buffer to overcome SSL3_RT_MAX_PLAIN_LENGTH overflows.
93 * or configure TLS not to exceed SSL3_RT_MAX_PLAIN_LENGTH.
94 *
95 * clean_in and clean_out are dbuffs over a fixed
96 * SSL3_RT_MAX_PLAIN_LENGTH allocation. The buffers should not be
97 * extensible, as doing so could allow the peer to send unlimited data.
98 *
99 * dirty_in and dirty_out allow for extensions. We therefore can't
100 * call the fill/drain helpers below on those buffers. Calling
101 * fr_tls_record_init() on one would re-init the dbuff and lose the
102 * talloc context which lets the buffer extend.
103 */
104
105/** Reset a record buffer so that it can be filled again
106 *
107 * A record buffer is filled, then drained. While the buffer is filling, the
108 * dbuff runs from the start of the buffer to the end of the memory, the
109 * current position is where the next octet is written, and fr_dbuff_used()
110 * says how many octets are in the buffer.
111 *
112 * Resetting returns the buffer to the filling state, and discards whatever
113 * the buffer held.
114 *
115 * @param[in] record to reset.
116 */
117static inline void fr_tls_record_init(fr_dbuff_t *record)
118{
119 fr_dbuff_init(record, fr_dbuff_start(record), (size_t) SSL3_RT_MAX_PLAIN_LENGTH);
120}
121
122/** Stop filling a record buffer, and start draining it
123 *
124 * While the buffer is draining, the dbuff runs from the start of the buffer
125 * to the end of the data which was written, the current position is where the
126 * next octet is read, and fr_dbuff_remaining() says how many octets are left
127 * to read.
128 *
129 * The buffer must not be written to while it is draining. Call
130 * fr_tls_record_init() to fill it again.
131 *
132 * @param[in] record to start draining.
133 */
134static inline void fr_tls_record_drain(fr_dbuff_t *record)
135{
136 size_t used = fr_dbuff_used(record);
137
138 fr_dbuff_init(record, fr_dbuff_start(record), used);
139}
140
141typedef enum : uint8_t {
142 TLS_INFO_ORIGIN_RECORD_RECEIVED,
143 TLS_INFO_ORIGIN_RECORD_SENT
144} fr_tls_info_origin_t;
145
146typedef struct {
147 int origin;
148 int content_type;
149 uint8_t handshake_type;
150 uint8_t alert_level;
151 uint8_t alert_description;
152 bool initialized;
153
154 size_t record_len;
155 int version; //!< NOT to be trusted! Use SSL_version(), see session.c
156
157 char info_description[256];
158} fr_tls_info_t;
159
160/** Result of the last operation on the session
161 *
162 * This is needed to record the result of an asynchronous
163 */
164typedef enum : uint8_t {
165 FR_TLS_RESULT_IN_PROGRESS = 0x00, //!< Handshake round in progress.
166 FR_TLS_RESULT_ERROR = 0x01, //!< Handshake failed.
167 FR_TLS_RESULT_SUCCESS = 0x02 //!< Handshake round succeed.
168} fr_tls_result_t;
169
170#ifdef PSK_MAX_IDENTITY_LEN
171/** State of the `load psk` call that the PSK callback requested
172 *
173 */
174typedef enum : uint8_t {
175 FR_TLS_PSK_INIT = 0, //!< No call is pending.
176 FR_TLS_PSK_REQUESTED, //!< `load psk` needs to run. The PSK callback has
177 ///< paused the handshake until the section finishes.
178 FR_TLS_PSK_SUCCESS, //!< `load psk` returned a key, and `key` holds the key.
179 FR_TLS_PSK_FAILED //!< `load psk` failed, or did not return a usable key.
180} fr_tls_psk_state_t;
181
182/** The `load psk` call that the PSK callback requested, and the key that the section returned
183 *
184 */
185typedef struct {
186 fr_tls_psk_state_t state; //!< Whether the call is pending, finished, or failed.
187 unsigned int max_psk_len; //!< The longest key that OpenSSL accepts, copied from
188 ///< the `max_psk_len` argument of the PSK callback.
189 uint8_t *key; //!< The key from `reply.TLS-PSK-Key`, copied out of the
190 ///< subrequest before the subrequest is freed.
191 size_t key_len; //!< Length of `key`.
192} fr_tls_psk_t;
193#endif
194
195/** Policy sections that an OpenSSL callback has asked the handshake to run
196 *
197 * A callback cannot run the unlang interpreter, so the callback raises a bit
198 * in `pending`, and pauses the handshake. tls_session_async_handshake_cont()
199 * pushes the section for each raised bit, lowest bit first, and only resumes
200 * the handshake once no bit is raised. Each bit is one shot, cleared before
201 * the push function for the bit is called. Each section writes the result
202 * of the section to the state for that section (`cache`, `ticket`, `psk`, or
203 * `validate`), not to `pending`.
204 */
205DIAG_OFF(attributes)
206typedef enum CC_HINT(flag_enum) : uint64_t {
207 FR_TLS_PENDING_STATEFUL_TICKET = 0x01, //!< `load session { ... }`, `store session { ... }`
208 ///< or `clear session { ... }`, for the session cache.
209 FR_TLS_PENDING_STATELESS_TICKET = 0x02, //!< `encode session { ... }` or `decode session { ... }`,
210 ///< for a stateless session ticket.
211 FR_TLS_PENDING_PSK = 0x04, //!< `load psk { ... }`
212 FR_TLS_PENDING_VERIFY = 0x08 //!< `verify certificate { ... }`
213} fr_tls_pending_t;
214DIAG_ON(attributes)
215
216#ifdef _TLS_PRIVATE
217/** Ask the handshake to run a section once the handshake has paused
218 */
219#define TLS_PENDING_SET(_tls_session, _bit) ((_tls_session)->pending |= (_bit))
220
221/** Clear a bit before the push function for the bit is called
222 */
223#define TLS_PENDING_CLEAR(_tls_session, _bit) ((_tls_session)->pending &= ~(_bit))
224#endif
225
226/** Tracks the state of a TLS session
227 *
228 * Currently used for RADSEC and EAP-TLS + dependents (EAP-TTLS, EAP-PEAP etc...).
229 *
230 * In the case of EAP-TLS + dependents a #eap_tls_session_t struct is used to track
231 * the transfer of TLS records.
232 */
233struct fr_tls_session_s {
234 fr_tls_conf_t *conf; //!< The parsed "tls" section this session was
235 ///< built from. It does not change, so it is
236 ///< recorded here rather than fetched from
237 ///< OpenSSL's ex_data on every use.
238
239 SSL_CTX *ctx; //!< TLS configuration context.
240 SSL *ssl; //!< This SSL session.
241 SSL_SESSION *session; //!< Session resumption data.
242 fr_tls_result_t result; //!< Result of the last handshake round.
243 fr_value_box_t session_id; //!< ID of the session
244
245 fr_tls_info_t info; //!< Information about the state of the TLS session.
246
247 fr_tls_bio_dbuff_t *into_ssl; //!< Encrypted data from the peer, which OpenSSL reads.
248 fr_tls_bio_dbuff_t *from_ssl; //!< Encrypted data OpenSSL wrote, waiting to be sent.
249 fr_dbuff_t clean_in; //!< Cleartext data that needs to be encrypted.
250 fr_dbuff_t clean_out; //!< Decrypted cleartext, for the caller to read.
251 fr_dbuff_t *dirty_in; //!< Encrypted data to decrypt. The producer
252 ///< cursor of into_ssl, which the caller fills.
253 fr_dbuff_t *dirty_out; //!< Encrypted data, ready to send. The consumer
254 ///< cursor of from_ssl, which the caller drains.
255 int last_ret; //!< Last result returned by SSL_read().
256
257 uint32_t rounds; //!< Handshake round trips.
258
259 size_t mtu; //!< Maximum record fragment size.
260
261 int socket_type; //!< SOCK_STREAM for TLS, SOCK_DGRAM for DTLS.
262 ///< Taken from the context the session was
263 ///< allocated against, so that the rest of the
264 ///< code reads a field rather than asking
265 ///< OpenSSL the same question repeatedly.
266
267 fr_socket_t const *cookie; //!< What the DTLS cookie is bound to: the source
268 ///< and destination of the connection, which is
269 ///< how the rest of the server identifies a DTLS
270 ///< session. Set by the application, and hashed
271 ///< but never read by the library.
272
273 void *opaque; //!< Used to store module specific data.
274
275 unsigned char *alpn; //!< Protocol name both ends agreed on, as
276 ///< SSL_get0_alpn_selected() gives it: the name
277 ///< alone, with no leading length octet.
278 size_t sizeof_alpn; //!< Length of `alpn`.
279
280 fr_tls_ticket_stateful_t *cache; //!< Current session resumption state.
281 bool allow_session_resumption; //!< Whether session resumption is allowed.
282 bool verify_peer_cert; //!< Whether verification of the peer's certificate
283 ///< has been requested.
284
285 fr_tls_pending_t pending; //!< Policy sections that an OpenSSL callback has
286 ///< asked the handshake to run.
287
288 fr_tls_verify_t validate; //!< Current session certificate validation state.
289
290 fr_tls_ticket_stateless_state_t ticket; //!< Whether `encode session` or `decode session`
291 ///< is waiting to run, and what it returned.
292
293#ifdef PSK_MAX_IDENTITY_LEN
294 fr_tls_psk_t psk; //!< Whether `load psk` is waiting to run, and what
295 ///< `load psk` returned.
296#endif
297
298 bool invalid; //!< Whether heartbleed attack was detected.
299
300 bool write_encrypted; //!< Whether the records we write are encrypted.
301 ///< which lets us track if we can send an alert
302
303 bool peer_cert_ok; //!< Whether the peer's certificate was validated
304 bool seen_application_data; //!< Application data has started moving, which
305 ///< indicates that all session tickets have been sent.
306
307 bool session_ticket_supported; //!< Did the client present a session_ticket in the hello.
308
309 bool session_ticket_created; //!< The stateless session ticket creation callback has
310 ///< successfully run.
311
312 bool session_ticket_received; //!< A client has seen the NewSessionTicket which
313 ///< a TLS 1.3 server sends after the handshake.
314
315 bool can_pause; //!< If true, it's ok to pause the request
316 ///< using the OpenSSL async API.
317
318 bool sent_commitment; //!< Has the commitment message been sent.
319
320 uint8_t alerts_sent;
321 bool pending_alert;
322 uint8_t pending_alert_level;
323 uint8_t pending_alert_description;
324
325 fr_pair_list_t extra_pairs; //!< Pairs to add to cache and certificate validation
326 ///< calls. These will be duplicated for every call.
327};
328
329/** Return the tls config associated with a tls_session
330 *
331 * @param[in] ssl to retrieve the configuration from.
332 * @return #fr_tls_conf_t associated with the session.
333 */
334static inline fr_tls_conf_t *fr_tls_session_conf(SSL *ssl)
335{
336 return talloc_get_type_abort(SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF), fr_tls_conf_t);
337}
338
339/** Return the tls_session associated with a SSL *
340 *
341 * @param[in] ssl to retrieve the configuration from.
342 * @return #fr_tls_conf_t associated with the session.
343 */
344static inline fr_tls_session_t *fr_tls_session(SSL *ssl)
345{
346 return talloc_get_type_abort(SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TLS_SESSION), fr_tls_session_t);
347}
348
349/** Check to see if a request is bound to a session
350 *
351 * @param[in] ssl session to check for requests.
352 * @return
353 * - true if a request is bound to this session.
354 * - false if a request is not bound to this session.
355 */
356static inline CC_HINT(nonnull) bool fr_tls_session_request_bound(SSL *ssl)
357{
358 return (SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST) != NULL);
359}
360
361/** Return the request associated with a ssl session
362 *
363 * @param[in] ssl session to retrieve the configuration from.
364 * @return #request associated with the session.
365 */
366static inline request_t *fr_tls_session_request(SSL const *ssl)
367{
368 request_t *request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST);
369
370 if (!request) return NULL;
371
372 return talloc_get_type_abort(SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST), request_t);
373}
374
375static inline CC_HINT(nonnull) void _fr_tls_session_request_bind(char const *file, int line,
376 SSL *ssl, request_t *request)
377{
378 int ret;
379
380 RDEBUG3("(TLS) %s[%d] - Binding SSL * (%p) to request (%p)", file, line, ssl, request);
381
382#ifndef NDEBUG
383 {
384 request_t *old;
385 old = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST);
386 if (old) {
387 (void)talloc_get_type_abort(old, request_t);
388 fr_assert(0);
389 }
390 }
391#endif
392 ret = SSL_set_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST, request);
393 if (unlikely(ret == 0)) {
394 fr_assert(0);
395 return;
396 }
397}
398/** Place a request pointer in the SSL * for retrieval by callbacks
399 *
400 * @note A request must not already be bound to the SSL *
401 *
402 * @param[in] ssl to be bound.
403 * @param[in] request to bind to the tls_session.
404 */
405 #define fr_tls_session_request_bind(_ssl, _request) _fr_tls_session_request_bind(__FILE__, __LINE__, _ssl, _request)
406
407static inline CC_HINT(nonnull) void _fr_tls_session_request_unbind(char const *file, int line, SSL *ssl)
408{
409 request_t *request = fr_tls_session_request(ssl);
410 int ret;
411
412 if (!request) return;
413
414#ifndef NDEBUG
415 (void)talloc_get_type_abort(request, request_t);
416#endif
417
418 RDEBUG3("(TLS) %s[%d] - Unbinding SSL * (%p) from request (%p)", file, line, ssl, request);
419 ret = SSL_set_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST, NULL);
420 if (unlikely(ret == 0)) {
421 fr_assert(0);
422 return;
423 }
424}
425/** Remove a request pointer from the tls_session
426 *
427 * @note A request must be bound to the tls_session
428 *
429 * @param[in] ssl session containing the request pointer.
430 */
431#define fr_tls_session_request_unbind(_ssl) _fr_tls_session_request_unbind(__FILE__, __LINE__, _ssl)
432
433/** Add extra pairs to the temporary subrequests
434 *
435 * @param[in] child to add extra pairs to.
436 * @param[in] tls_session to add extra pairs from.
437 */
438static inline CC_HINT(nonnull)
439void fr_tls_session_extra_pairs_copy_to_child(request_t *child, fr_tls_session_t *tls_session)
440{
441 if (!fr_pair_list_empty(&tls_session->extra_pairs)) {
442 MEM(fr_pair_list_copy(child->request_ctx, &child->request_pairs, &tls_session->extra_pairs) >= 0);
443 }
444}
445
446/** Add an additional pair (copying it) to the list of extra pairs
447 *
448 * @param[in] tls_session to add extra pairs to.
449 * @param[in] vp to add to tls_session.
450 */
451static inline CC_HINT(nonnull)
452void fr_tls_session_extra_pair_add(fr_tls_session_t *tls_session, fr_pair_t *vp)
453{
454 fr_pair_t *copy;
455
456 MEM(copy = fr_pair_copy(tls_session, vp));
457 fr_pair_append(&tls_session->extra_pairs, copy);
458}
459
460/** Add an additional pair to the list of extra pairs
461 *
462 * @param[in] tls_session to add extra pairs to.
463 * @param[in] vp to add to tls_session.
464 */
465static inline CC_HINT(nonnull)
466void fr_tls_session_extra_pair_add_shallow(fr_tls_session_t *tls_session, fr_pair_t *vp)
467{
468 fr_assert(talloc_parent(vp) == tls_session);
469 fr_pair_append(&tls_session->extra_pairs, vp);
470}
471
472/** How many octets the datagram at the head of `dirty_out` holds
473 *
474 * Only meaningful for a datagram session. A stream session writes everything
475 * which is waiting, and never asks.
476 *
477 * @param[in] tls_session to read.
478 * @return
479 * - the length of the next datagram.
480 * - 0 if no whole datagram is waiting.
481 */
482static inline CC_HINT(nonnull) size_t fr_tls_session_datagram_len(fr_tls_session_t *tls_session)
483{
484 return fr_tls_bio_dbuff_datagram_len(tls_session->from_ssl);
485}
486
487/** Discard the datagram at the head of `dirty_out`, which has now been sent
488 *
489 * The caller advances `dirty_out` itself, so the cursor and the boundary move
490 * together.
491 *
492 * @param[in] tls_session to advance.
493 */
494static inline CC_HINT(nonnull) void fr_tls_session_datagram_sent(fr_tls_session_t *tls_session)
495{
496 fr_tls_bio_dbuff_datagram_sent(tls_session->from_ssl);
497}
498
499int fr_tls_session_password_cb(char *buf, int num, int rwflag, void *userdata);
500
501unsigned int fr_tls_session_psk_client_cb(SSL *ssl, UNUSED char const *hint,
502 char *identity, unsigned int max_identity_len,
503 unsigned char *psk, unsigned int max_psk_len);
504
505unsigned int fr_tls_session_psk_server_cb(SSL *ssl, const char *identity,
506 unsigned char *psk, unsigned int max_psk_len);
507
508#ifdef PSK_MAX_IDENTITY_LEN
509unlang_action_t fr_tls_session_psk_pending_push(request_t *request, fr_tls_session_t *tls_session);
510#endif
511
512void fr_tls_session_info_cb(SSL const *s, int where, int ret);
513
514void fr_tls_session_msg_cb(int write_p, int msg_version, int content_type,
515 void const *buf, size_t len, SSL *ssl, void *arg);
516
517void fr_tls_session_keylog_cb(const SSL *ssl, const char *line);
518
519int fr_tls_session_pairs_from_x509_cert(fr_pair_list_t *pair_list, TALLOC_CTX *ctx,
520 request_t *request, X509 *cert, X509 *issuer,
521 bool der_decode) CC_HINT(nonnull(1,2,3,4));
522
523int fr_tls_session_client_hello_pairs(SSL *ssl, request_t *request);
524
525bool fr_tls_session_is_init_finished(fr_tls_session_t *tls_session);
526
527int fr_tls_session_recv(request_t *request, fr_tls_session_t *tls_session);
528
529int fr_tls_session_send(request_t *request, fr_tls_session_t *tls_session);
530
531int fr_tls_session_alert(request_t *request, fr_tls_session_t *tls_session, uint8_t level, uint8_t description);
532
533void fr_tls_session_error_alert(request_t *request, fr_tls_session_t *session,
534 uint32_t error, uint8_t description);
535
536void fr_tls_session_close_send(request_t *request, fr_tls_session_t *session);
537void fr_tls_session_error_add(request_t *request, uint32_t error);
538
539unlang_action_t fr_tls_session_async_handshake_push(request_t *request, fr_tls_session_t *tls_session);
540
541fr_tls_session_t *fr_tls_session_alloc_client(TALLOC_CTX *ctx, SSL_CTX *ssl_ctx, request_t *request);
542
543fr_tls_session_t *fr_tls_session_alloc_server(TALLOC_CTX *ctx, SSL_CTX *ssl_ctx, request_t *request, bool client_cert);
544
545unlang_action_t fr_tls_new_session_push(request_t *request, fr_tls_conf_t const *tls_conf);
546
547unlang_action_t fr_tls_session_fail_session(request_t *request, fr_tls_session_t *tls_session);
548
549#ifdef __cplusplus
550}
551#endif
552#endif /* WITH_TLS */
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
void int line
Definition acutest.h:810
static uint8_t request[4]
#define DIAG_ON(_x)
Definition build.h:576
#define RCSIDH(h, id)
Definition build.h:604
#define unlikely(_x)
Definition build.h:486
#define UNUSED
Definition build.h:415
#define DIAG_OFF(_x)
Definition build.h:575
#define fr_dbuff_used(_dbuff_or_marker)
Return the number of bytes remaining between the start of the dbuff or marker and the current positio...
Definition dbuff.h:819
#define fr_dbuff_init(_out, _start, _len_or_end)
Initialise an dbuff for encoding or decoding.
Definition dbuff.h:387
#define fr_dbuff_start(_dbuff_or_marker)
Return the 'start' position of a dbuff or marker.
Definition dbuff.h:950
#define MEM(x)
Definition debug.h:38
#define RDEBUG3(fmt,...)
Definition log.h:360
unsigned int uint32_t
unsigned char uint8_t
static size_t used
int fr_pair_list_copy(TALLOC_CTX *ctx, fr_pair_list_t *to, fr_pair_list_t const *from)
Duplicate a list of pairs.
Definition pair.c:2303
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1297
fr_pair_t * fr_pair_copy(TALLOC_CTX *ctx, fr_pair_t const *vp)
Copy a single valuepair.
Definition pair.c:500
#define fr_assert(_expr)
Definition rad_assert.h:37
static rs_t * conf
Definition radsniff.c:52
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
Holds information necessary for binding or connecting to a socket.
Definition socket.h:60
static fr_sbuff_err_t char size_t * len
Definition value.h:1061
int nonnull(2, 5))