Radcli library 2.0.0
A simple radius library -- new API reference
Loading...
Searching...
No Matches
dae.c
1/*
2 * Copyright (C) 2026 Nikos Mavrogiannopoulos
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
28
29/* radcli2.h's RFC 5176 dynamic-authorization listener (radcli_dae_new()/
30 * _set_handler()/_start()/_free(), plus the ctx-level poll surface
31 * radcli_ctx_get_poll()/radcli_ctx_dispatch(), doc/requirements/dae.md's
32 * INIT/NET/SEC/DATA categories).
33 *
34 * radcli_dae_new() validates the dae-* configuration and resolves every
35 * dae-server entry to concrete addresses, but opens no socket
36 * (REQ-DAE-INIT-*); radcli_dae_start() does that, non-blocking and
37 * close-on-exec (REQ-DAE-SEC-010/011). There is deliberately no
38 * radcli_dae_fd(): the descriptor is exposed only via
39 * radcli_ctx_get_poll(), which operates on ctx rather than on any one
40 * radcli_dae, so a future dynamic-authorization transport that shares one
41 * descriptor with the ordinary request path (rather than a separate
42 * UDP/3799 listener) never leaves an application holding two accessors
43 * that alias the same descriptor -- see radcli_ctx_get_poll()'s doc
44 * comment in radcli2.h. radcli never calls poll()/select()/epoll_wait()
45 * itself (REQ-NET2-NET-001, REQ-GEN-SEC-003).
46 *
47 * radcli_ctx_dispatch() runs the full validation pipeline (REQ-NET2-NET-002)
48 * before ever invoking the registered radcli_dae_handler: source-address
49 * authorization, Request Authenticator, Message-Authenticator, Event-
50 * Timestamp freshness, then duplicate suppression against a fixed
51 * 256-slot table shared by every configured dae-server entry
52 * (REQ-DAE-SEC-001..006). Session-selector convenience accessors
53 * (radcli_dae_req_session_id()/_user_name()/_framed_ip()/_nas_port()/
54 * _check_nas(), doc/requirements/dae.md's DATA category) are implemented
55 * below; an application can still reach the same data via
56 * radcli_dae_req_attrs() directly instead. */
57
58#include <config.h>
59#include <includes.h>
60#include <radcli/radcli.h>
61#include <radcli/radcli2.h>
62#include "util.h"
63#include "avp.h"
64#include "options.h"
65#include "rc-crypto.h"
66#include "rc-random.h"
67#include <poll.h>
68#include <fcntl.h>
69#include <time.h>
70#include <pthread.h>
71
72#define RADCLI_DAE_DEFAULT_PORT 3799
73/* Sane bound on the number of addresses dae-server's entries resolve to --
74 * not a protocol limit, just parse-loop hygiene against an operator config
75 * mistake (e.g. a hostname with an unexpectedly large RRset). */
76#define RADCLI_DAE_MAX_DACS 64
77
78/* Duplicate suppression is keyed on (Identifier, Request Authenticator)
79 * alone; Identifier is one octet, so this is the whole space -- a fixed
80 * table, not a cache with an eviction policy (REQ-DAE-SEC-005). RFC 5176
81 * SS2.3 names source address and source port as part of the tuple too, but
82 * neither is part of what the Request/Message-Authenticator actually hash
83 * (Code+Identifier+Length+Authenticator+Attributes+Secret): the source
84 * address is not cryptographically bound to the packet at all, so it was
85 * never real proof of anything -- only the Authenticator, keyed on the
86 * shared secret, is. One shared table per listener (not one per configured
87 * dae-server entry) follows from that: source port was dropped from the
88 * match first (an off-path attacker replaying one captured packet controls
89 * the port freely, so requiring it to match let the packet be replayed as
90 * "new" from a fresh port indefinitely); partitioning the table per
91 * configured sender address reopened the identical bypass one level up,
92 * since dae-server entries commonly share one secret (no per-entry
93 * :secret override) -- a captured packet replayed with the source address
94 * spoofed to a DIFFERENT configured entry sharing that secret still
95 * verified, but landed in that entry's own empty table and was delivered
96 * as new. A single shared table closes both: whichever configured sender
97 * the replay is spoofed as, the same dedup key lands in the same slot.
98 * (A genuine collision between two different, unrelated packets would need
99 * the 256-bit SHA-256 dedup key -- REQ-DAE-SEC-005, computed by
100 * process_packet() itself over the verified packet, not the wire's 128-bit
101 * MD5 Request Authenticator -- to coincide by chance: not a practical
102 * concern.) */
103#define RADCLI_DAE_SLOTS 256
104
105struct radcli_dae_slot {
106 unsigned valid;
107 time_t timestamp;
108 uint8_t dedup_key[RC_SHA256_DIGEST_SIZE]; /* REQ-DAE-SEC-005: an
109 * SHA-256 digest process_
110 * packet() computes itself
111 * over the verified packet
112 * -- not the wire's MD5
113 * Request Authenticator. */
114 unsigned pending; /* 1 = PENDING (awaiting an application decision) */
115 uint8_t reply_code;
116 uint32_t error_cause; /* 0 = no Error-Cause attribute (an ACK) */
117};
118
119struct radcli_dae_dac {
120 struct sockaddr_storage addr;
121 socklen_t addrlen;
122 char *secret; /* NULL => use radcli_dae_st.secret */
123};
124
125/* Fixed bound on radcli2_priv_dae_on_radsec_packet()'s queue of validated
126 * RadSec requests awaiting delivery via radcli_ctx_dispatch() -- never
127 * grown at run time, same "no unbounded packet-driven allocation"
128 * principle as RADCLI_DAE_SLOTS/RADCLI_DAE_MAX_DACS above. In practice
129 * this holds at most one entry almost always: radcli_transport_exchange()
130 * serializes the whole RadSec session behind one lock for an entire
131 * send-and-wait cycle (lib/sendserver.c), so only one thread is ever
132 * reading the wire at a time. A small bound still exists for the case
133 * where the application is slow to call radcli_ctx_dispatch() while
134 * several requests arrive; overflow drops the oldest queued entry and
135 * logs, rather than growing without limit. */
136#define RADCLI_DAE_RADSEC_QUEUE_SIZE 8
137
138/* REQ-DAE-SEC-013: bound on radcli_ctx_dispatch()'s queue of RadSec
139 * replies (ACK/NAK) that could not be sent immediately without blocking
140 * (radcli2_priv_tls_dae_send() returned "would block") -- send_reply()
141 * defers to this queue instead of waiting, so a slow-reading DAC cannot
142 * turn a dispatch() call into a multi-second stall of the caller's event
143 * loop. A fixed byte size per slot, not RC_BUFFER_LEN: every DAE reply is
144 * a header plus at most one Error-Cause (6 bytes) and Proxy-State
145 * attributes mirrored from the request (RFC 5176 SS3, bounded by whatever
146 * the request itself carried) plus a Message-Authenticator (18 bytes) --
147 * 512 bytes is generous headroom over any of this without the ~8KB
148 * per-slot cost RC_BUFFER_LEN would add for a queue that is expected to
149 * hold at most a handful of entries at once. */
150#define RADCLI_DAE_RADSEC_REPLY_QUEUE_SIZE 8
151#define RADCLI_DAE_RADSEC_REPLY_MAX_LEN 512
152
153struct radcli_dae_pending_reply {
154 uint8_t buf[RADCLI_DAE_RADSEC_REPLY_MAX_LEN];
155 size_t len;
156};
157
158struct radcli_dae_st {
159 rc_handle *rh;
160 char *secret;
161 struct radcli_dae_dac *dacs;
162 unsigned n_dacs;
163 struct radcli_dae_slot *slots; /* RADCLI_DAE_SLOTS entries, shared by
164 * every configured dae-server entry --
165 * see RADCLI_DAE_SLOTS's comment. */
166 int max_clock_skew;
167 int require_message_authenticator;
168 unsigned no_nas_check; /* RADCLI_DAE_NO_NAS_CHECK passed to radcli_dae_new() */
169 char *listen_host; /* NULL => any address */
170 int listen_port;
171 int fd;
172 radcli_dae_handler handler;
173 void *handler_user;
174
175 /* Set at construction when dae-accept=yes follows serv-type=tls/dtls
176 * (radcli_dae_new()): CoA/Disconnect flow over rh's own established
177 * TLS/DTLS session (lib/tls.c's tls_recvfrom() demux,
178 * radcli2_priv_dae_on_radsec_packet()) instead of a dae-owned UDP
179 * listener -- dacs/n_dacs/secret/listen_host/listen_port/fd above are
180 * all unused in this mode (dae->fd stays -1 throughout). */
181 unsigned radsec;
182 unsigned started; /* radcli_dae_start() already called -- radsec mode
183 * has no fd to test "already started" against. */
184
185 /* radcli2_priv_dae_on_radsec_packet()'s queue -- see
186 * RADCLI_DAE_RADSEC_QUEUE_SIZE's comment. FIFO: radsec_queue[0] is
187 * the oldest still-undelivered request. Protected by radsec_lock:
188 * radcli2_priv_dae_on_radsec_packet() can run on whatever thread is
189 * currently inside radcli_transport_exchange() (tls_recvfrom()'s
190 * inline demux, holding the *session's* lock, lib/tls.c) at the exact
191 * same time radcli_ctx_dispatch()'s poll thread (having already
192 * released the session lock -- radcli2_priv_tls_dae_poll() only holds
193 * it for the read itself) is draining the queue or processing a
194 * record of its own -- the session lock alone does not serialize
195 * these two call sites against each other. process_packet()'s own
196 * duplicate-suppression table (dae->slots[]) needs the same
197 * protection for the identical reason, so this lock is held around
198 * every radsec-mode call into process_packet() too, not just the
199 * queue operations. */
200 struct radcli_dae_request_st *radsec_queue[RADCLI_DAE_RADSEC_QUEUE_SIZE];
201 unsigned radsec_queue_len;
202
203 /* REQ-DAE-SEC-013: outbound replies deferred by send_reply() when
204 * radcli2_priv_tls_dae_send() reports the send would block. FIFO,
205 * drained in order (a later reply must not overtake an earlier one
206 * still waiting) by radsec_flush_reply_queue(), called from
207 * radcli_ctx_dispatch()'s radsec branch. Also protected by
208 * radsec_lock, for the same reason radsec_queue[] above is. */
209 struct radcli_dae_pending_reply radsec_reply_queue[RADCLI_DAE_RADSEC_REPLY_QUEUE_SIZE];
210 unsigned radsec_reply_queue_len;
211
212 pthread_mutex_t radsec_lock;
213};
214
215struct radcli_dae_request_st {
216 struct radcli_dae_st *dae;
217 struct radcli_dae_dac *dac; /* the authorized sender this arrived from */
218 uint8_t code;
219 uint8_t id;
220 uint8_t request_authenticator[AUTH_VECTOR_LEN];
221 uint8_t dedup_key[RC_SHA256_DIGEST_SIZE]; /* REQ-DAE-SEC-005: SHA-256
222 * over the verified packet,
223 * snapshotted so a later
224 * record_reply_decision()
225 * call can re-verify the
226 * slot it claimed is still
227 * this same request's. */
228 struct sockaddr_storage from;
229 socklen_t fromlen;
230 char secret[MAX_SECRET_LENGTH + 1]; /* dac->secret or dae->secret, copied in */
231 radcli_avp_list *attrs;
232 unsigned replied;
233 char *session_id; /* Acct-Session-Id, NUL-terminated; NULL if absent */
234 char *user_name; /* User-Name, NUL-terminated; NULL if absent */
235
236 /* Set when this request represents a retransmission radcli_dae_process()
237 * (L0) already matched to an ANSWERED duplicate-suppression slot: there
238 * is no new decision to make, so radcli_dae_reply()/_reply_error() are
239 * meaningless (and, for an L0-only dae with no socket, would fail
240 * outright), and radcli_dae_reply_to_buffer() reproduces the cached
241 * decision snapshotted here instead of the caller's ack/error_cause
242 * arguments -- a genuine retransmission always gets the same answer
243 * (RFC 5176 SS2.3), never a fresh one. */
244 unsigned is_cached_duplicate;
245 uint8_t cached_reply_code;
246 uint32_t cached_error_cause;
247};
248
249/*- Free a dacs array and each entry's secret.
250 *
251 * @param dacs the array to free; NULL is accepted and ignored.
252 * @param n the number of entries in dacs.
253 -*/
254static void free_dacs(struct radcli_dae_dac *dacs, unsigned n)
255{
256 unsigned i;
257
258 if (dacs == NULL)
259 return;
260 for (i = 0; i < n; i++)
261 free(dacs[i].secret);
262 free(dacs);
263}
264
265/*- Append req to dae's RadSec delivery queue (radcli2_priv_dae_on_radsec_
266 * packet(), radcli_ctx_dispatch()'s RadSec branch). If the queue is
267 * already at RADCLI_DAE_RADSEC_QUEUE_SIZE, the oldest entry is dropped
268 * (freed) and logged first -- see that constant's comment.
269 *
270 * @param dae the listener whose queue to append to.
271 * @param req the request to enqueue.
272 -*/
273static void radsec_queue_push(struct radcli_dae_st *dae, struct radcli_dae_request_st *req)
274{
275 unsigned i;
276
277 if (dae->radsec_queue_len == RADCLI_DAE_RADSEC_QUEUE_SIZE) {
278 rc_log(LOG_WARNING, "radcli_ctx_dispatch: RadSec dynamic-authorization "
279 "queue full (%u), dropping the oldest undelivered request",
280 (unsigned)RADCLI_DAE_RADSEC_QUEUE_SIZE);
281 radcli_dae_request_free((radcli_dae_request *)dae->radsec_queue[0]);
282 for (i = 1; i < dae->radsec_queue_len; i++)
283 dae->radsec_queue[i - 1] = dae->radsec_queue[i];
284 dae->radsec_queue_len--;
285 }
286 dae->radsec_queue[dae->radsec_queue_len++] = req;
287}
288
289/*- Pop and return the oldest queued RadSec request.
290 *
291 * @param dae the listener whose queue to pop from.
292 * @return the oldest queued request, or NULL if the queue is empty.
293 -*/
294static struct radcli_dae_request_st *radsec_queue_pop(struct radcli_dae_st *dae)
295{
296 struct radcli_dae_request_st *req;
297 unsigned i;
298
299 if (dae->radsec_queue_len == 0)
300 return NULL;
301 req = dae->radsec_queue[0];
302 for (i = 1; i < dae->radsec_queue_len; i++)
303 dae->radsec_queue[i - 1] = dae->radsec_queue[i];
304 dae->radsec_queue_len--;
305 return req;
306}
307
308/* Appends one reply to dae->radsec_reply_queue -- REQ-DAE-SEC-013. Caller
309 * must hold dae->radsec_lock. If the queue is already at
310 * RADCLI_DAE_RADSEC_REPLY_QUEUE_SIZE, the OLDEST queued reply is dropped
311 * (and logged) to make room, per that requirement's own text ("dropped
312 * rather than buffered without limit"); a reply longer than
313 * RADCLI_DAE_RADSEC_REPLY_MAX_LEN is dropped outright rather than queued
314 * (RFC 5176 replies are small and fixed-shape -- see that constant's
315 * comment -- so this should never actually happen in practice). */
316/*- Append one reply to dae->radsec_reply_queue -- see the comment above
317 * for the drop policy. Caller must hold dae->radsec_lock.
318 *
319 * @param dae the listener whose reply queue to append to.
320 * @param buf the reply bytes to enqueue.
321 * @param len buf's length in bytes.
322 -*/
323static void radsec_reply_queue_push_locked(struct radcli_dae_st *dae, const uint8_t *buf, size_t len)
324{
325 unsigned i;
326
327 if (len > RADCLI_DAE_RADSEC_REPLY_MAX_LEN) {
328 rc_log(LOG_ERR, "radcli_ctx_dispatch: RadSec reply of %zu bytes exceeds "
329 "the %d-byte queue slot size, dropping", len,
330 RADCLI_DAE_RADSEC_REPLY_MAX_LEN);
331 return;
332 }
333 if (dae->radsec_reply_queue_len == RADCLI_DAE_RADSEC_REPLY_QUEUE_SIZE) {
334 rc_log(LOG_WARNING, "radcli_ctx_dispatch: RadSec reply queue full (%u), "
335 "dropping the oldest unsent reply", (unsigned)RADCLI_DAE_RADSEC_REPLY_QUEUE_SIZE);
336 for (i = 1; i < dae->radsec_reply_queue_len; i++)
337 dae->radsec_reply_queue[i - 1] = dae->radsec_reply_queue[i];
338 dae->radsec_reply_queue_len--;
339 }
340 memcpy(dae->radsec_reply_queue[dae->radsec_reply_queue_len].buf, buf, len);
341 dae->radsec_reply_queue[dae->radsec_reply_queue_len].len = len;
342 dae->radsec_reply_queue_len++;
343}
344
345/* Attempts to send every reply currently queued, in order, stopping at
346 * the first one that would still block (a later reply must never
347 * overtake an earlier one that has not gone out yet) -- one non-blocking
348 * attempt per queued reply per call, never a wait. Caller must hold
349 * dae->radsec_lock. A session error (radcli2_priv_tls_dae_send()
350 * returning -1) drops the reply it was attempting: the session is being
351 * marked for reconnection anyway, and there is nothing more sensible to
352 * do with a reply for a connection that no longer exists. */
353/*- Attempt one non-blocking send per queued reply, in order, stopping at
354 * the first one that would still block -- see the comment above. Caller
355 * must hold dae->radsec_lock.
356 *
357 * @param dae the listener whose reply queue to flush.
358 -*/
359static void radsec_flush_reply_queue_locked(struct radcli_dae_st *dae)
360{
361 while (dae->radsec_reply_queue_len > 0) {
362 struct radcli_dae_pending_reply *p = &dae->radsec_reply_queue[0];
363 int ret = radcli2_priv_tls_dae_send(dae->rh, p->buf, p->len);
364 unsigned i;
365
366 if (ret == 0)
367 break; /* still would block -- try again on a later call */
368 for (i = 1; i < dae->radsec_reply_queue_len; i++)
369 dae->radsec_reply_queue[i - 1] = dae->radsec_reply_queue[i];
370 dae->radsec_reply_queue_len--;
371 }
372}
373
374/*- Lock, flush, and unlock dae's RadSec reply queue; see
375 * radsec_flush_reply_queue_locked().
376 *
377 * @param dae the listener whose reply queue to flush.
378 -*/
379static void radsec_flush_reply_queue(struct radcli_dae_st *dae)
380{
381 pthread_mutex_lock(&dae->radsec_lock);
382 radsec_flush_reply_queue_locked(dae);
383 pthread_mutex_unlock(&dae->radsec_lock);
384}
385
386/* Parses one "address_or_hostname[:secret]" dae-server entry. IPv6
387 * literals need bracket notation ("[addr]" or "[addr]:secret") to attach a
388 * secret unambiguously, since a bare IPv6 address itself contains colons;
389 * a bare token with zero or more than one colon is taken whole as the
390 * address, with no secret override in that form. *name and *secret are
391 * malloc()'d (*secret may come back NULL, meaning "no override"); caller
392 * frees both. Returns 0 on success, -1 on a malformed token -- including
393 * one that embeds a network prefix ('/'), which dae-server never accepts
394 * (REQ-DAE-INIT-003), or a ":secret" override longer than
395 * MAX_SECRET_LENGTH: rejected here at construction, the same as an
396 * overlong dae-secret, rather than silently truncated the first time it is
397 * used to verify a packet. */
398/*- Parse one "address_or_hostname[:secret]" dae-server entry -- see the
399 * comment above for the bracket-notation/prefix-rejection details.
400 *
401 * @param token the dae-server entry text to parse.
402 * @param name set to a malloc()'d copy of the address/hostname.
403 * @param secret set to a malloc()'d copy of the secret override, or NULL
404 * if token carries none.
405 * @return 0 on success, -1 on a malformed token.
406 -*/
407static int parse_dae_server_token(const char *token, char **name, char **secret)
408{
409 const char *close, *colon;
410 size_t namelen;
411
412 *name = NULL;
413 *secret = NULL;
414
415 if (token[0] == '\0' || strchr(token, '/') != NULL)
416 return -1;
417
418 if (token[0] == '[') {
419 close = strchr(token, ']');
420 if (close == NULL || close == token + 1)
421 return -1;
422 namelen = (size_t)(close - (token + 1));
423 *name = malloc(namelen + 1);
424 if (*name == NULL)
425 return -1;
426 memcpy(*name, token + 1, namelen);
427 (*name)[namelen] = '\0';
428
429 if (close[1] == ':') {
430 if (close[2] == '\0')
431 goto fail;
432 if (strlen(close + 2) > MAX_SECRET_LENGTH) {
433 rc_log(LOG_ERR, "radcli_dae_new: dae-server: secret override for "
434 "\"%s\" is longer than %d bytes", *name, MAX_SECRET_LENGTH);
435 goto fail;
436 }
437 *secret = strdup(close + 2);
438 if (*secret == NULL)
439 goto fail;
440 } else if (close[1] != '\0') {
441 goto fail;
442 }
443 return 0;
444 }
445
446 colon = strchr(token, ':');
447 if (colon != NULL && strchr(colon + 1, ':') == NULL) {
448 if (colon == token || colon[1] == '\0')
449 return -1;
450 namelen = (size_t)(colon - token);
451 *name = malloc(namelen + 1);
452 if (*name == NULL)
453 return -1;
454 memcpy(*name, token, namelen);
455 (*name)[namelen] = '\0';
456 if (strlen(colon + 1) > MAX_SECRET_LENGTH) {
457 rc_log(LOG_ERR, "radcli_dae_new: dae-server: secret override for "
458 "\"%s\" is longer than %d bytes", *name, MAX_SECRET_LENGTH);
459 goto fail;
460 }
461 *secret = strdup(colon + 1);
462 if (*secret == NULL)
463 goto fail;
464 return 0;
465 }
466
467 *name = strdup(token);
468 if (*name == NULL)
469 return -1;
470 return 0;
471
472fail:
473 free(*name);
474 free(*secret);
475 *name = NULL;
476 *secret = NULL;
477 return -1;
478}
479
480/* Resolves name (already stripped of any ":secret" suffix) and appends one
481 * DAC entry per resulting address, each carrying secret_override (or
482 * NULL, meaning "use dae->secret"). REQ-DAE-INIT-004: every address a
483 * hostname resolves to is authorized. */
484/*- Resolve name (already stripped of any ":secret" suffix) and append one
485 * DAC entry per resulting address -- see the comment above.
486 *
487 * @param dae the listener to append resolved DAC entries to.
488 * @param name the hostname/address to resolve.
489 * @param secret_override the per-entry secret to record, or NULL to fall
490 * back to dae->secret.
491 * @return 0 on success, -1 on failure (resolution error, allocation
492 * failure, or too many resolved addresses).
493 -*/
494static int add_dac_addrs(struct radcli_dae_st *dae, const char *name,
495 const char *secret_override)
496{
497 struct addrinfo hints, *res, *rp;
498 int err;
499 struct radcli_dae_dac *tmp;
500 unsigned added = 0;
501
502 memset(&hints, 0, sizeof(hints));
503 hints.ai_family = AF_UNSPEC;
504 hints.ai_socktype = SOCK_DGRAM;
505
506 err = getaddrinfo(name, NULL, &hints, &res);
507 if (err != 0) {
508 rc_log(LOG_ERR, "radcli_dae_new: dae-server: cannot resolve %s: %s",
509 name, gai_strerror(err));
510 return -1;
511 }
512
513 for (rp = res; rp != NULL; rp = rp->ai_next) {
514 if (dae->n_dacs >= RADCLI_DAE_MAX_DACS) {
515 rc_log(LOG_ERR, "radcli_dae_new: dae-server: too many resolved "
516 "addresses (max %d)", RADCLI_DAE_MAX_DACS);
517 freeaddrinfo(res);
518 return -1;
519 }
520 tmp = realloc(dae->dacs, (dae->n_dacs + 1) * sizeof(*tmp));
521 if (tmp == NULL) {
522 freeaddrinfo(res);
523 return -1;
524 }
525 dae->dacs = tmp;
526 memset(&dae->dacs[dae->n_dacs], 0, sizeof(*tmp));
527 memcpy(&dae->dacs[dae->n_dacs].addr, rp->ai_addr, rp->ai_addrlen);
528 dae->dacs[dae->n_dacs].addrlen = rp->ai_addrlen;
529 if (secret_override != NULL) {
530 dae->dacs[dae->n_dacs].secret = strdup(secret_override);
531 if (dae->dacs[dae->n_dacs].secret == NULL) {
532 freeaddrinfo(res);
533 return -1;
534 }
535 }
536 dae->n_dacs++;
537 added++;
538 }
539 freeaddrinfo(res);
540
541 if (added == 0) {
542 rc_log(LOG_ERR, "radcli_dae_new: dae-server: %s resolved to no addresses", name);
543 return -1;
544 }
545 return 0;
546}
547
548/*- Parse dae-listen ("[host]:port", "host:port", ":port", or an
549 * empty/NULL spec for the default).
550 *
551 * @param spec the dae-listen text to parse.
552 * @param host set to a malloc()'d host string, or NULL for "any address";
553 * caller frees.
554 * @param port set to the parsed port.
555 * @return 0 on success, -1 on a malformed spec.
556 -*/
557static int parse_dae_listen(const char *spec, char **host, int *port)
558{
559 const char *close, *colon;
560 long p;
561 char *end;
562
563 *host = NULL;
564 *port = RADCLI_DAE_DEFAULT_PORT;
565
566 if (spec == NULL || spec[0] == '\0')
567 return 0;
568
569 if (spec[0] == '[') {
570 close = strchr(spec, ']');
571 if (close == NULL || close == spec + 1)
572 return -1;
573 *host = malloc((size_t)(close - (spec + 1)) + 1);
574 if (*host == NULL)
575 return -1;
576 memcpy(*host, spec + 1, (size_t)(close - (spec + 1)));
577 (*host)[close - (spec + 1)] = '\0';
578
579 if (close[1] == ':') {
580 p = strtol(close + 2, &end, 10);
581 if (*end != '\0' || p < 0 || p > 65535)
582 goto fail;
583 *port = (int)p;
584 } else if (close[1] != '\0') {
585 goto fail;
586 }
587 return 0;
588 }
589
590 colon = strrchr(spec, ':');
591 if (colon == NULL) {
592 *host = strdup(spec);
593 if (*host == NULL)
594 return -1;
595 return 0;
596 }
597
598 if (colon != spec) {
599 *host = malloc((size_t)(colon - spec) + 1);
600 if (*host == NULL)
601 return -1;
602 memcpy(*host, spec, (size_t)(colon - spec));
603 (*host)[colon - spec] = '\0';
604 }
605 if (colon[1] != '\0') {
606 p = strtol(colon + 1, &end, 10);
607 if (*end != '\0' || p < 0 || p > 65535)
608 goto fail;
609 *port = (int)p;
610 }
611 return 0;
612
613fail:
614 free(*host);
615 *host = NULL;
616 return -1;
617}
618
638radcli_dae *radcli_dae_new(radcli_ctx *ctx, unsigned flags)
639{
640 rc_handle *rh = (rc_handle *)ctx;
641 struct radcli_dae_st *dae = NULL;
642 const char *accept_str, *secret, *server_str, *require_ma_str, *listen_str;
643 char *server_dup = NULL, *saveptr, *tok;
644 int force_udp, radsec_mode;
645
646 if (rh == NULL)
647 return NULL;
648
649 if (flags & ~(unsigned)RADCLI_DAE_NO_NAS_CHECK)
650 return NULL;
651
652 accept_str = rc_conf_str_id(rh, OPT_DAE_ACCEPT);
653 if (accept_str == NULL || strcasecmp(accept_str, "no") == 0) {
654 /* Not a misconfiguration: dae-accept unset/"no" is the documented
655 * default, and returning NULL here is how a caller learns dynamic
656 * authorization is off (REQ-DAE-INIT-001) -- no error log. */
657 return NULL;
658 }
659 if (strcasecmp(accept_str, "yes") == 0) {
660 force_udp = 0;
661 } else if (strcasecmp(accept_str, "udp") == 0) {
662 force_udp = 1;
663 } else {
664 rc_log(LOG_ERR, "radcli_dae_new: dae-accept: invalid value \"%s\" "
665 "(must be no, yes, or udp)", accept_str);
666 return NULL;
667 }
668
669 /* dae-accept=yes means "follow serv-type": under TLS/DTLS, CoA/
670 * Disconnect flow over the already-established RadSec session
671 * (RFC 6614 SS2.1/SS2.5, RFC 7360 SS3.1: one port, one connection,
672 * every packet type) instead of a separate RFC 5176/UDP listener.
673 * dae-accept=udp always forces the RFC 5176/UDP listener regardless
674 * of serv-type -- kept as a fully supported, documented option (not
675 * a deprecated fallback): the shared-connection RadSec path above is
676 * new and, unlike the UDP listener, not yet validated against
677 * real-world DACs. */
678 radsec_mode = (!force_udp && (rh->so_type == RC_SOCKET_TLS || rh->so_type == RC_SOCKET_DTLS));
679
680 if (rh->active_dae != NULL) {
681 rc_log(LOG_ERR, "radcli_dae_new: a radcli_dae is already active on this context");
682 return NULL;
683 }
684
685 if (radsec_mode) {
686 /* REQ-DAE-INIT-007: under RadSec there is no dae-server source
687 * ACL -- the TLS/DTLS peer's verified identity is the sole
688 * authorization for who may send a CoA/Disconnect-Request, so
689 * construction must refuse if that verification is disabled. */
690 const char *verify_str = rc_conf_str_id(rh, OPT_TLS_VERIFY_HOSTNAME);
691
692 if (verify_str != NULL &&
693 (strcasecmp(verify_str, "false") == 0 || strcasecmp(verify_str, "no") == 0)) {
694 rc_log(LOG_ERR, "radcli_dae_new: dae-accept=yes under serv-type "
695 "tls/dtls needs tls-verify-hostname enabled: RadSec's "
696 "verified peer identity is what authorizes a "
697 "CoA/Disconnect-Request sender, replacing dae-server");
698 return NULL;
699 }
700
701 /* REQ-DAE-INIT-008: inapplicable under RadSec (the secret is the
702 * RFC 6614/7360 fixed string, the authorized sender is the TLS
703 * peer, and there is no dae-owned listener to bind) -- warn, but
704 * do not fail, so switching serv-type to tls/dtls is a config
705 * change an operator can make incrementally. */
706 {
707 static const struct {
708 rc_option_id id;
709 const char *name;
710 } inapplicable[] = {
711 { OPT_DAE_LISTEN, "dae-listen" },
712 { OPT_DAE_SERVER, "dae-server" },
713 { OPT_DAE_SECRET, "dae-secret" },
714 { OPT_DAE_REQUIRE_MESSAGE_AUTHENTICATOR, "dae-require-message-authenticator" }
715 };
716 unsigned i;
717
718 for (i = 0; i < sizeof(inapplicable) / sizeof(inapplicable[0]); i++) {
719 const char *v = rc_conf_str_id(rh, inapplicable[i].id);
720
721 if (v != NULL && v[0] != '\0')
722 rc_log(LOG_WARNING, "radcli_dae_new: %s is set but has no "
723 "effect under serv-type tls/dtls (RadSec replaces "
724 "it)", inapplicable[i].name);
725 }
726 }
727
728 dae = calloc(1, sizeof(*dae));
729 if (dae == NULL)
730 return NULL;
731 dae->rh = rh;
732 dae->fd = -1;
733 dae->radsec = 1;
734 dae->no_nas_check = (flags & RADCLI_DAE_NO_NAS_CHECK) != 0;
735 if (pthread_mutex_init(&dae->radsec_lock, NULL) != 0) {
736 free(dae);
737 return NULL;
738 }
739
740 /* REQ-DAE-INIT-005: still needed under RadSec -- Event-Timestamp
741 * freshness and duplicate suppression are replay protections
742 * independent of the transport's source-authorization model. */
743 dae->slots = calloc(RADCLI_DAE_SLOTS, sizeof(*dae->slots));
744 if (dae->slots == NULL)
745 goto fail;
746
747 dae->max_clock_skew = rc_conf_int_id(rh, OPT_DAE_MAX_CLOCK_SKEW);
748 if (dae->max_clock_skew < 0) {
749 rc_log(LOG_ERR, "radcli_dae_new: dae-max-clock-skew must not be negative");
750 goto fail;
751 }
752
753 rh->active_dae = dae;
754 return (radcli_dae *)dae;
755 }
756
757 secret = rc_conf_str_id(rh, OPT_DAE_SECRET);
758 server_str = rc_conf_str_id(rh, OPT_DAE_SERVER);
759 if (secret == NULL || secret[0] == '\0' || server_str == NULL || server_str[0] == '\0') {
760 rc_log(LOG_ERR, "radcli_dae_new: dae-accept is enabled but dae-server "
761 "and/or dae-secret is not set");
762 return NULL;
763 }
764 if (strlen(secret) > MAX_SECRET_LENGTH) {
765 rc_log(LOG_ERR, "radcli_dae_new: dae-secret is longer than %d bytes",
766 MAX_SECRET_LENGTH);
767 return NULL;
768 }
769
770 dae = calloc(1, sizeof(*dae));
771 if (dae == NULL)
772 return NULL;
773 dae->rh = rh;
774 dae->fd = -1;
775 dae->no_nas_check = (flags & RADCLI_DAE_NO_NAS_CHECK) != 0;
776
777 dae->secret = strdup(secret);
778 if (dae->secret == NULL)
779 goto fail;
780
781 server_dup = strdup(server_str);
782 if (server_dup == NULL)
783 goto fail;
784
785 for (tok = strtok_r(server_dup, ",", &saveptr); tok != NULL;
786 tok = strtok_r(NULL, ",", &saveptr)) {
787 char *name = NULL, *secret_override = NULL;
788 int ret;
789
790 while (*tok == ' ' || *tok == '\t')
791 tok++;
792
793 if (parse_dae_server_token(tok, &name, &secret_override) != 0) {
794 rc_log(LOG_ERR, "radcli_dae_new: dae-server: invalid entry \"%s\"", tok);
795 goto fail;
796 }
797
798 ret = add_dac_addrs(dae, name, secret_override);
799 free(name);
800 free(secret_override);
801 if (ret != 0)
802 goto fail;
803 }
804 free(server_dup);
805 server_dup = NULL;
806
807 if (dae->n_dacs == 0) {
808 rc_log(LOG_ERR, "radcli_dae_new: dae-server produced no authorized senders");
809 goto fail;
810 }
811
812 /* REQ-DAE-INIT-005: allocated once, here, at construction -- never
813 * grown, evicted, or resized at run time. Shared across every
814 * configured dae-server entry (RADCLI_DAE_SLOTS's comment explains
815 * why partitioning per entry is the wrong design). */
816 dae->slots = calloc(RADCLI_DAE_SLOTS, sizeof(*dae->slots));
817 if (dae->slots == NULL)
818 goto fail;
819
820 dae->max_clock_skew = rc_conf_int_id(rh, OPT_DAE_MAX_CLOCK_SKEW);
821 if (dae->max_clock_skew < 0) {
822 rc_log(LOG_ERR, "radcli_dae_new: dae-max-clock-skew must not be negative");
823 goto fail;
824 }
825
826 require_ma_str = rc_conf_str_id(rh, OPT_DAE_REQUIRE_MESSAGE_AUTHENTICATOR);
827 dae->require_message_authenticator =
828 (require_ma_str != NULL && strcasecmp(require_ma_str, "yes") == 0);
829
830 listen_str = rc_conf_str_id(rh, OPT_DAE_LISTEN);
831 if (parse_dae_listen(listen_str, &dae->listen_host, &dae->listen_port) != 0) {
832 rc_log(LOG_ERR, "radcli_dae_new: dae-listen: invalid value \"%s\"",
833 listen_str ? listen_str : "");
834 goto fail;
835 }
836
837 rh->active_dae = dae;
838 return (radcli_dae *)dae;
839
840fail:
841 free(server_dup);
843 return NULL;
844}
845
853{
854
855 if (dae == NULL)
856 return;
857 dae->handler = cb;
858 dae->handler_user = user;
859}
860
861/* Sets O_NONBLOCK and FD_CLOEXEC on fd. A datagram discarded by the kernel
862 * between the readiness report and the read -- or a second reader draining
863 * it first -- must not block the application's entire event loop inside a
864 * library that never owns it (REQ-GEN-SEC-003); FD_CLOEXEC keeps a
865 * library-opened descriptor from leaking across exec() in an application
866 * that spawns children (REQ-GEN-SEC-004's ambient-state family). Matches
867 * lib/tls.c's fcntl()-based style rather than the SOCK_NONBLOCK/SOCK_CLOEXEC
868 * socket() flags, for portability. */
869/*- Set O_NONBLOCK and FD_CLOEXEC on fd -- see the comment above for why.
870 * Shared with lib/sendserver.c's persistent UDP request socket
871 * (REQ-NET2-SEND-016), which needs the identical non-blocking-drain
872 * property for the same reason (radcli2_priv_reqreg_drain() loops
873 * recvfrom() until EAGAIN; a blocking socket would hang the caller's
874 * whole event loop on the last, empty call instead).
875 *
876 * @param fd the descriptor to modify.
877 * @return 0 on success, -1 on failure.
878 -*/
879int radcli2_priv_set_nonblock_cloexec(int fd)
880{
881 int flags;
882
883 flags = fcntl(fd, F_GETFL, 0);
884 if (flags == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
885 return -1;
886 flags = fcntl(fd, F_GETFD, 0);
887 if (flags == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
888 return -1;
889 return 0;
890}
891
897{
898 struct addrinfo hints, *res, *rp;
899 int err, fd = -1;
900 char portstr[8];
901 char addr_txt[NI_MAXHOST] = "?";
902
903 if (dae == NULL)
904 return -1;
905 if (dae->started) {
906 rc_log(LOG_ERR, "radcli_dae_start: already started");
907 return -1;
908 }
909
910 if (dae->radsec) {
911 /* No dae-owned socket to bind: force rh's own TLS/DTLS handshake
912 * now rather than waiting for the first ordinary rc_auth()/
913 * rc_acct() call, so enabling DAE makes the NAS reachable
914 * immediately -- matching the UDP listener's own immediate
915 * bind() below. */
916 if (radcli2_priv_tls_ensure_connected(dae->rh) != 0) {
917 rc_log(LOG_ERR, "radcli_dae_start: could not establish the "
918 "RadSec (TLS/DTLS) session");
919 return -1;
920 }
921 dae->started = 1;
922 return 0;
923 }
924
925 snprintf(portstr, sizeof(portstr), "%d", dae->listen_port);
926
927 memset(&hints, 0, sizeof(hints));
928 hints.ai_family = AF_UNSPEC;
929 hints.ai_socktype = SOCK_DGRAM;
930 hints.ai_flags = AI_PASSIVE;
931
932 err = getaddrinfo(dae->listen_host, portstr, &hints, &res);
933 if (err != 0) {
934 rc_log(LOG_ERR, "radcli_dae_start: dae-listen: %s", gai_strerror(err));
935 return -1;
936 }
937
938 for (rp = res; rp != NULL; rp = rp->ai_next) {
939 fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
940 if (fd == -1)
941 continue;
942 if (radcli2_priv_set_nonblock_cloexec(fd) != 0) {
943 close(fd);
944 fd = -1;
945 continue;
946 }
947 if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
948 getnameinfo(rp->ai_addr, rp->ai_addrlen, addr_txt, sizeof(addr_txt),
949 NULL, 0, NI_NUMERICHOST);
950 break;
951 }
952 close(fd);
953 fd = -1;
954 }
955 freeaddrinfo(res);
956
957 if (fd == -1) {
958 rc_log(LOG_ERR, "radcli_dae_start: bind: %s", strerror(errno));
959 return -1;
960 }
961
962 /* Diagnostic only: with dae-listen unset, getaddrinfo(AI_PASSIVE)'s
963 * candidate order decides which single address family gets bound
964 * (radcli_dae_start() binds exactly one socket, matching
965 * radcli_ctx_get_poll()'s one-descriptor contract), which is easy to
966 * mistake for "the listener isn't receiving anything" if left silent. */
967 rc_log(LOG_INFO, "radcli_dae_start: listening on %s port %d", addr_txt, dae->listen_port);
968
969 dae->fd = fd;
970 dae->started = 1;
971 return 0;
972}
973
979{
980 unsigned i;
981
982 if (dae == NULL)
983 return;
984 if (dae->rh != NULL && dae->rh->active_dae == dae)
985 dae->rh->active_dae = NULL;
986 /* Under RadSec, dae->fd is always -1: the TLS/DTLS session belongs to
987 * ordinary request handling (rc_init_tls()/lib/tls.c) and outlives
988 * this radcli_dae -- there is nothing dae-owned to close here. */
989 if (dae->fd != -1)
990 close(dae->fd);
991 if (dae->radsec) {
992 /* No other thread may still be calling radcli_ctx_dispatch()/
993 * relying on this dae once radcli_dae_free() is called (the
994 * caller's responsibility, same as freeing any other object
995 * concurrently in use) -- the lock here is just to pair cleanly
996 * with pthread_mutex_destroy(), not to defend against a
997 * concurrent user past this point. */
998 pthread_mutex_lock(&dae->radsec_lock);
999 for (i = 0; i < dae->radsec_queue_len; i++)
1000 radcli_dae_request_free((radcli_dae_request *)dae->radsec_queue[i]);
1001 dae->radsec_queue_len = 0;
1002 pthread_mutex_unlock(&dae->radsec_lock);
1003 pthread_mutex_destroy(&dae->radsec_lock);
1004 } else {
1005 for (i = 0; i < dae->radsec_queue_len; i++)
1006 radcli_dae_request_free((radcli_dae_request *)dae->radsec_queue[i]);
1007 }
1008 free_dacs(dae->dacs, dae->n_dacs);
1009 free(dae->slots);
1010 free(dae->secret);
1011 free(dae->listen_host);
1012 free(dae);
1013}
1014
1015/* Milliseconds until watchdog-interval elapses for an established RadSec
1016 * ctx (REQ-WATCHDOG-NET-002), or -1 if disabled/not applicable. Shared by
1017 * radcli_ctx_get_poll() (below) and radcli_ctx_dispatch() (the actual due
1018 * check that triggers radcli2_priv_dae_send_watchdog()), so the two agree
1019 * on exactly the same deadline. */
1020static int watchdog_deadline_ms(rc_handle *rh, int fd)
1021{
1022 int interval;
1023
1024 if (fd == -1)
1025 /* An unestablished session (radcli2_priv_tls_last_msg() reports
1026 * 0) must not be reported as an overdue watchdog, which would
1027 * otherwise busy-loop a caller not yet polling this fd. */
1028 return -1;
1029
1030 interval = rc_conf_int_id(rh, OPT_WATCHDOG_INTERVAL);
1031 if (interval > 0) {
1032 time_t last = radcli2_priv_tls_last_msg(rh);
1033 long elapsed_ms = (long)(time(0) - last) * 1000L;
1034 long remaining_ms = (long)interval * 1000L - elapsed_ms;
1035
1036 return (remaining_ms > 0) ? (int)remaining_ms : 0;
1037 }
1038 return -1;
1039}
1040
1041/* Folds a new candidate deadline into *timeout_ms (the running minimum;
1042 * negative candidates -- "no timeout needed" -- are ignored). */
1043static void fold_timeout(int *timeout_ms, int candidate)
1044{
1045 if (candidate < 0)
1046 return;
1047 if (*timeout_ms < 0 || candidate < *timeout_ms)
1048 *timeout_ms = candidate;
1049}
1050
1091int radcli_ctx_get_poll(radcli_ctx *ctx, struct pollfd *pfds, size_t max_pfds,
1092 size_t *nfds, int *timeout_ms)
1093{
1094 rc_handle *rh = (rc_handle *)ctx;
1095 size_t n = 0;
1096
1097 if (rh == NULL || pfds == NULL || nfds == NULL || timeout_ms == NULL)
1098 return -1;
1099 if (max_pfds < RADCLI_CTX_MAX_POLLFDS)
1100 return -1;
1101
1102 *timeout_ms = -1;
1103
1104 if (rh->so_type == RC_SOCKET_TLS || rh->so_type == RC_SOCKET_DTLS) {
1105 /* Reported for any established RadSec radcli_ctx, whether or not
1106 * dynamic authorization is active on it: the watchdog machinery
1107 * below (REQ-WATCHDOG-NET-001/002) is a property of the TLS/DTLS
1108 * session itself (keeping a NAT/firewall mapping alive, or just
1109 * avoiding a rehandshake after an idle period), not of DAE -- an
1110 * application using radcli purely as an ordinary rc_auth()/
1111 * rc_acct() RadSec client, with no radcli_dae at all, is as
1112 * entitled to it as one with dae-accept=yes. The DAE-specific
1113 * reply-queue bits just below are the only part still gated on
1114 * an active radcli_dae. */
1115 int dae_radsec = (rh->active_dae != NULL && rh->active_dae->radsec);
1116 int fd = radcli2_priv_tls_fd(rh);
1117 unsigned events = (fd != -1) ? POLLIN : 0;
1118
1119 /* REQ-DAE-SEC-013: a reply send_reply() deferred (radsec_reply_
1120 * queue non-empty) needs POLLOUT too, or a poll()-driven
1121 * application waiting only on POLLIN (the common case) may
1122 * never learn the socket became writable again and call
1123 * dispatch() to flush it. DAE-only. */
1124 if (dae_radsec && fd != -1 && rh->active_dae->radsec_reply_queue_len > 0)
1125 events |= POLLOUT;
1126
1127 if (fd != -1) {
1128 pfds[n].fd = fd;
1129 pfds[n].events = (short)events;
1130 pfds[n].revents = 0;
1131 n++;
1132 }
1133
1134 /* A record already pulled off the wire by an in-flight ordinary
1135 * request (tls_recvfrom()'s inline demux) may be sitting queued
1136 * with no further fd activity to prompt a redispatch -- ask the
1137 * caller back promptly rather than only on the next POLLIN. Read
1138 * without dae->radsec_lock deliberately: this is an advisory
1139 * hint only (worst case a stale read costs one extra dispatch()
1140 * call that finds nothing, never a correctness issue), and
1141 * radcli_ctx_get_poll() is documented to be callable from any
1142 * thread cheaply and often. DAE-only, same reason as above. */
1143 if (dae_radsec && (rh->active_dae->radsec_queue_len > 0 ||
1144 rh->active_dae->radsec_reply_queue_len > 0))
1145 *timeout_ms = 0;
1146 else
1147 fold_timeout(timeout_ms, watchdog_deadline_ms(rh, fd));
1148
1149 fold_timeout(timeout_ms, radcli2_priv_reqreg_earliest_deadline_ms(rh));
1150
1151 *nfds = n;
1152 return 0;
1153 }
1154
1155 /* UDP: the DAE listener (if active) and the shared request-registry
1156 * socket (if any RADCLI_REQUEST_SENDONLY exchange has ever used one)
1157 * are genuinely different local sockets -- report both when present. */
1158 if (rh->active_dae != NULL && rh->active_dae->fd != -1) {
1159 pfds[n].fd = rh->active_dae->fd;
1160 pfds[n].events = POLLIN;
1161 pfds[n].revents = 0;
1162 n++;
1163 }
1164 if (rh->req_fd != -1) {
1165 pfds[n].fd = rh->req_fd;
1166 pfds[n].events = POLLIN;
1167 pfds[n].revents = 0;
1168 n++;
1169 }
1170
1171 /* No DAE-proactive timer needed: the duplicate-suppression table
1172 * expires slots lazily, on next access, rather than on a schedule --
1173 * radcli owns no timer (REQ-GEN-SEC-003). Only an in-flight
1174 * RADCLI_REQUEST_SENDONLY exchange contributes a deadline on UDP. */
1175 fold_timeout(timeout_ms, radcli2_priv_reqreg_earliest_deadline_ms(rh));
1176
1177 *nfds = n;
1178 return 0;
1179}
1180
1241int radcli2_priv_dae_send_watchdog(radcli_ctx *ctx)
1242{
1243 rc_handle *rh = (rc_handle *)ctx;
1244 uint8_t send_buffer[RC_BUFFER_LEN];
1245 unsigned char vector[AUTH_VECTOR_LEN];
1246 char secret[MAX_SECRET_LENGTH + 1];
1247 radcli_avp_list *empty;
1248 int total_length;
1249 int ret;
1250
1251 if (rh == NULL)
1252 return -1;
1253
1254 if (rh->so_type != RC_SOCKET_TLS && rh->so_type != RC_SOCKET_DTLS)
1255 return -1;
1256
1257 if (radcli2_priv_tls_fd(rh) == -1)
1258 return -1; /* not established -- see radcli2_priv_tls_ensure_connected() */
1259
1260 {
1261 int interval = rc_conf_int_id(rh, OPT_WATCHDOG_INTERVAL);
1262
1263 if (interval > 0 &&
1264 (double)(time(0) - radcli2_priv_tls_last_recv(rh)) >= interval * 2.5) {
1265 rc_log(LOG_WARNING, "radcli2_priv_dae_send_watchdog: no record received in "
1266 "%.1fx watchdog-interval -- presuming the peer dead "
1267 "and reconnecting", 2.5);
1268 if (radcli2_priv_tls_force_reconnect(rh) < 0)
1269 return -1;
1270 }
1271 }
1272
1273 if (radcli2_priv_tls_fd(rh) == -1)
1274 return -1; /* the forced reconnect above left no usable session */
1275
1276 if (rh->so.static_secret == NULL)
1277 return -1;
1278 strlcpy(secret, rh->so.static_secret, sizeof(secret));
1279
1280 /* radcli_avp_encode() (via radcli_encode_request()) rejects a NULL
1281 * list outright -- RFC 5997 Status-Server needs no attributes but
1282 * Message-Authenticator, but "no attributes" still means an empty
1283 * list, not a NULL one. */
1284 empty = radcli_avp_list_new();
1285 if (empty == NULL)
1286 return -1;
1287
1288 /* Own established RadSec session, correlated to no other in-flight
1289 * exchange (REQ-WATCHDOG-NET-001 never queues/retries this) -- a
1290 * CSPRNG draw is sufficient (REQ-NET2-SEND-010). */
1291 ret = radcli_encode_request(rh, PW_STATUS_SERVER, empty, secret,
1292 send_buffer, rc_get_random_byte(), vector, &total_length);
1293 radcli_avp_list_free(empty);
1294 if (ret < 0)
1295 return -1;
1296
1297 return radcli2_priv_tls_dae_send(rh, send_buffer, (size_t)total_length);
1298}
1299
1300/* If sa is an IPv4-mapped IPv6 address ("::ffff:a.b.c.d"), extracts the
1301 * mapped IPv4 address into *out and returns 1; otherwise returns 0. A
1302 * dual-stack AF_INET6 listening socket (the default unless the platform
1303 * sets IPV6_V6ONLY) delivers an IPv4 sender's packet this way, with
1304 * from->sa_family == AF_INET6 -- see find_dac()'s use of this. */
1305/*- Extract an IPv4-mapped IPv6 address's IPv4 part -- see the comment
1306 * above.
1307 *
1308 * @param sa the address to check.
1309 * @param out set to the mapped IPv4 address, if sa is one.
1310 * @return 1 if sa is an IPv4-mapped IPv6 address (out set), 0 otherwise.
1311 -*/
1312static int get_v4_mapped(const struct sockaddr *sa, struct in_addr *out)
1313{
1314 const struct sockaddr_in6 *sin6;
1315
1316 if (sa->sa_family != AF_INET6)
1317 return 0;
1318 sin6 = (const struct sockaddr_in6 *)sa;
1319 if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
1320 return 0;
1321 memcpy(out, &sin6->sin6_addr.s6_addr[12], sizeof(*out));
1322 return 1;
1323}
1324
1325/* Finds the configured DAC matching from's address (port ignored: the
1326 * DAC's source port varies per request, only the address is part of its
1327 * identity -- REQ-DAE-SEC-001). Returns NULL if from is not authorized. */
1328/*- Find the configured DAC matching from's address -- see the comment
1329 * above.
1330 *
1331 * @param dae the listener whose DAC list to search.
1332 * @param from the sender's address to match.
1333 * @return the matching DAC, or NULL if from is not authorized.
1334 -*/
1335static struct radcli_dae_dac *find_dac(struct radcli_dae_st *dae,
1336 const struct sockaddr *from)
1337{
1338 unsigned i;
1339 struct in_addr mapped;
1340 int have_mapped = get_v4_mapped(from, &mapped);
1341
1342 for (i = 0; i < dae->n_dacs; i++) {
1343 const struct sockaddr *caddr = (const struct sockaddr *)&dae->dacs[i].addr;
1344
1345 if (caddr->sa_family == from->sa_family) {
1346 if (memcmp(SA_GET_INADDR(caddr), SA_GET_INADDR(from), SA_GET_INLEN(from)) == 0)
1347 return &dae->dacs[i];
1348 continue;
1349 }
1350 /* from is AF_INET6 but mapped from an IPv4 sender: also try
1351 * it against an AF_INET dae-server entry, or every packet
1352 * from an authorized IPv4 DAC is silently unreachable on a
1353 * dual-stack listener. */
1354 if (have_mapped && caddr->sa_family == AF_INET &&
1355 memcmp(&((const struct sockaddr_in *)caddr)->sin_addr, &mapped,
1356 sizeof(mapped)) == 0)
1357 return &dae->dacs[i];
1358 }
1359 return NULL;
1360}
1361
1362/* Returns a malloc()'d, NUL-terminated copy of attrs' first name attribute's
1363 * value, or NULL if the dictionary lacks name, attrs carries none, or
1364 * allocation failed. Used to populate radcli_dae_request_st's
1365 * session_id/user_name fields once, at receive time, since
1366 * radcli_avp_get_bytes() returns unterminated wire bytes and
1367 * radcli_dae_req_session_id()/_user_name() must return a C string. */
1368/*- Return a malloc()'d, NUL-terminated copy of attrs' first attrid
1369 * attribute's value -- see the comment above for why this exists.
1370 *
1371 * @param rh a handle to parsed configuration.
1372 * @param attrs the attribute list to search.
1373 * @param attrid the attribute ID to look up and copy.
1374 * @return the copied string, or NULL if the dictionary lacks attrid,
1375 * attrs carries none, or allocation failed.
1376 -*/
1377static char *dup_avp_str(rc_handle *rh, const radcli_avp_list *attrs, uint32_t attrid)
1378{
1379 const radcli_attr_def *d = radcli_dict_lookup_num(rh, attrid, 0);
1380 const radcli_avp *a;
1381 const void *val;
1382 size_t len;
1383 char *s;
1384
1385 if (d == NULL)
1386 return NULL;
1387 a = radcli_avp_get(attrs, d, 0);
1388 if (a == NULL || radcli_avp_get_bytes(a, &val, &len) != 0)
1389 return NULL;
1390 s = malloc(len + 1);
1391 if (s == NULL)
1392 return NULL;
1393 memcpy(s, val, len);
1394 s[len] = '\0';
1395 return s;
1396}
1397
1398/* Verifies buf[0..length)'s Request Authenticator (buf[4..20)) per RFC 5176
1399 * SS2.3 (as specified for Accounting-Request, RFC 2866 SS4.1):
1400 * MD5(Code+Identifier+Length+16 zero octets+Attributes+Secret) must equal
1401 * the received field. Constant-time comparison (REQ-DAE-SEC-002). buf must
1402 * be at least length + strlen(secret) bytes writable (the caller's receive
1403 * buffer, per the same convention lib/sendserver.c's request-building uses
1404 * to append the secret before hashing). */
1405/*- Verify buf[0..length)'s Request Authenticator -- see the comment above.
1406 *
1407 * @param buf the received packet; temporarily modified and restored.
1408 * Must be at least length + strlen(secret) bytes writable.
1409 * @param length buf's length in bytes.
1410 * @param secret the shared secret.
1411 * @return 0 if the authenticator is valid, -1 otherwise.
1412 -*/
1413static int verify_request_authenticator(uint8_t *buf, size_t length, const char *secret)
1414{
1415 uint8_t received[AUTH_VECTOR_LEN];
1416 uint8_t calc[AUTH_VECTOR_LEN];
1417 size_t secretlen = rc_secret_len(secret);
1418
1419 if (length < AUTH_HDR_LEN)
1420 return -1;
1421
1422 memcpy(received, buf + 4, AUTH_VECTOR_LEN);
1423 memset(buf + 4, 0, AUTH_VECTOR_LEN);
1424 memcpy(buf + length, secret, secretlen);
1425 rc_md5_calc(calc, buf, length + secretlen);
1426 memcpy(buf + 4, received, AUTH_VECTOR_LEN); /* restore: buf is the caller's receive buffer */
1427
1428 return rc_memcmp(received, calc, AUTH_VECTOR_LEN) == 0 ? 0 : -1;
1429}
1430
1431/* Encodes reply_code (already the concrete 41/42/44/45 code) with
1432 * error_cause as attribute 101 when non-zero, mirroring every Proxy-State
1433 * from the request, Message-Authenticator, and a Response Authenticator
1434 * computed over req's own Request Authenticator (RFC 5176 SS2.3,
1435 * REQ-DAE-SEC-008). Shared by send_reply() (which then sends the bytes
1436 * over req->dae->fd) and radcli_dae_reply_to_buffer() (which hands them
1437 * to the caller instead). */
1438/*- Build req's reply into out_buf.
1439 *
1440 * @param req the request to reply to.
1441 * @param reply_code the concrete RADIUS reply code to send.
1442 * @param error_cause a radcli_error_cause value to encode as attribute
1443 * 101, or 0 for none.
1444 * @param out_buf destination buffer for the encoded reply.
1445 * @param out_cap out_buf's capacity in bytes.
1446 * @param out_len set to the reply's encoded length on success.
1447 * @return 0 on success, -1 on failure (e.g. out_cap too small).
1448 -*/
1449static int build_reply(struct radcli_dae_request_st *req, uint8_t reply_code, uint32_t error_cause,
1450 uint8_t *out_buf, size_t out_cap, int *out_len)
1451{
1452 rc_handle *rh = req->dae->rh;
1453 AUTH_HDR *auth = (AUTH_HDR *)out_buf;
1454 radcli_avp_list *reply_attrs;
1455 const radcli_avp *a;
1456 const radcli_attr_def *d_proxy_state;
1457 int encoded_len, total_length;
1458 size_t secretlen;
1459 uint8_t digest[AUTH_VECTOR_LEN];
1460
1461 /* Header, plus room for the Message-Authenticator add_msg_auth_attr()
1462 * always appends below, must fit before out_cap - AUTH_HDR_LEN -
1463 * (2 + MD5_DIGEST_SIZE) is computed for radcli_avp_encode()'s
1464 * buflen just below: with only the old `out_cap < AUTH_HDR_LEN`
1465 * guard, an out_cap in [AUTH_HDR_LEN, AUTH_HDR_LEN + 2 +
1466 * MD5_DIGEST_SIZE) made that subtraction wrap to a huge size_t
1467 * instead of failing cleanly. */
1468 if (out_cap < (size_t)(AUTH_HDR_LEN + 2 + MD5_DIGEST_SIZE))
1469 return -1;
1470
1471 reply_attrs = radcli_avp_list_new();
1472 if (reply_attrs == NULL)
1473 return -1;
1474
1475 if (error_cause != 0) {
1476 const radcli_attr_def *d_ec = radcli_dict_lookup_num(rh, PW_ERROR_CAUSE, 0);
1477
1478 if (d_ec != NULL)
1479 radcli_avp_add_uint32(reply_attrs, d_ec, error_cause);
1480 }
1481
1482 d_proxy_state = radcli_dict_lookup_num(rh, PW_PROXY_STATE, 0);
1483 if (d_proxy_state != NULL) {
1484 radcli_avp_iter it = radcli_avp_list_iter(req->attrs);
1485
1486 while ((a = radcli_avp_iter_next(&it)) != NULL) {
1487 const void *val;
1488 size_t len;
1489
1490 if (radcli_avp_def(a) != d_proxy_state)
1491 continue;
1492 if (radcli_avp_get_bytes(a, &val, &len) == 0)
1493 radcli_avp_add_bytes(reply_attrs, d_proxy_state, val, len);
1494 }
1495 }
1496
1497 auth->code = reply_code;
1498 auth->id = req->id;
1499 memcpy(auth->vector, req->request_authenticator, AUTH_VECTOR_LEN);
1500
1501 encoded_len = radcli_avp_encode(rh, reply_attrs, req->secret, req->request_authenticator,
1502 auth->data, out_cap - AUTH_HDR_LEN - (2 + MD5_DIGEST_SIZE), NULL);
1503 radcli_avp_list_free(reply_attrs);
1504 if (encoded_len < 0)
1505 return -1;
1506
1507 total_length = AUTH_HDR_LEN + encoded_len;
1508 auth->length = htons((uint16_t)total_length);
1509
1510 if (out_cap < total_length + (2 + MD5_DIGEST_SIZE))
1511 return -1;
1512 total_length = add_msg_auth_attr(rh, req->secret, auth, total_length);
1513
1514 secretlen = rc_secret_len(req->secret);
1515 if ((size_t)(out_cap - total_length) < secretlen)
1516 return -1;
1517 memcpy(out_buf + total_length, req->secret, secretlen);
1518 rc_md5_calc(digest, out_buf, (size_t)total_length + secretlen);
1519 memcpy(auth->vector, digest, AUTH_VECTOR_LEN);
1520
1521 *out_len = total_length;
1522 return 0;
1523}
1524
1525/*- Build req's reply via build_reply() and send it over req->dae->fd.
1526 *
1527 * @param req the request to reply to.
1528 * @param reply_code the concrete RADIUS reply code to send.
1529 * @param error_cause a radcli_error_cause value to encode as attribute
1530 * 101, or 0 for none.
1531 * @return 0 on success, -1 on failure.
1532 -*/
1533static int send_reply(struct radcli_dae_request_st *req, uint8_t reply_code, uint32_t error_cause)
1534{
1535 uint8_t send_buffer[RC_BUFFER_LEN];
1536 int total_length;
1537
1538 if (build_reply(req, reply_code, error_cause, send_buffer, sizeof(send_buffer),
1539 &total_length) != 0)
1540 return -1;
1541
1542 if (req->dae->radsec) {
1543 /* No dae-owned socket/peer address under RadSec: the reply goes
1544 * back over rh's own established TLS/DTLS session. Unlike an
1545 * ordinary request (which may legitimately block up to
1546 * radius_timeout waiting for POLLOUT via tls_sendto()), this can
1547 * be called from radcli_ctx_dispatch() -- invoked only because
1548 * the descriptor was reported readable -- which must never turn
1549 * into a multi-second stall of the caller's event loop just
1550 * because a reply happens to need one. One non-blocking attempt
1551 * (radcli2_priv_tls_dae_send()); if it would block, defer to
1552 * REQ-DAE-SEC-013's bounded queue instead of waiting, to be
1553 * flushed by a later radcli_ctx_dispatch() call
1554 * (radsec_flush_reply_queue()). Order matters: flush whatever is
1555 * already queued first, and if anything remains queued after
1556 * that, this reply queues behind it too rather than jumping the
1557 * line by being attempted directly. */
1558 struct radcli_dae_st *dae = req->dae;
1559 int ret;
1560
1561 pthread_mutex_lock(&dae->radsec_lock);
1562 radsec_flush_reply_queue_locked(dae);
1563 if (dae->radsec_reply_queue_len > 0) {
1564 radsec_reply_queue_push_locked(dae, send_buffer, (size_t)total_length);
1565 pthread_mutex_unlock(&dae->radsec_lock);
1566 return 0;
1567 }
1568 pthread_mutex_unlock(&dae->radsec_lock);
1569
1570 ret = radcli2_priv_tls_dae_send(dae->rh, send_buffer, (size_t)total_length);
1571 if (ret == 0) {
1572 pthread_mutex_lock(&dae->radsec_lock);
1573 radsec_reply_queue_push_locked(dae, send_buffer, (size_t)total_length);
1574 pthread_mutex_unlock(&dae->radsec_lock);
1575 return 0;
1576 }
1577 return (ret > 0) ? 0 : -1;
1578 }
1579
1580 if (sendto(req->dae->fd, send_buffer, (size_t)total_length, 0,
1581 (struct sockaddr *)&req->from, req->fromlen) != total_length)
1582 return -1;
1583
1584 return 0;
1585}
1586
1594{
1595
1596 if (req == NULL)
1597 return 0;
1598 return (radcli_code)req->code;
1599}
1600
1608const radcli_avp_list *radcli_dae_req_attrs(const radcli_dae_request *req)
1609{
1610
1611 if (req == NULL)
1612 return NULL;
1613 return req->attrs;
1614}
1615
1622{
1623
1624 if (req == NULL)
1625 return NULL;
1626 return req->session_id;
1627}
1628
1635{
1636
1637 if (req == NULL)
1638 return NULL;
1639 return req->user_name;
1640}
1641
1649int radcli_dae_req_framed_ip(const radcli_dae_request *req, struct sockaddr_storage *out)
1650{
1651 rc_handle *rh;
1652 const radcli_attr_def *d;
1653 const radcli_avp *a;
1654
1655 if (req == NULL || out == NULL)
1656 return -1;
1657 rh = req->dae->rh;
1658
1659 d = radcli_dict_lookup_num(rh, PW_FRAMED_IP_ADDRESS, 0);
1660 a = (d != NULL) ? radcli_avp_get(req->attrs, d, 0) : NULL;
1661 if (a != NULL) {
1662 uint32_t val;
1663
1664 if (radcli_avp_get_uint32(a, &val) == 0) {
1665 struct sockaddr_in sin;
1666
1667 memset(&sin, 0, sizeof(sin));
1668 sin.sin_family = AF_INET;
1669 sin.sin_addr.s_addr = htonl(val);
1670 memset(out, 0, sizeof(*out));
1671 memcpy(out, &sin, sizeof(sin));
1672 return 0;
1673 }
1674 }
1675
1676 d = radcli_dict_lookup_num(rh, PW_FRAMED_IPV6_ADDRESS, 0);
1677 a = (d != NULL) ? radcli_avp_get(req->attrs, d, 0) : NULL;
1678 if (a != NULL) {
1679 struct in6_addr addr;
1680 unsigned prefix;
1681
1682 if (radcli_avp_get_ip6(a, &addr, &prefix) == 0) {
1683 struct sockaddr_in6 sin6;
1684
1685 memset(&sin6, 0, sizeof(sin6));
1686 sin6.sin6_family = AF_INET6;
1687 sin6.sin6_addr = addr;
1688 memset(out, 0, sizeof(*out));
1689 memcpy(out, &sin6, sizeof(sin6));
1690 return 0;
1691 }
1692 }
1693
1694 return -1;
1695}
1696
1704int radcli_dae_req_nas_port(const radcli_dae_request *req, uint32_t *out)
1705{
1706 rc_handle *rh;
1707 const radcli_attr_def *d;
1708 const radcli_avp *a;
1709
1710 if (req == NULL)
1711 return -1;
1712 rh = req->dae->rh;
1713
1714 d = radcli_dict_lookup_num(rh, PW_NAS_PORT, 0);
1715 a = (d != NULL) ? radcli_avp_get(req->attrs, d, 0) : NULL;
1716 if (a == NULL)
1717 return -1;
1718 if (out == NULL)
1719 return 0;
1720 return radcli_avp_get_uint32(a, out);
1721}
1722
1723
1724/* Records reply_code/error_cause as the decision in req's duplicate-
1725 * suppression slot, if it still matches req exactly (dedup_key, REQ-DAE-
1726 * SEC-005) and is still awaiting one -- i.e. transitions it from PENDING to
1727 * ANSWERED.
1728 * Shared by reply_and_record() (after an actual send) and
1729 * radcli_dae_reply_to_buffer() (after producing bytes an L0 caller will send
1730 * itself): either way, a later retransmission must be answered from this
1731 * same decision, not a fresh one (REQ-DAE-SEC-005), even if the reply was
1732 * deferred past the handler's own return. */
1733/*- Record reply_code/error_cause in req's duplicate-suppression slot --
1734 * see the comment above.
1735 *
1736 * @param req the request whose slot to update.
1737 * @param reply_code the reply code that was sent.
1738 * @param error_cause the error cause that was sent, or 0 for none.
1739 -*/
1740static void record_reply_decision(struct radcli_dae_request_st *req, uint8_t reply_code,
1741 uint32_t error_cause)
1742{
1743 struct radcli_dae_slot *slot;
1744
1745 if (req->dae == NULL)
1746 return;
1747 slot = &req->dae->slots[req->id];
1748 if (slot->valid && slot->pending &&
1749 rc_memcmp(slot->dedup_key, req->dedup_key,
1750 RC_SHA256_DIGEST_SIZE) == 0) {
1751 slot->pending = 0;
1752 slot->reply_code = reply_code;
1753 slot->error_cause = error_cause;
1754 }
1755}
1756
1757/*- Send req's reply and record the decision for duplicate suppression.
1758 *
1759 * @param req the request to reply to; must not already be replied to.
1760 * @param reply_code the concrete RADIUS reply code to send.
1761 * @param error_cause a radcli_error_cause value to encode as attribute
1762 * 101, or 0 for none.
1763 * @return 0 on success, -1 if req is NULL, already replied to, or the
1764 * send failed.
1765 -*/
1766static int reply_and_record(struct radcli_dae_request_st *req, uint8_t reply_code, uint32_t error_cause)
1767{
1768 int ret;
1769
1770 if (req == NULL || req->replied)
1771 return -1;
1772 req->replied = 1;
1773
1774 ret = send_reply(req, reply_code, error_cause);
1775 record_reply_decision(req, reply_code, error_cause);
1776
1777 return ret;
1778}
1779
1780/*- Select the concrete ACK/NAK code (41/42/44/45) for req's own code
1781 * (40/43) and the application's accept/reject decision (RFC 5176 SS2.1,
1782 * SS3). Shared by radcli_dae_reply() and radcli_dae_reply_to_buffer().
1783 *
1784 * @param req the request being replied to.
1785 * @param ack nonzero for an ACK, zero for a NAK.
1786 * @return the concrete reply code.
1787 -*/
1788static uint8_t select_reply_code(const struct radcli_dae_request_st *req, int ack)
1789{
1790 return ack
1791 ? (req->code == RADCLI_DISCONNECT_REQUEST ? RADCLI_DISCONNECT_ACK : RADCLI_COA_ACK)
1792 : (req->code == RADCLI_DISCONNECT_REQUEST ? RADCLI_DISCONNECT_NAK : RADCLI_COA_NAK);
1793}
1794
1807{
1808
1809 if (req == NULL)
1810 return -1;
1811 return reply_and_record(req, select_reply_code(req, ack), 0);
1812}
1813
1821int radcli_dae_reply_error(radcli_dae_request *req, uint32_t error_cause)
1822{
1823 uint8_t reply_code;
1824
1825 if (req == NULL)
1826 return -1;
1827 reply_code = (req->code == RADCLI_DISCONNECT_REQUEST) ? RADCLI_DISCONNECT_NAK : RADCLI_COA_NAK;
1828 return reply_and_record(req, reply_code, error_cause);
1829}
1830
1852int radcli_dae_reply_to_buffer(radcli_dae_request *req, int ack, uint32_t error_cause,
1853 void *buf, size_t *len)
1854{
1855 uint8_t reply_code;
1856 int out_len;
1857
1858 if (req == NULL || buf == NULL || len == NULL)
1859 return -1;
1860
1861 if (req->is_cached_duplicate) {
1862 /* A genuine retransmission always gets the same answer
1863 * (RFC 5176 SS2.3): the caller's ack/error_cause are not
1864 * this decision's to make over again. */
1865 reply_code = req->cached_reply_code;
1866 error_cause = req->cached_error_cause;
1867 } else {
1868 if (req->replied)
1869 return -1;
1870 reply_code = select_reply_code(req, ack);
1871 }
1872
1873 if (build_reply(req, reply_code, error_cause, (uint8_t *)buf, *len, &out_len) != 0)
1874 return -1;
1875 *len = (size_t)out_len;
1876
1877 if (!req->is_cached_duplicate) {
1878 req->replied = 1;
1879 record_reply_decision(req, reply_code, error_cause);
1880 }
1881
1882 return 0;
1883}
1884
1891{
1892
1893 if (req == NULL)
1894 return;
1895 radcli_avp_list_free(req->attrs);
1896 free(req->session_id);
1897 free(req->user_name);
1898 free(req);
1899}
1900
1901/* Rebuilds and resends a duplicate's reply from the retransmitted request
1902 * (its Proxy-State attributes, per RFC 5176 SS2.3) plus the cached decision
1903 * -- never from a stored copy of the original reply, which is why a slot
1904 * stays ~32 bytes rather than a full packet buffer. */
1905/* Outcomes of process_packet(), the validation pipeline shared by
1906 * radcli_ctx_dispatch() (socket path) and radcli_dae_process() (L0 buffer
1907 * path) -- REQ-DAE-NET-003 requires the two to be indistinguishable. */
1908enum process_result {
1909 PROCESS_DROP, /* discarded at some check; *out_req left NULL */
1910 PROCESS_NEW, /* a newly validated request; *out_req set, PENDING */
1911 PROCESS_DUP_ANSWERED, /* a retransmission of an ANSWERED request; *out_req
1912 * set, carrying the cached decision */
1913};
1914
1915/* Runs the full RFC 5176 validation pipeline on one packet (REQ-NET2-NET-002):
1916 * source-address authorization (REQ-DAE-SEC-001), packet-code and length
1917 * sanity (REQ-DAE-ERR-001), Request Authenticator (REQ-DAE-SEC-002),
1918 * Message-Authenticator when present or required (REQ-DAE-SEC-003),
1919 * Event-Timestamp freshness (REQ-DAE-SEC-004), then duplicate suppression
1920 * (REQ-DAE-SEC-005/006). buf must have RC_MAX_PACKET_LEN + MAX_SECRET_LENGTH
1921 * bytes of headroom past len (verify_request_authenticator() writes there);
1922 * both callers below satisfy this from a stack buffer sized RC_BUFFER_LEN. */
1923/*- Run the full RFC 5176 validation pipeline on one packet -- see the
1924 * comment above.
1925 *
1926 * @param dae the listener the packet was received on.
1927 * @param buf the received packet; must have RC_MAX_PACKET_LEN +
1928 * MAX_SECRET_LENGTH bytes of headroom past len.
1929 * @param len the received packet's length in bytes.
1930 * @param from the sender's address.
1931 * @param fromlen from's length in bytes.
1932 * @param out_req set on PROCESS_NEW/PROCESS_DUP_ANSWERED; left NULL on
1933 * PROCESS_DROP.
1934 * @return the validation outcome.
1935 -*/
1936static enum process_result process_packet(struct radcli_dae_st *dae, uint8_t *buf, size_t len,
1937 const struct sockaddr *from, socklen_t fromlen,
1938 struct radcli_dae_request_st **out_req)
1939{
1940 rc_handle *rh = dae->rh;
1941 size_t length = len;
1942 struct radcli_dae_dac *dac;
1943 struct radcli_dae_slot *slot;
1944 time_t now;
1945 int retention;
1946 struct radcli_dae_request_st *req;
1947 radcli_avp_list *attrs = NULL;
1948 const radcli_attr_def *d;
1949 const radcli_avp *a;
1950 const char *secret;
1951
1952 *out_req = NULL;
1953
1954 if (dae->radsec) {
1955 /* REQ-DAE-SEC-015: "the source is the session" by construction
1956 * -- the record necessarily arrived on rh's own TLS/DTLS-
1957 * verified connection, so there is no separate source-address
1958 * ACL to check the way find_dac() checks one for UDP; the
1959 * RFC 6614/7360 fixed secret (rh->so.static_secret, set by
1960 * lib/tls.c's rc_init_tls()) replaces dae-secret/dac->secret. */
1961 dac = NULL;
1962 secret = rh->so.static_secret;
1963 } else {
1964 /* REQ-DAE-SEC-001: discarded before parsing attributes or computing
1965 * any MD5/HMAC, and without a reply -- a response would confirm to a
1966 * scanner that a listener is present. */
1967 dac = find_dac(dae, from);
1968 if (dac == NULL)
1969 return PROCESS_DROP;
1970 secret = (dac->secret != NULL) ? dac->secret : dae->secret;
1971 }
1972
1973 /* Header sanity: enough bytes for a header, the wire Length field
1974 * agrees with what was actually received (never trust it beyond
1975 * that) and with RFC 2865's packet-size cap (also what leaves
1976 * verify_request_authenticator() below enough headroom in buf to
1977 * append the secret without overrunning it), and the code is one
1978 * this pipeline handles at all (REQ-DAE-ERR-001). */
1979 if (length < AUTH_HDR_LEN)
1980 return PROCESS_DROP;
1981 {
1982 uint16_t wire_length;
1983
1984 memcpy(&wire_length, buf + 2, sizeof(wire_length));
1985 wire_length = ntohs(wire_length);
1986 if (wire_length < AUTH_HDR_LEN || wire_length > length ||
1987 wire_length > RC_MAX_PACKET_LEN)
1988 return PROCESS_DROP;
1989 length = wire_length; /* never read past the packet's own Length */
1990 }
1991 if (buf[0] != RADCLI_DISCONNECT_REQUEST && buf[0] != RADCLI_COA_REQUEST)
1992 return PROCESS_DROP;
1993
1994 {
1995 if (verify_request_authenticator(buf, length, secret) != 0)
1996 return PROCESS_DROP;
1997
1998 if (radcli_avp_decode(rh, secret, buf + 4, buf + AUTH_HDR_LEN,
1999 length - AUTH_HDR_LEN, 0, &attrs) != 0)
2000 return PROCESS_DROP;
2001
2002 /* Message-Authenticator: verified when present (mismatch is a
2003 * silent discard, same as any other authentication failure);
2004 * required when dae-require-message-authenticator is set
2005 * (RFC 5176 SS3 makes the attribute itself a MAY, so absence
2006 * alone is not a failure otherwise). Disconnect-Request and
2007 * CoA-Request derive their own Request Authenticator from a
2008 * hash of the packet (RFC 5176 SS2.3, the Accounting-Request
2009 * convention), so -- like Accounting-Request -- the sender
2010 * computes this HMAC with the Authenticator field treated as
2011 * sixteen zero octets (RFC 2869 SS5.14), not the packet's
2012 * actual Request Authenticator: only that ordering is
2013 * non-circular, since the real Request Authenticator is
2014 * itself hashed over the attributes including this one. */
2015 d = radcli_dict_lookup_num(rh, PW_MESSAGE_AUTHENTICATOR, 0);
2016 a = (d != NULL) ? radcli_avp_get(attrs, d, 0) : NULL;
2017 if (a != NULL) {
2018 uint8_t zero_vector[AUTH_VECTOR_LEN];
2019
2020 memset(zero_vector, 0, sizeof(zero_vector));
2021 if (validate_message_authenticator(buf, length - AUTH_HDR_LEN, secret,
2022 zero_vector) != 0) {
2023 radcli_avp_list_free(attrs);
2024 return PROCESS_DROP;
2025 }
2026 } else if (dae->require_message_authenticator) {
2027 radcli_avp_list_free(attrs);
2028 return PROCESS_DROP;
2029 }
2030 }
2031
2032 /* Event-Timestamp: two-sided freshness check when present and
2033 * enabled; absence is accepted (RFC 5176 SS6.3 makes it a SHOULD). */
2034 if (dae->max_clock_skew > 0) {
2035 d = radcli_dict_lookup_num(rh, PW_EVENT_TIMESTAMP, 0);
2036 a = (d != NULL) ? radcli_avp_get(attrs, d, 0) : NULL;
2037 if (a != NULL) {
2038 uint32_t ts;
2039 long diff;
2040
2041 if (radcli_avp_get_uint32(a, &ts) != 0) {
2042 radcli_avp_list_free(attrs);
2043 return PROCESS_DROP;
2044 }
2045 now = time(NULL);
2046 diff = (long)now - (long)ts;
2047 if (diff < 0)
2048 diff = -diff;
2049 if (diff > dae->max_clock_skew) {
2050 radcli_avp_list_free(attrs);
2051 return PROCESS_DROP;
2052 }
2053 }
2054 }
2055
2056 /* Duplicate suppression (REQ-DAE-SEC-005/006): a fixed slot per
2057 * Identifier, shared across every configured dae-server entry. The
2058 * match key is an SHA-256 digest process_packet() computes itself
2059 * over the verified packet (buf[0..length)), not the wire's 16-byte
2060 * MD5 Request Authenticator -- REQ-GEN-SEC-008 is why: that field is
2061 * a secret-suffix MD5 MAC, the construction class Blast-RADIUS
2062 * (CVE-2024-3596) broke, so basing "same key implies same content" on
2063 * it would only be as strong as MD5. The packet is already fully
2064 * authenticated by this point (REQ-DAE-SEC-002/003/004 above), so
2065 * hashing it again here with a collision-resistant function makes
2066 * that assumption actually true, independent of MD5's weakness,
2067 * rather than inherited from it -- at the cost of one extra SHA-256
2068 * over an already-small packet. A match (see RADCLI_DAE_SLOTS's
2069 * comment on why neither source address nor source port is part of
2070 * this) is a genuine retransmission -- answered from the cached
2071 * decision if one exists, or silently dropped if the original is
2072 * still PENDING; anything else (including a first-ever arrival)
2073 * claims the slot and produces a new request. */
2074 now = time(NULL);
2075 retention = (dae->max_clock_skew > 0) ? dae->max_clock_skew : 30;
2076 slot = &dae->slots[buf[1]];
2077
2078 req = calloc(1, sizeof(*req));
2079 if (req == NULL) {
2080 radcli_avp_list_free(attrs);
2081 return PROCESS_DROP;
2082 }
2083 req->dae = dae;
2084 req->dac = dac; /* NULL under RadSec -- see above */
2085 req->code = buf[0];
2086 req->id = buf[1];
2087 memcpy(req->request_authenticator, buf + 4, AUTH_VECTOR_LEN);
2088 rc_sha256_calc(req->dedup_key, buf, length);
2089 /* from is NULL exactly when dae->radsec is set (radcli2_priv_dae_on_
2090 * radsec_packet() is process_packet()'s only radsec-mode caller, and
2091 * always passes NULL/0 -- see above); the explicit from != NULL check
2092 * alongside dae->radsec is redundant at runtime but makes that
2093 * invariant provable locally instead of relying on a correlation
2094 * across call sites the compiler's static analyzer cannot see. */
2095 if (!dae->radsec && from != NULL) {
2096 memcpy(&req->from, from, fromlen);
2097 req->fromlen = fromlen;
2098 }
2099 strlcpy(req->secret, secret, sizeof(req->secret));
2100 req->attrs = attrs;
2101 attrs = NULL;
2102 req->session_id = dup_avp_str(rh, req->attrs, PW_ACCT_SESSION_ID);
2103 req->user_name = dup_avp_str(rh, req->attrs, PW_USER_NAME);
2104
2105 if (slot->valid && (now - slot->timestamp) <= retention &&
2106 rc_memcmp(slot->dedup_key, req->dedup_key,
2107 RC_SHA256_DIGEST_SIZE) == 0) {
2108 if (!slot->pending) {
2109 req->is_cached_duplicate = 1;
2110 req->cached_reply_code = slot->reply_code;
2111 req->cached_error_cause = slot->error_cause;
2112 *out_req = req;
2113 return PROCESS_DUP_ANSWERED;
2114 }
2115 /* Original still PENDING an application decision -- discarded
2116 * silently, exactly like any other failed check. */
2118 return PROCESS_DROP;
2119 }
2120
2121 /* REQ-DAE-SEC-018: NAS-Identifier, if both the request and the
2122 * nas-identifier config option carry one, must agree -- RFC 5176 SS3.5
2123 * "NAS Identification Mismatch" (Error-Cause 403). Absence of either
2124 * is not a mismatch -- there is nothing to check it against.
2125 * NAS-IP-Address/NAS-IPv6-Address are deliberately not compared here:
2126 * unlike NAS-Identifier, a DAC-observed address for a NAS routinely
2127 * differs from what the NAS is itself configured with (NAT, containers,
2128 * a proxy/load balancer in front of the NAS), so it is not something
2129 * radcli can check on the application's behalf. */
2130 if (!dae->no_nas_check) {
2131 const char *cfg_id = rc_conf_str_id(rh, OPT_NAS_IDENTIFIER);
2132
2133 if (cfg_id != NULL) {
2134 d = radcli_dict_lookup_num(rh, PW_NAS_IDENTIFIER, 0);
2135 a = (d != NULL) ? radcli_avp_get(req->attrs, d, 0) : NULL;
2136 if (a != NULL) {
2137 const void *val;
2138 size_t vlen;
2139
2140 if (radcli_avp_get_bytes(a, &val, &vlen) == 0 &&
2141 (vlen != strlen(cfg_id) || memcmp(val, cfg_id, vlen) != 0)) {
2142 req->cached_reply_code = (req->code == RADCLI_DISCONNECT_REQUEST)
2143 ? RADCLI_DISCONNECT_NAK : RADCLI_COA_NAK;
2144 req->cached_error_cause = RADCLI_ERROR_NAS_IDENTIFICATION_MISMATCH;
2145 slot->valid = 1;
2146 slot->timestamp = now;
2147 memcpy(slot->dedup_key, req->dedup_key, RC_SHA256_DIGEST_SIZE);
2148 slot->pending = 0;
2149 slot->reply_code = req->cached_reply_code;
2150 slot->error_cause = req->cached_error_cause;
2151 req->is_cached_duplicate = 1;
2152 *out_req = req;
2153 return PROCESS_DUP_ANSWERED;
2154 }
2155 }
2156 }
2157 }
2158
2159 slot->valid = 1;
2160 slot->timestamp = now;
2161 memcpy(slot->dedup_key, req->dedup_key, RC_SHA256_DIGEST_SIZE);
2162 slot->pending = 1;
2163
2164 *out_req = req;
2165 return PROCESS_NEW;
2166}
2167
2168/* Builds and sends a Disconnect-NAK/CoA-NAK with Error-Cause 406
2169 * ("Unsupported Extension") for reqbuf/reqlen, a CoA-Request or
2170 * Disconnect-Request that arrived on rh's RadSec session while dynamic
2171 * authorization is not enabled over it -- REQ-DAE-SEC-016, RFC 6614 SS2.5.
2172 * There is no radcli_dae_st to build this through (dynamic authorization
2173 * is off, or in UDP mode): a standalone builder, using rh->so.static_secret
2174 * directly, matching build_reply()'s wire logic for just this one fixed
2175 * case (no Proxy-State mirroring -- there is no configured dae to have
2176 * asked for it). reqbuf need only be long enough to name a Code and
2177 * Identifier; nothing about it is otherwise trusted or verified before
2178 * replying, since the reply itself carries no secret worth protecting
2179 * (the RFC 6614/7360 secret is a fixed, public string). */
2180/*- Build and send a Disconnect-NAK/CoA-NAK with Error-Cause 406
2181 * ("Unsupported Extension") for reqbuf/reqlen -- see the comment above.
2182 *
2183 * @param rh the handle whose RadSec session the request arrived on.
2184 * @param reqbuf the request; only its Code and Identifier are read.
2185 * @param reqlen reqbuf's length in bytes.
2186 -*/
2187static void send_radsec_unsupported_nak(rc_handle *rh, const uint8_t *reqbuf, size_t reqlen)
2188{
2189 uint8_t out[AUTH_HDR_LEN + 6 + 2 + MD5_DIGEST_SIZE]; /* header + Error-Cause(6) + Message-Authenticator(2+16) */
2190 AUTH_HDR *auth = (AUTH_HDR *)out;
2191 radcli_avp_list *reply_attrs;
2192 const radcli_attr_def *d_ec;
2193 int encoded_len, total_length;
2194 size_t secretlen;
2195 uint8_t digest[AUTH_VECTOR_LEN];
2196 const char *secret = rh->so.static_secret;
2197
2198 if (reqlen < AUTH_HDR_LEN || secret == NULL)
2199 return;
2200
2201 reply_attrs = radcli_avp_list_new();
2202 if (reply_attrs == NULL)
2203 return;
2204
2205 d_ec = radcli_dict_lookup_num(rh, PW_ERROR_CAUSE, 0);
2206 if (d_ec != NULL)
2207 radcli_avp_add_uint32(reply_attrs, d_ec, 406);
2208
2209 auth->code = (reqbuf[0] == RADCLI_DISCONNECT_REQUEST) ? RADCLI_DISCONNECT_NAK : RADCLI_COA_NAK;
2210 auth->id = reqbuf[1];
2211 memcpy(auth->vector, reqbuf + 4, AUTH_VECTOR_LEN);
2212
2213 encoded_len = radcli_avp_encode(rh, reply_attrs, secret, reqbuf + 4,
2214 auth->data, sizeof(out) - AUTH_HDR_LEN - (2 + MD5_DIGEST_SIZE), NULL);
2215 radcli_avp_list_free(reply_attrs);
2216 if (encoded_len < 0)
2217 return;
2218
2219 total_length = AUTH_HDR_LEN + encoded_len;
2220 auth->length = htons((uint16_t)total_length);
2221 total_length = add_msg_auth_attr(rh, (char *)secret, auth, total_length);
2222
2223 secretlen = rc_secret_len(secret);
2224 if ((size_t)(sizeof(out) - total_length) < secretlen)
2225 return;
2226 memcpy(out + total_length, secret, secretlen);
2227 rc_md5_calc(digest, out, (size_t)total_length + secretlen);
2228 memcpy(auth->vector, digest, AUTH_VECTOR_LEN);
2229
2230 /* One non-blocking attempt, matching send_reply()'s own reasoning
2231 * (this can run from radcli_ctx_dispatch(), which must never turn
2232 * into a multi-second stall) -- but dropped rather than queued if it
2233 * would block: there is no radcli_dae_st here to hold a queue on
2234 * (dynamic authorization is off, or in UDP mode, which is exactly why
2235 * this path was reached at all), and RFC 6614 SS2.5's 406 signal is
2236 * best-effort, not a delivery this library owes a guarantee for. */
2237 radcli2_priv_tls_dae_send(rh, out, (size_t)total_length);
2238}
2239
2240/*- Process one RADIUS/TLS or RADIUS/DTLS record already known to carry
2241 * Code 40 (Disconnect-Request) or 43 (CoA-Request) -- called from lib/
2242 * tls.c's tls_recvfrom() (inline, mid-exchange) and from this file's own
2243 * radcli_ctx_dispatch() (via radcli2_priv_tls_dae_poll()). Never invokes a
2244 * registered radcli_dae_handler directly (that would let it run on
2245 * whatever thread/call stack happens to be inside rc_auth()/rc_acct() at
2246 * the time): a validated request is queued for radcli_ctx_dispatch() to
2247 * deliver, exactly as REQ-DAE-SEC-012's reentrancy guard already assumes.
2248 * If dynamic authorization is not enabled over RadSec at all (no active
2249 * radcli_dae, or one in UDP mode), replies with the RFC 6614 SS2.5-
2250 * mandated CoA-NAK/Disconnect-NAK (Error-Cause 406) instead.
2251 *
2252 * @param rh a handle to parsed configuration.
2253 * @param buf the received packet, header included.
2254 * @param len buf's length in bytes.
2255 -*/
2256void radcli2_priv_dae_on_radsec_packet(rc_handle *rh, const uint8_t *buf, size_t len)
2257{
2258 struct radcli_dae_st *dae = rh->active_dae;
2259 uint8_t local_buf[RC_BUFFER_LEN];
2260 struct radcli_dae_request_st *req = NULL;
2261
2262 if (dae == NULL || !dae->radsec) {
2263 send_radsec_unsupported_nak(rh, buf, len);
2264 return;
2265 }
2266
2267 if (len == 0 || len > sizeof(local_buf) - 1)
2268 return;
2269
2270 /* process_packet() mutates the buffer in place (verify_request_
2271 * authenticator() zeroes and restores the Authenticator field) --
2272 * copy so the caller's (lib/tls.c's) own receive buffer is untouched. */
2273 memcpy(local_buf, buf, len);
2274
2275 /* Guards dae->slots[] (process_packet()'s duplicate-suppression
2276 * table) and dae->radsec_queue[] against the concurrent caller this
2277 * function can have: the thread currently inside an in-flight
2278 * radcli_transport_exchange() (tls_recvfrom()'s inline demux, holding
2279 * the *session* lock, not this one) and radcli_ctx_dispatch()'s poll
2280 * thread (which has already released the session lock by the time it
2281 * gets here -- radcli2_priv_tls_dae_poll() only holds it for the read
2282 * itself) can otherwise both be inside this function at once. */
2283 pthread_mutex_lock(&dae->radsec_lock);
2284 switch (process_packet(dae, local_buf, len, NULL, 0, &req)) {
2285 case PROCESS_NEW:
2286 radsec_queue_push(dae, req);
2287 break;
2288 case PROCESS_DUP_ANSWERED:
2289 req->replied = 1; /* answered from the cached decision, not a fresh one */
2290 send_reply(req, req->cached_reply_code, req->cached_error_cause);
2292 break;
2293 case PROCESS_DROP:
2294 break;
2295 }
2296 pthread_mutex_unlock(&dae->radsec_lock);
2297}
2298
2324int radcli_ctx_dispatch(radcli_ctx *ctx)
2325{
2326 rc_handle *rh = (rc_handle *)ctx;
2327 struct radcli_dae_st *dae;
2328 uint8_t buf[RC_BUFFER_LEN];
2329 struct sockaddr_storage from;
2330 socklen_t fromlen;
2331 ssize_t n;
2332 struct radcli_dae_request_st *req = NULL;
2333
2334 if (rh == NULL)
2335 return -1;
2336
2337 if (rh->in_dispatch) {
2338 rc_log(LOG_ERR, "radcli_ctx_dispatch: reentrant call");
2339 return -1;
2340 }
2341
2342 rh->in_dispatch = 1;
2343
2344 /* REQ-NET2-SEND-013: unconditional and non-blocking, regardless of
2345 * transport or whether any RADCLI_REQUEST_SENDONLY exchange is
2346 * actually in flight (both functions no-op on a NULL rh->reqreg). */
2347 radcli2_priv_reqreg_drain(rh);
2348 radcli2_priv_reqreg_service_timeouts(rh);
2349
2350 if (rh->so_type == RC_SOCKET_TLS || rh->so_type == RC_SOCKET_DTLS) {
2351 /* REQ-WATCHDOG-NET-001: folded in here rather than left as a
2352 * separate caller-invoked call -- still never radcli calling
2353 * itself unprompted (REQ-GEN-SEC-003): this only ever runs
2354 * inside a radcli_ctx_dispatch() call the application itself
2355 * makes, on its own schedule, driven by radcli_ctx_get_poll()'s
2356 * advisory timeout_ms (watchdog_deadline_ms(), above it in this
2357 * file). */
2358 if (watchdog_deadline_ms(rh, radcli2_priv_tls_fd(rh)) == 0)
2359 radcli2_priv_dae_send_watchdog(ctx);
2360 }
2361
2362 dae = rh->active_dae;
2363 if (dae == NULL) {
2364 rh->in_dispatch = 0;
2365 return 0;
2366 }
2367
2368 if (dae->radsec) {
2369 /* REQ-DAE-SEC-013: retry any replies send_reply() deferred
2370 * earlier before doing anything else -- this call may be here
2371 * because radcli_ctx_get_poll() reported POLLOUT specifically
2372 * for this, not because there is new data to read at all. One
2373 * non-blocking attempt per queued reply; never a wait. */
2374 radsec_flush_reply_queue(dae);
2375
2376 /* radcli2_priv_tls_dae_poll() leaves the session lock held on
2377 * success (>0) -- radcli2_priv_dae_on_radsec_packet() needs it
2378 * held for the whole call (it takes dae->radsec_lock underneath,
2379 * and consistently nesting session-lock-outside-radsec_lock on
2380 * every call path, including tls_recvfrom()'s own inline demux,
2381 * is what avoids a lock-order inversion between the two -- see
2382 * lib/tls.c's doc comment on radcli2_priv_tls_dae_poll()). */
2383 int ret = radcli2_priv_tls_dae_poll(rh, buf, sizeof(buf) - 1);
2384
2385 if (ret > 0) {
2386 if (buf[0] == RADCLI_DISCONNECT_REQUEST || buf[0] == RADCLI_COA_REQUEST) {
2387 radcli2_priv_dae_on_radsec_packet(rh, buf, (size_t)ret);
2388 } else {
2389 rc_log(LOG_INFO, "radcli_ctx_dispatch: unexpected packet code "
2390 "%u on the RadSec session while idle, ignored",
2391 (unsigned)buf[0]);
2392 }
2393 radcli2_priv_tls_dae_poll_done(rh);
2394 }
2395
2396 /* Drain whatever is queued -- from the poll above, or from an
2397 * in-flight radcli_transport_exchange() on another thread that
2398 * already demuxed a record via tls_recvfrom()'s inline path.
2399 * radsec_lock (not the session lock, already released above)
2400 * guards radsec_queue[] here, matching radsec_queue_push()'s own
2401 * locking in radcli2_priv_dae_on_radsec_packet(). */
2402 for (;;) {
2403 pthread_mutex_lock(&dae->radsec_lock);
2404 req = radsec_queue_pop(dae);
2405 pthread_mutex_unlock(&dae->radsec_lock);
2406 if (req == NULL)
2407 break;
2408 if (dae->handler != NULL)
2409 dae->handler((radcli_dae_request *)req, dae->handler_user);
2410 else
2412 }
2413
2414 rh->in_dispatch = 0;
2415 return 0;
2416 }
2417
2418 if (dae->fd == -1) {
2419 /* Constructed but never (successfully) started -- nothing DAE-
2420 * specific to do, but the reqreg/watchdog work above may already
2421 * have done something useful, so this is not itself a failure. */
2422 rh->in_dispatch = 0;
2423 return 0;
2424 }
2425
2426 fromlen = sizeof(from);
2427 n = recvfrom(dae->fd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&from, &fromlen);
2428 if (n < 0) {
2429 /* EAGAIN/EWOULDBLOCK ("nothing to read after all") and any other
2430 * receive error both simply mean there is nothing to deliver
2431 * this call; a socket-level error is not the caller's to act
2432 * on here. */
2433 rh->in_dispatch = 0;
2434 return 0;
2435 }
2436
2437 switch (process_packet(dae, buf, (size_t)n, (struct sockaddr *)&from, fromlen, &req)) {
2438 case PROCESS_NEW:
2439 if (dae->handler != NULL)
2440 dae->handler((radcli_dae_request *)req, dae->handler_user);
2441 else
2443 break;
2444 case PROCESS_DUP_ANSWERED:
2445 req->replied = 1; /* answered from the cached decision, not a fresh one */
2446 send_reply(req, req->cached_reply_code, req->cached_error_cause);
2448 break;
2449 case PROCESS_DROP:
2450 break;
2451 }
2452
2453 rh->in_dispatch = 0;
2454 return 0;
2455}
2456
2487int radcli_dae_process(radcli_dae *dae, const void *buf, size_t len,
2488 const struct sockaddr *from, socklen_t fromlen,
2489 radcli_dae_request **req)
2490{
2491 struct radcli_dae_request_st *built_req = NULL;
2492 uint8_t local_buf[RC_BUFFER_LEN];
2493 enum process_result result;
2494
2495 if (dae == NULL || buf == NULL || from == NULL || req == NULL ||
2496 len == 0 || len > sizeof(local_buf) - 1)
2497 return -1;
2498 *req = NULL;
2499
2500 /* This L0 entry point has no meaning under RadSec: process_packet()'s
2501 * radsec branch trusts the record's origin entirely to rh's own
2502 * TLS-verified session (REQ-DAE-SEC-015) and does not check a source
2503 * address at all -- accepting an arbitrary caller-supplied buf/from
2504 * pair here would let any caller feed it unauthenticated bytes as if
2505 * they had arrived on that session, with only the RFC 6614/7360 fixed
2506 * (not actually secret) string standing in for authentication. It
2507 * would also bypass radsec_lock entirely, racing lib/tls.c's own
2508 * calls into the same dae. Reject outright rather than accept a
2509 * caller's buffer as a substitute for the real session. */
2510 if (dae->radsec)
2511 return -1;
2512
2513 /* fromlen is caller-supplied, not kernel-supplied (unlike
2514 * radcli_ctx_dispatch()'s recvfrom(), which sets it itself): a caller
2515 * relaying an address from an untrusted producer -- e.g. over IPC
2516 * from a privileged listener, the documented reason this L0 entry
2517 * point exists at all -- could pass a fromlen that does not match
2518 * from's actual family, or one large enough to overflow
2519 * req->from (struct sockaddr_storage) once process_packet() below
2520 * does memcpy(&req->from, from, fromlen). Bound it against both the
2521 * declared family and struct sockaddr_storage before trusting it. */
2522 if (fromlen < sizeof(struct sockaddr_in) || fromlen > sizeof(struct sockaddr_storage))
2523 return -1;
2524 if (from->sa_family == AF_INET6 && fromlen < sizeof(struct sockaddr_in6))
2525 return -1;
2526 if (from->sa_family != AF_INET && from->sa_family != AF_INET6)
2527 return -1;
2528
2529 /* process_packet() mutates the buffer in place (verify_request_
2530 * authenticator() zeroes and restores the Authenticator field) --
2531 * copy so the caller's own buffer is never touched. */
2532 memcpy(local_buf, buf, len);
2533
2534 result = process_packet(dae, local_buf, len, from, fromlen, &built_req);
2535 if (result == PROCESS_DROP)
2536 return -1;
2537
2538 *req = (radcli_dae_request *)built_req;
2539 return (result == PROCESS_DUP_ANSWERED) ? RADCLI_DAE_DUPLICATE : RADCLI_DAE_NEW;
2540}
int radcli_dae_req_nas_port(const radcli_dae_request *req, uint32_t *out)
Return the request's NAS-Port.
Definition dae.c:1704
void radcli_dae_free(radcli_dae *dae)
Release a listener, closing its socket if radcli_dae_start() opened one.
Definition dae.c:978
const char * radcli_dae_req_session_id(const radcli_dae_request *req)
Return the request's Acct-Session-Id, if it carried one.
Definition dae.c:1621
void(* radcli_dae_handler)(radcli_dae_request *req, void *user)
Application callback invoked by radcli_ctx_dispatch() for a validated request.
Definition radcli2.h:703
int radcli_ctx_get_poll(radcli_ctx *ctx, struct pollfd *pfds, size_t max_pfds, size_t *nfds, int *timeout_ms)
Report what to wait for on ctx's behalf, for the caller's own event loop – radcli never calls poll()/...
Definition dae.c:1091
void radcli_dae_set_handler(radcli_dae *dae, radcli_dae_handler cb, void *user)
Register the callback radcli_ctx_dispatch() invokes for each validated request. May be called before ...
Definition dae.c:852
#define RADCLI_DAE_NEW
Definition radcli2.h:728
radcli_code radcli_dae_req_code(const radcli_dae_request *req)
Return the received packet's RADIUS code.
Definition dae.c:1593
int radcli_ctx_dispatch(radcli_ctx *ctx)
Read what is ready on ctx's descriptor(s), validate it, and invoke the registered handler for anythin...
Definition dae.c:2324
int radcli_dae_reply(radcli_dae_request *req, int ack)
Answer a request with an ACK or NAK, selecting 41/42 or 44/45 from the request's own code,...
Definition dae.c:1806
struct radcli_dae_st radcli_dae
Definition radcli2.h:678
const char * radcli_dae_req_user_name(const radcli_dae_request *req)
Return the request's User-Name, if it carried one.
Definition dae.c:1634
int radcli_dae_start(radcli_dae *dae)
Start receiving: binds the socket described by dae-listen.
Definition dae.c:896
const radcli_avp_list * radcli_dae_req_attrs(const radcli_dae_request *req)
Return the request's decoded attributes.
Definition dae.c:1608
int radcli_dae_reply_to_buffer(radcli_dae_request *req, int ack, uint32_t error_cause, void *buf, size_t *len)
Produce a reply as bytes instead of sending it – the L0 counterpart of radcli_dae_reply()/radcli_dae_...
Definition dae.c:1852
#define RADCLI_CTX_MAX_POLLFDS
Definition radcli2.h:764
int radcli_dae_process(radcli_dae *dae, const void *buf, size_t len, const struct sockaddr *from, socklen_t fromlen, radcli_dae_request **req)
Validate a caller-supplied packet, without a radcli-owned socket – the L0 counterpart of radcli_ctx_d...
Definition dae.c:2487
#define RADCLI_DAE_DUPLICATE
Definition radcli2.h:733
int radcli_dae_reply_error(radcli_dae_request *req, uint32_t error_cause)
Answer a request with a NAK carrying the given Error-Cause.
Definition dae.c:1821
radcli_dae * radcli_dae_new(radcli_ctx *ctx, unsigned flags)
Validate dae-* configuration and build a dynamic-authorization listener. Opens no socket – see radcli...
Definition dae.c:638
int radcli_dae_req_framed_ip(const radcli_dae_request *req, struct sockaddr_storage *out)
Return the request's Framed-IP-Address or Framed-IPv6-Address.
Definition dae.c:1649
struct radcli_dae_request_st radcli_dae_request
Definition radcli2.h:688
void radcli_dae_request_free(radcli_dae_request *req)
Release a request.
Definition dae.c:1890
@ RADCLI_DAE_NO_NAS_CHECK
Definition radcli2.h:745
void radcli_avp_list_free(radcli_avp_list *list)
Free a list and every attribute it holds.
Definition avp.c:159
radcli_avp_iter radcli_avp_list_iter(const radcli_avp_list *list)
Begin iterating list.
Definition avp.c:666
const radcli_avp * radcli_avp_get(const radcli_avp_list *list, const radcli_attr_def *def, unsigned idx)
Find the idx-th occurrence of an attribute in a list.
Definition avp.c:644
int radcli_avp_get_ip6(const radcli_avp *a, struct in6_addr *out, unsigned *prefix)
Read an attribute's value as an IPv6 address or prefix.
Definition avp.c:769
int radcli_avp_add_bytes(radcli_avp_list *list, const radcli_attr_def *def, const void *value, size_t len)
Append an attribute holding an arbitrary byte string.
Definition avp.c:224
int radcli_avp_get_uint32(const radcli_avp *a, uint32_t *out)
Read an attribute's value as an integer/IPv4-address/date.
Definition avp.c:716
radcli_avp_list * radcli_avp_list_new(void)
Create an empty attribute-value pair list.
Definition avp.c:145
const radcli_attr_def * radcli_avp_def(const radcli_avp *a)
Return the attribute definition of a.
Definition avp.c:703
int radcli_avp_add_uint32(radcli_avp_list *list, const radcli_attr_def *def, uint32_t value)
Append an integer/IPv4-address/date-typed attribute.
Definition avp.c:323
const radcli_avp * radcli_avp_iter_next(radcli_avp_iter *it)
Return the current attribute and advance.
Definition avp.c:680
int radcli_avp_get_bytes(const radcli_avp *a, const void **out, size_t *len)
Read an attribute's value as raw bytes.
Definition avp.c:853
const radcli_attr_def * radcli_dict_lookup_num(const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor)
Look up a dictionary attribute by its legacy numeric ID and vendor.
Definition dict2.c:564
radcli_code
Definition radcli2.h:506