Radcli library 2.0.0
A simple radius library -- new API reference
Loading...
Searching...
No Matches
avp.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
25#include <config.h>
26#include <includes.h>
27#include <radcli/radcli.h>
28#include <radcli/radcli2.h>
29#include <ccan/list/list.h>
30#include <utf8decode/utf8decode.h>
31#include "dict2.h"
32#include "util.h"
33#include "rc-crypto.h"
34#include "avp.h"
35#include "options.h"
36
40
46
47/* radcli_avp/radcli_avp_list construction and access. Values are always
48 * stored as heap-allocated, length-carrying bytes -- there is no
49 * 253-octet ceiling as in VALUE_PAIR. Typed setters/getters interpret
50 * those bytes according to the attribute's radcli_attr_type, validated
51 * against radcli_attr_def_type(); wire encoding/decoding is
52 * radcli_avp_encode()/radcli_avp_decode(), further down this file.
53 *
54 * radcli_avp_add_uint64()/radcli_avp_get_uint64() are meaningful only for
55 * RADCLI_TYPE_INTEGER64 (RFC 8044 SS3.3's "integer64" data type; see
56 * radcli2.h), the dictionary attribute type introduced alongside them for
57 * MIP6-Feature-Vector (RFC 5447 SS4.2.5), the one standard attribute of
58 * this type, and RADCLI_TYPE_IFID (RFC 8044 SS3.7's "ifid" data type,
59 * e.g. Framed-Interface-Id, RFC 3162 SS2.3) -- the two share this pair
60 * because both are 8 raw octets, network byte order, on the wire.
61 */
62
63/* Doubly-linked via ccan/list rather than a hand-rolled next-only chain, to
64 * match the rest of the codebase's convention for its (few) other linked
65 * structures -- see lib/dict.c's dict_encrypt_flag / dict_counter64_pair for
66 * the singly-linked style used for prepend-only, walk-to-free dictionary
67 * side tables.
68 *
69 * No owner back-pointer: radcli_avp_iter (radcli2.h) carries the list
70 * alongside the current position, the same two things any ccan/list or
71 * kernel-list iterator needs (a head to detect end-of-list, a node for
72 * position) -- so unlike an earlier version of this API, no per-node state
73 * is needed just to answer "am I at the end".
74 *
75 * Locking: none, by design -- same contract as ccan/list itself and as
76 * kernel intrusive lists generally. A radcli_avp_list is built by exactly
77 * one thread (radcli_avp_list_new() plus radcli_avp_add_*()/
78 * radcli_avp_decode()) and is expected to have a single owner at a time
79 * thereafter; concurrent readers are fine, a concurrent mutator is not.
80 * Nothing here takes a lock, so callers sharing one list across threads
81 * (e.g. handing decoded request avps to a worker while another thread still
82 * walks them) must serialise that themselves. radcli_avp_iter is a plain
83 * value (no allocation, safe to copy, safe to run several independent
84 * iterators over one list concurrently, as long as nothing is mutating it),
85 * but it is only valid as long as the radcli_avp_list it was constructed
86 * from is; nothing detects use of an iterator, or an avp it returned, past
87 * that list's radcli_avp_list_free(), same as walking a kernel list past
88 * its lifetime. */
89struct radcli_avp_st {
90 const radcli_attr_def *def;
91 struct list_node node;
92 size_t len; /* data holds len bytes, possibly 0 */
93 unsigned char data[];
94};
95
96struct radcli_avp_list_st {
97 struct list_head head;
98 int error; /* sticky: set by the first failed add call, see radcli_avp_list_error() */
99};
100
101/* linux/list.h has list_is_last(pos, head): true if pos is the last entry,
102 * for callers that only have a node, not the list_head, and need to detect
103 * end-of-list without walking off it. ccan/list has no equivalent -- every
104 * built-in helper takes the head and iterates from there -- so this is that
105 * primitive, backing radcli_avp_iter_next() below, rather than a one-off
106 * inline pointer compare. */
107/*- Report whether n is the last node in list h.
108 *
109 * @param n the node to check.
110 * @param h the list n belongs to.
111 * @return true if n has no successor in h, false otherwise.
112 -*/
113static inline bool avp_list_node_is_last(const struct list_node *n, const struct list_head *h)
114{
115 return n->next == &h->n;
116}
117
118/* Every radcli_avp_add_*()/_by_num() failure routes through here (including
119 * radcli_avp_add_bytes()'s own -- the primitive every other add is defined
120 * in terms of, but not every add's own validation happens inside it, e.g.
121 * radcli_avp_add_str()'s RADCLI_TYPE_STRING check runs before it would ever
122 * call radcli_avp_add_bytes()), so radcli_avp_list_error() sees every
123 * failure regardless of which layer detected it. NULL-safe: a caller
124 * chaining adds onto a list that never allocated (radcli_avp_list_new()
125 * returned NULL) still gets a well-defined -1/NULL from every call in this
126 * file, not a crash. */
127/*- Mark l as having failed and return -1, the common tail of every
128 * radcli_avp_add_*() failure path.
129 *
130 * @param l the list to mark; NULL-safe (a no-op then).
131 * @return always -1.
132 -*/
133static int avp_list_fail(radcli_avp_list *l)
134{
135 struct radcli_avp_list_st *list = (struct radcli_avp_list_st *)l;
136
137 if (list != NULL)
138 list->error = 1;
139 return -1;
140}
141
145radcli_avp_list *radcli_avp_list_new(void)
146{
147 struct radcli_avp_list_st *list = calloc(1, sizeof(*list));
148 if (list == NULL) {
149 rc_log(LOG_CRIT, "radcli_avp_list_new: out of memory");
150 return NULL;
151 }
152 list_head_init(&list->head);
153 return (radcli_avp_list *)list;
154}
155
159void radcli_avp_list_free(radcli_avp_list *list)
160{
161 struct list_node *n, *next;
162
163 if (list == NULL)
164 return;
165
166 /* list_check(), for parity with every other entry point in this file
167 * (list_add_tail()/list_for_each() all run it via list_debug()): a
168 * no-op build (CCAN_LIST_DEBUG unset, as this tree always builds) costs
169 * nothing, but it means turning that debug knob on to chase a real
170 * corruption gets coverage here too, not just on insert/lookup. */
171 (void)list_debug(&list->head);
172
173 /* Walk list->head's chain directly and capture next before freeing the
174 * current node, rather than re-deriving the first node with list_top()
175 * once per iteration (as an earlier version of this loop did, replacing
176 * an even earlier list_for_each_safe() that a static analyzer read as a
177 * double-free -- see git history). Once the list is non-empty and
178 * populated from real decoded data (radcli_avp_decode()'s error path,
179 * rather than a small hand-built test list), re-entering list_top()
180 * after each free() gave the same analyzer a *use-after-free* reading
181 * of the identical list_top_() pointer arithmetic instead.
182 *
183 * The actual fix is this loop never converting the sentinel node
184 * (list->head.n) into a (fictitious, one-before-the-allocation)
185 * struct radcli_avp_st * at all: list_entry()/container_of() is only
186 * ever applied to "n" after the loop condition has confirmed it is a
187 * real entry, unlike list_for_each_off()/list_for_each_safe_off(),
188 * which unconditionally form that phantom pointer every iteration
189 * (including the terminating one) purely to compare it against the
190 * head. That phantom pointer is never dereferenced and is exactly the
191 * trick struct list_head-based kernel lists rely on too, but it is
192 * what a static analyzer sees and objects to; not forming it avoids
193 * relying on that "never dereferenced" caveat in the first place.
194 *
195 * list_del() still runs on each node before free(), the standard
196 * "list_del(&pos->list); kfree(pos);" kernel teardown idiom applied
197 * one node at a time: it keeps list->head's links correct at every
198 * step rather than only at the end, and under CCAN_LIST_DEBUG it
199 * poisons the freed node's next/prev, turning any stray second use of
200 * a dangling avp into a NULL-pointer deref instead of walking into
201 * whatever the allocator put in that freed slot next. */
202 for (n = list->head.n.next; n != &list->head.n; n = next) {
203 struct radcli_avp_st *a = list_entry(n, struct radcli_avp_st, node);
204
205 next = n->next;
206 list_del(n);
207 free(a);
208 }
209 free(list);
210}
211
224int radcli_avp_add_bytes(radcli_avp_list *list, const radcli_attr_def *def, const void *value, size_t len)
225{
226 struct radcli_avp_st *a;
227
228 if (list == NULL || def == NULL)
229 return avp_list_fail(list);
230 if (len > 0 && value == NULL)
231 return avp_list_fail(list);
232
233 /* Single allocation for the header and its data: data is never
234 * resized after creation, so there is no reason to keep them apart.
235 * One byte larger than len -- calloc() already zero-fills it, so
236 * data[len] is always a free NUL terminator, letting
237 * radcli_avp_get_cstr() hand back a C-string view with no separate
238 * allocation or copy at read time. */
239 a = calloc(1, sizeof(*a) + len + 1);
240 if (a == NULL) {
241 rc_log(LOG_CRIT, "radcli_avp_add_bytes: out of memory");
242 return avp_list_fail(list);
243 }
244
245 if (len > 0)
246 memcpy(a->data, value, len);
247 a->len = len;
248 a->def = def;
249
250 list_add_tail(&list->head, &a->node);
251
252 return 0;
253}
254
255/* RFC 8044 SS3.1 requires RADCLI_TYPE_TEXT values to be valid UTF-8.
256 * lib/utf8decode/utf8decode.h's decode() alone does not reject an
257 * embedded NUL -- U+0000 is a legal Unicode code point, encoded as a
258 * single 0x00 byte -- so that is checked explicitly here too. */
259/*- Validate that buf holds RFC 8044 §3.1-conformant UTF-8, rejecting an
260 * embedded NUL as well as malformed encoding.
261 *
262 * @param buf the bytes to validate.
263 * @param len buf's length in bytes.
264 * @return nonzero if buf is valid UTF-8 with no embedded NUL, zero otherwise.
265 -*/
266static int is_valid_utf8(const void *buf, size_t len)
267{
268 const unsigned char *p = buf;
269 uint32_t state = UTF8_ACCEPT, codep = 0;
270 size_t i;
271
272 for (i = 0; i < len; i++) {
273 if (p[i] == 0)
274 return 0;
275 if (decode(&state, &codep, p[i]) == UTF8_REJECT)
276 return 0;
277 }
278 return state == UTF8_ACCEPT;
279}
280
299int radcli_avp_add_str(radcli_avp_list *list, const radcli_attr_def *def, const char *value)
300{
302
303 if (def == NULL || value == NULL)
304 return avp_list_fail(list);
305 t = radcli_attr_def_type(def);
306 if (t != RADCLI_TYPE_STRING && t != RADCLI_TYPE_TEXT)
307 return avp_list_fail(list);
308 /* A NUL-terminated C string can never itself contain the embedded
309 * NUL is_valid_utf8() also checks for -- strlen() below stops at the
310 * first one -- so that check is redundant here, but harmless and
311 * kept for a single shared implementation with radcli_avp_get_cstr(). */
312 if (t == RADCLI_TYPE_TEXT && !is_valid_utf8(value, strlen(value)))
313 return avp_list_fail(list);
314 return radcli_avp_add_bytes(list, def, value, strlen(value));
315}
316
323int radcli_avp_add_uint32(radcli_avp_list *list, const radcli_attr_def *def, uint32_t value)
324{
326
327 if (def == NULL)
328 return avp_list_fail(list);
329 t = radcli_attr_def_type(def);
331 return avp_list_fail(list);
332 return radcli_avp_add_bytes(list, def, &value, sizeof(value));
333}
334
345int radcli_avp_add_uint64(radcli_avp_list *list, const radcli_attr_def *def, uint64_t value)
346{
348
349 if (def == NULL)
350 return avp_list_fail(list);
351 t = radcli_attr_def_type(def);
353 return avp_list_fail(list);
354 return radcli_avp_add_bytes(list, def, &value, sizeof(value));
355}
356
363int radcli_avp_add_ip4(radcli_avp_list *list, const radcli_attr_def *def, struct in_addr value)
364{
365 uint32_t hostval;
366
367 if (def == NULL || radcli_attr_def_type(def) != RADCLI_TYPE_IPADDR)
368 return avp_list_fail(list);
369 hostval = ntohl(value.s_addr);
370 return radcli_avp_add_bytes(list, def, &hostval, sizeof(hostval));
371}
372
386int radcli_avp_add_ip6(radcli_avp_list *list, const radcli_attr_def *def,
387 const struct in6_addr *value, unsigned prefix)
388{
390 unsigned char buf[18]; /* RFC 3162: reserved(1) + prefix-len(1) + address(16) */
391
392 if (def == NULL || value == NULL)
393 return avp_list_fail(list);
394 t = radcli_attr_def_type(def);
395
396 if (t == RADCLI_TYPE_IPV6ADDR) {
397 if (prefix != 0)
398 return avp_list_fail(list);
399 return radcli_avp_add_bytes(list, def, value, 16);
400 }
401 if (t == RADCLI_TYPE_IPV6PREFIX) {
402 if (prefix > 128)
403 return avp_list_fail(list);
404 buf[0] = 0;
405 buf[1] = (unsigned char)prefix;
406 memcpy(buf + 2, value, 16);
407 return radcli_avp_add_bytes(list, def, buf, sizeof(buf));
408 }
409 return avp_list_fail(list);
410}
411
429int radcli_avp_add_ip4prefix(radcli_avp_list *list, const radcli_attr_def *def,
430 struct in_addr value, unsigned prefix)
431{
432 unsigned char buf[6]; /* RFC 8044 SS3.9: reserved(1) + prefix-len(1) + address(4) */
433
434 if (def == NULL || radcli_attr_def_type(def) != RADCLI_TYPE_IPV4PREFIX)
435 return avp_list_fail(list);
436 if (prefix > 32)
437 return avp_list_fail(list);
438 buf[0] = 0;
439 buf[1] = (unsigned char)prefix;
440 /* struct in_addr's s_addr is already network byte order, same as the
441 * wire format's address octets -- no ntohl()/htonl() round trip
442 * needed, unlike radcli_avp_add_ip4()'s RADCLI_TYPE_IPADDR case whose
443 * *internal* representation is host byte order. */
444 memcpy(buf + 2, &value.s_addr, 4);
445 return radcli_avp_add_bytes(list, def, buf, sizeof(buf));
446}
447
448/* _by_num() wrappers: fold the radcli_dict_lookup_num() a caller would
449 * otherwise write inline into the add call itself, so a well-known
450 * attribute goes back to being a single call with a single failure path --
451 * the rc_avpair_add() ergonomics of radcli.h -- instead of a separate
452 * lookup, a NULL check, and then the add. See radexample.c and
453 * REQ-GEN-STYLE-002 (doc/requirements/general.md) for the caller-burden
454 * reasoning.
455 */
456
466int radcli_avp_add_bytes_by_num(radcli_avp_list *list, const radcli_ctx *ctx,
467 uint32_t attrid, uint32_t vendor,
468 const void *value, size_t len)
469{
470 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, attrid, vendor);
471
472 if (def == NULL)
473 return avp_list_fail(list);
474 return radcli_avp_add_bytes(list, def, value, len);
475}
476
485int radcli_avp_add_str_by_num(radcli_avp_list *list, const radcli_ctx *ctx,
486 uint32_t attrid, uint32_t vendor, const char *value)
487{
488 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, attrid, vendor);
489
490 if (def == NULL)
491 return avp_list_fail(list);
492 return radcli_avp_add_str(list, def, value);
493}
494
503int radcli_avp_add_uint32_by_num(radcli_avp_list *list, const radcli_ctx *ctx,
504 uint32_t attrid, uint32_t vendor, uint32_t value)
505{
506 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, attrid, vendor);
507
508 if (def == NULL)
509 return avp_list_fail(list);
510 return radcli_avp_add_uint32(list, def, value);
511}
512
521int radcli_avp_add_uint64_by_num(radcli_avp_list *list, const radcli_ctx *ctx,
522 uint32_t attrid, uint32_t vendor, uint64_t value)
523{
524 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, attrid, vendor);
525
526 if (def == NULL)
527 return avp_list_fail(list);
528 return radcli_avp_add_uint64(list, def, value);
529}
530
539int radcli_avp_add_ip4_by_num(radcli_avp_list *list, const radcli_ctx *ctx,
540 uint32_t attrid, uint32_t vendor, struct in_addr value)
541{
542 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, attrid, vendor);
543
544 if (def == NULL)
545 return avp_list_fail(list);
546 return radcli_avp_add_ip4(list, def, value);
547}
548
558int radcli_avp_add_ip6_by_num(radcli_avp_list *list, const radcli_ctx *ctx,
559 uint32_t attrid, uint32_t vendor,
560 const struct in6_addr *value, unsigned prefix)
561{
562 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, attrid, vendor);
563
564 if (def == NULL)
565 return avp_list_fail(list);
566 return radcli_avp_add_ip6(list, def, value, prefix);
567}
568
578int radcli_avp_add_ip4prefix_by_num(radcli_avp_list *list, const radcli_ctx *ctx,
579 uint32_t attrid, uint32_t vendor,
580 struct in_addr value, unsigned prefix)
581{
582 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, attrid, vendor);
583
584 if (def == NULL)
585 return avp_list_fail(list);
586 return radcli_avp_add_ip4prefix(list, def, value, prefix);
587}
588
610int radcli_avp_add_username(radcli_avp_list *list, const radcli_ctx *ctx,
611 const char *username, const char *realm)
612{
613 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, PW_USER_NAME, 0);
614 char *composed;
615 size_t len;
616 int ret;
617
618 if (def == NULL || username == NULL)
619 return avp_list_fail(list);
620
621 if (realm == NULL)
622 realm = rc_conf_str_id((rc_handle const *)ctx, OPT_DEFAULT_REALM);
623
624 if (strchr(username, '@') != NULL || realm == NULL || realm[0] == 0)
625 return radcli_avp_add_str(list, def, username);
626
627 len = strlen(username) + 1 + strlen(realm) + 1;
628 composed = malloc(len);
629 if (composed == NULL)
630 return avp_list_fail(list);
631 snprintf(composed, len, "%s@%s", username, realm);
632
633 ret = radcli_avp_add_str(list, def, composed);
634 free(composed);
635 return ret;
636}
637
644const radcli_avp *radcli_avp_get(const radcli_avp_list *list, const radcli_attr_def *def, unsigned idx)
645{
646 const struct radcli_avp_st *a = NULL;
647 unsigned n = 0;
648
649 if (list == NULL || def == NULL)
650 return NULL;
651
652 list_for_each(&list->head, a, node) {
653 if (a->def == def) {
654 if (n == idx)
655 return (const radcli_avp *)a;
656 n++;
657 }
658 }
659 return NULL;
660}
661
666radcli_avp_iter radcli_avp_list_iter(const radcli_avp_list *list)
667{
669
670 it.list = list;
671 it.cur = list ? (const radcli_avp *)list_top(&list->head, struct radcli_avp_st, node) : NULL;
672 return it;
673}
674
681{
682 const struct radcli_avp_st *avp = (const struct radcli_avp_st *)it->cur;
683 const radcli_avp *ret = it->cur;
684
685 /* it->cur is resolved once, at radcli_avp_list_iter() construction (the
686 * first element, or NULL for an empty/NULL list) or right here on each
687 * advance -- never re-derived from it->list once it goes NULL. That is
688 * what makes NULL mean "exhausted" unconditionally: a caller that calls
689 * this again after already seeing NULL keeps getting NULL, rather than
690 * silently restarting from the top. */
691 if (avp != NULL) {
692 const struct radcli_avp_list_st *list = (const struct radcli_avp_list_st *)it->list;
693
694 if (avp_list_node_is_last(&avp->node, &list->head))
695 it->cur = NULL;
696 else
697 it->cur = (const radcli_avp *)list_entry(avp->node.next, struct radcli_avp_st, node);
698 }
699 return ret;
700}
701
703const radcli_attr_def *radcli_avp_def(const radcli_avp *a)
704{
705 const struct radcli_avp_st *avp = (const struct radcli_avp_st *)a;
706 return avp ? avp->def : NULL;
707}
708
716int radcli_avp_get_uint32(const radcli_avp *a, uint32_t *out)
717{
718 const struct radcli_avp_st *avp = (const struct radcli_avp_st *)a;
720
721 if (avp == NULL || avp->def == NULL)
722 return -1;
723 t = radcli_attr_def_type(avp->def);
725 return -1;
726 if (avp->len != sizeof(uint32_t))
727 return -1;
728
729 if (out)
730 memcpy(out, avp->data, sizeof(uint32_t));
731 return 0;
732}
733
742int radcli_avp_get_uint64(const radcli_avp *a, uint64_t *out)
743{
744 const struct radcli_avp_st *avp = (const struct radcli_avp_st *)a;
746
747 if (avp == NULL || avp->def == NULL)
748 return -1;
749 t = radcli_attr_def_type(avp->def);
751 return -1;
752 if (avp->len != sizeof(uint64_t))
753 return -1;
754
755 if (out)
756 memcpy(out, avp->data, sizeof(uint64_t));
757 return 0;
758}
759
769int radcli_avp_get_ip6(const radcli_avp *a, struct in6_addr *out, unsigned *prefix)
770{
771 const struct radcli_avp_st *avp = (const struct radcli_avp_st *)a;
773
774 if (avp == NULL || avp->def == NULL)
775 return -1;
776 t = radcli_attr_def_type(avp->def);
777
778 if (t == RADCLI_TYPE_IPV6ADDR) {
779 if (avp->len != 16)
780 return -1;
781 if (out)
782 memcpy(out, avp->data, 16);
783 if (prefix)
784 *prefix = 128;
785 return 0;
786 }
787 if (t == RADCLI_TYPE_IPV6PREFIX) {
788 const unsigned char *p = avp->data;
789 size_t addrbytes;
790
791 if (avp->len < 2 || avp->len > 18)
792 return -1;
793 addrbytes = avp->len - 2;
794
795 if (out) {
796 memset(out, 0, 16);
797 memcpy(out, p + 2, addrbytes);
798 }
799 if (prefix)
800 *prefix = p[1];
801 return 0;
802 }
803 return -1;
804}
805
813int radcli_avp_get_ip4prefix(const radcli_avp *a, struct in_addr *out, unsigned *prefix)
814{
815 const struct radcli_avp_st *avp = (const struct radcli_avp_st *)a;
816 const unsigned char *p;
817 size_t addrbytes;
818
819 if (avp == NULL || avp->def == NULL)
820 return -1;
822 return -1;
823 /* Mirrors radcli_avp_get_ip6()'s RADCLI_TYPE_IPV6PREFIX case: tolerate
824 * a wire value shorter than the full address (only reserved(1) +
825 * prefix-len(1) mandatory), zero-padding the rest, rather than
826 * requiring exactly 6 bytes. */
827 if (avp->len < 2 || avp->len > 6)
828 return -1;
829 p = avp->data;
830 addrbytes = avp->len - 2;
831
832 if (out) {
833 memset(out, 0, sizeof(*out));
834 memcpy(out, p + 2, addrbytes);
835 }
836 if (prefix)
837 *prefix = p[1];
838 return 0;
839}
840
853int radcli_avp_get_bytes(const radcli_avp *a, const void **out, size_t *len)
854{
855 const struct radcli_avp_st *avp = (const struct radcli_avp_st *)a;
856
857 if (avp == NULL)
858 return -1;
859
860 if (out)
861 *out = avp->data;
862 if (len)
863 *len = avp->len;
864 return 0;
865}
866
896const char *radcli_avp_get_cstr(const radcli_avp *a)
897{
898 const struct radcli_avp_st *avp = (const struct radcli_avp_st *)a;
899
900 if (avp == NULL)
901 return NULL;
902
903 /* An embedded NUL before the real end would make any C-string
904 * function (strlen()/strcmp()/printf("%s")) stop early and silently
905 * see a shorter value than what was actually received -- e.g. a
906 * server-supplied Class value of "admin\0attacker" read back as the
907 * trusted string "admin". Reject rather than hand back a pointer
908 * that looks like a complete string but isn't. */
909 if (avp->len > 0 && memchr(avp->data, 0, avp->len) != NULL) {
910 rc_log(LOG_WARNING, "radcli_avp_get_cstr: %s contains an embedded "
911 "NUL byte, refusing to return a C string",
912 radcli_attr_def_name(avp->def));
913 return NULL;
914 }
915
916 /* RADCLI_TYPE_TEXT (RFC 8044 SS3.1) additionally requires valid UTF-8.
917 * radcli_avp_add_str() already enforces this for values added through
918 * this API, but an attribute can also be populated by
919 * radcli_avp_decode() from a received packet, whose bytes never went
920 * through add-side validation -- check again here so a malicious or
921 * buggy peer cannot smuggle invalid UTF-8 into what this function
922 * hands back as "text". RADCLI_TYPE_STRING attributes are unaffected:
923 * their opaque octets were never required to be UTF-8. */
924 if (radcli_attr_def_type(avp->def) == RADCLI_TYPE_TEXT &&
925 !is_valid_utf8(avp->data, avp->len)) {
926 rc_log(LOG_WARNING, "radcli_avp_get_cstr: %s contains invalid "
927 "UTF-8, refusing to return a C string",
928 radcli_attr_def_name(avp->def));
929 return NULL;
930 }
931
932 /* data[len] is always a valid, zeroed byte: radcli_avp_add_bytes()
933 * over-allocates by one for exactly this. */
934 return (const char *)avp->data;
935}
936
937/* radcli_avp_get_*_by_num(): the receive-side mirror of
938 * radcli_avp_add_*_by_num() -- fold radcli_dict_lookup_num() +
939 * radcli_avp_get() + the matching typed getter into one call for the
940 * common, single-occurrence case. See radcli2.h for the full rationale.
941 */
942
952const radcli_avp *radcli_avp_get_by_num(const radcli_avp_list *list, const radcli_ctx *ctx,
953 uint32_t attrid, uint32_t vendor, unsigned idx)
954{
955 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, attrid, vendor);
956
957 if (def == NULL)
958 return NULL;
959 return radcli_avp_get(list, def, idx);
960}
961
970int radcli_avp_get_uint32_by_num(const radcli_avp_list *list, const radcli_ctx *ctx,
971 uint32_t attrid, uint32_t vendor, uint32_t *out)
972{
973 const radcli_avp *a = radcli_avp_get_by_num(list, ctx, attrid, vendor, 0);
974
975 if (a == NULL)
976 return -1;
977 return radcli_avp_get_uint32(a, out);
978}
979
988int radcli_avp_get_uint64_by_num(const radcli_avp_list *list, const radcli_ctx *ctx,
989 uint32_t attrid, uint32_t vendor, uint64_t *out)
990{
991 const radcli_avp *a = radcli_avp_get_by_num(list, ctx, attrid, vendor, 0);
992
993 if (a == NULL)
994 return -1;
995 return radcli_avp_get_uint64(a, out);
996}
997
1007int radcli_avp_get_ip6_by_num(const radcli_avp_list *list, const radcli_ctx *ctx,
1008 uint32_t attrid, uint32_t vendor,
1009 struct in6_addr *out, unsigned *prefix)
1010{
1011 const radcli_avp *a = radcli_avp_get_by_num(list, ctx, attrid, vendor, 0);
1012
1013 if (a == NULL)
1014 return -1;
1015 return radcli_avp_get_ip6(a, out, prefix);
1016}
1017
1027int radcli_avp_get_ip4prefix_by_num(const radcli_avp_list *list, const radcli_ctx *ctx,
1028 uint32_t attrid, uint32_t vendor,
1029 struct in_addr *out, unsigned *prefix)
1030{
1031 const radcli_avp *a = radcli_avp_get_by_num(list, ctx, attrid, vendor, 0);
1032
1033 if (a == NULL)
1034 return -1;
1035 return radcli_avp_get_ip4prefix(a, out, prefix);
1036}
1037
1047int radcli_avp_get_bytes_by_num(const radcli_avp_list *list, const radcli_ctx *ctx,
1048 uint32_t attrid, uint32_t vendor,
1049 const void **out, size_t *len)
1050{
1051 const radcli_avp *a = radcli_avp_get_by_num(list, ctx, attrid, vendor, 0);
1052
1053 if (a == NULL)
1054 return -1;
1055 return radcli_avp_get_bytes(a, out, len);
1056}
1057
1068const char *radcli_avp_get_cstr_by_num(const radcli_avp_list *list, const radcli_ctx *ctx,
1069 uint32_t attrid, uint32_t vendor)
1070{
1071 const radcli_avp *a = radcli_avp_get_by_num(list, ctx, attrid, vendor, 0);
1072
1073 if (a == NULL)
1074 return NULL;
1075 return radcli_avp_get_cstr(a);
1076}
1077
1078/* radcli_avp_concat_str()/_by_num(): the generic form of the one convenience
1079 * legacy rc_aaa() folded into its own signature (its "msg" parameter,
1080 * lib/legacy/buildreq.c), for any attribute that can legitimately repeat,
1081 * not just Reply-Message. */
1117int radcli_avp_concat_str(char *buf, size_t buflen, const radcli_avp_list *list,
1118 const radcli_attr_def *def, const char *sep)
1119{
1120 const radcli_avp *a;
1121 unsigned idx;
1122 size_t used = 0, total = 0;
1123 size_t seplen = sep ? strlen(sep) : 0;
1124 int truncated = (buf == NULL || buflen == 0);
1125
1126 if (def == NULL)
1127 return -1;
1128
1129 if (buf != NULL && buflen > 0)
1130 buf[0] = 0;
1131
1132 for (idx = 0; (a = radcli_avp_get(list, def, idx)) != NULL; idx++) {
1133 const char *s = radcli_avp_get_cstr(a);
1134 size_t slen;
1135
1136 if (s == NULL)
1137 continue; /* embedded NUL: radcli_avp_get_cstr()'s own skip policy */
1138 slen = strlen(s);
1139
1140 if (total > 0 && seplen > 0) {
1141 total += seplen;
1142 if (!truncated) {
1143 if (used + seplen >= buflen) {
1144 truncated = 1;
1145 } else {
1146 memcpy(buf + used, sep, seplen);
1147 used += seplen;
1148 buf[used] = 0;
1149 }
1150 }
1151 }
1152 total += slen;
1153 if (!truncated) {
1154 if (used + slen >= buflen) {
1155 truncated = 1;
1156 } else {
1157 memcpy(buf + used, s, slen);
1158 used += slen;
1159 buf[used] = 0;
1160 }
1161 }
1162 }
1163 return (int)total;
1164}
1165
1189int radcli_avp_concat_str_by_num(char *buf, size_t buflen, const radcli_avp_list *list,
1190 const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor,
1191 const char *sep)
1192{
1193 const radcli_attr_def *def = radcli_dict_lookup_num(ctx, attrid, vendor);
1194
1195 if (def == NULL) {
1196 if (buf != NULL && buflen > 0)
1197 buf[0] = 0;
1198 return 0;
1199 }
1200 return radcli_avp_concat_str(buf, buflen, list, def, sep);
1201}
1202
1230int radcli_avp_list_error(const radcli_avp_list *list)
1231{
1232
1233 if (list == NULL)
1234 return 1;
1235 return list->error ? 1 : 0;
1236}
1237
1271int radcli_avp_add_gigawords64(radcli_ctx *ctx, radcli_avp_list *list,
1272 const radcli_attr_def *octets, uint64_t value)
1273{
1274 rc_handle *rh = (rc_handle *)ctx;
1275 const radcli_attr_def *gigawords;
1276
1277 if (rh == NULL || octets == NULL || radcli_attr_def_type(octets) != RADCLI_TYPE_INTEGER)
1278 return avp_list_fail(list);
1279
1280 gigawords = (const radcli_attr_def *)radcli_dict_attr_gigawords(rh, (const struct radcli_dict_attr *)octets);
1281 if (gigawords == NULL) {
1282 rc_log(LOG_ERR, "radcli_avp_add_gigawords64: %s has no gigawords= "
1283 "counterpart configured", radcli_attr_def_name(octets));
1284 return avp_list_fail(list);
1285 }
1286
1287 if (radcli_avp_add_uint32(list, octets, (uint32_t)value) != 0)
1288 return -1;
1289 if (value > UINT32_MAX) {
1290 /* Omitted when it would be zero, matching how a real NAS sends
1291 * it -- the previous call already added the octets attribute
1292 * either way, so a receiver with no Gigawords support still
1293 * gets the low 32 bits it always got. */
1294 if (radcli_avp_add_uint32(list, gigawords, (uint32_t)(value >> 32)) != 0)
1295 return -1;
1296 }
1297 return 0;
1298}
1299
1312int radcli_avp_get_gigawords64(const radcli_ctx *ctx, const radcli_avp_list *list,
1313 const radcli_attr_def *octets, uint64_t *out)
1314{
1315 const rc_handle *rh = (const rc_handle *)ctx;
1316 const radcli_attr_def *gigawords;
1317 const radcli_avp *a;
1318 uint32_t lo, hi = 0;
1319
1320 if (rh == NULL || octets == NULL)
1321 return -1;
1322
1323 gigawords = (const radcli_attr_def *)radcli_dict_attr_gigawords(rh, (const struct radcli_dict_attr *)octets);
1324 if (gigawords == NULL)
1325 return -1;
1326
1327 a = radcli_avp_get(list, octets, 0);
1328 if (a == NULL || radcli_avp_get_uint32(a, &lo) != 0)
1329 return -1;
1330
1331 a = radcli_avp_get(list, gigawords, 0);
1332 if (a != NULL && radcli_avp_get_uint32(a, &hi) != 0)
1333 return -1; /* present but not a 32-bit integer: malformed, not "absent" */
1334
1335 if (out)
1336 *out = ((uint64_t)hi << 32) | lo;
1337 return 0;
1338}
1339
1355int radcli_avp_add_gigawords64_by_num(radcli_ctx *ctx, radcli_avp_list *list,
1356 uint32_t attrid, uint32_t vendor, uint64_t value)
1357{
1358 const radcli_attr_def *octets = radcli_dict_lookup_num(ctx, attrid, vendor);
1359
1360 if (octets == NULL)
1361 return avp_list_fail(list);
1362 return radcli_avp_add_gigawords64(ctx, list, octets, value);
1363}
1364
1373int radcli_avp_get_gigawords64_by_num(const radcli_ctx *ctx, const radcli_avp_list *list,
1374 uint32_t attrid, uint32_t vendor, uint64_t *out)
1375{
1376 const radcli_attr_def *octets = radcli_dict_lookup_num(ctx, attrid, vendor);
1377
1378 if (octets == NULL)
1379 return -1;
1380 return radcli_avp_get_gigawords64(ctx, list, octets, out);
1381}
1382
1384
1385/* --- radcli_avp_decode()/radcli_avp_encode(): wire codec (internal
1386 * only) --
1387 *
1388 * Not part of the public radcli2 API -- declared in lib/avp.h, not in
1389 * radcli2.h or radcli.map. Mirrors lib/avpair.c's rc_avpair_gen2()/
1390 * lib/sendserver.c's rc_pack_list() framing rules exactly (RFC 2865 TLV
1391 * attributes, RFC 2865 SS5.26 VSA envelope with a 4-octet Vendor-Id).
1392 *
1393 * The decode side needs almost no per-type switch: since every radcli_avp
1394 * stores its value as length-carrying bytes, decoding most attributes is
1395 * "look it up in the dictionary, copy its value in" regardless of type --
1396 * the interpretation is deferred to the typed getters. The one exception is
1397 * the three 4-octet numeric types (INTEGER/IPADDR/DATE), which the wire
1398 * carries in network byte order but radcli_avp_add_uint32()/
1399 * radcli_avp_get_uint32() store/read in host byte order (matching legacy
1400 * VALUE_PAIR->lvalue's convention); decode converts with ntohl() so both
1401 * construction paths agree on what is in memory. A wrong-length instance of
1402 * one of these (e.g. a 3-octet INTEGER) is skipped outright, logged and
1403 * never stored -- the same strict-length policy as RADCLI_TYPE_INTEGER64/
1404 * _IFID below, rather than storing it un-byte-swapped for
1405 * radcli_avp_get_uint32() to reject later: ntohl() would read past a
1406 * too-short value, and there is no use in keeping a too-long one around
1407 * either, since these are fixed-width types with only one valid length.
1408 *
1409 * The other exception is an attribute the dictionary marks
1410 * "encrypt=Tunnel-Password" (Tunnel-Password, MS-MPPE-Send-Key,
1411 * MS-MPPE-Recv-Key -- see
1412 * etc/dictionary and lib/dict2.h's radcli_dict_flags_by_id()): decode
1413 * transparently reverses the RFC 2868 SS3.5 / RFC 2548 salt-encryption
1414 * scheme using the caller-supplied secret and request authenticator, and
1415 * radcli_avp_get_bytes() then returns the plaintext. Only decryption is
1416 * implemented; radcli_avp_encode() still refuses to originate any
1417 * encrypt=Tunnel-Password-flagged attribute (Tunnel-Password,
1418 * MS-MPPE-Send-Key, MS-MPPE-Recv-Key) -- a RADIUS client has not needed to
1419 * send one. radcli_avp_encode() dispatches on
1420 * radcli_dict_flags_by_id() too, the same lookup this decode path uses:
1421 * this is a whitelist, not a blocklist -- an attribute is encoded
1422 * unencrypted only because the dictionary says it needs no encryption,
1423 * never because radcli_avp_encode() simply did not recognise that
1424 * it does. Note that this whitelist only guards against attributes this
1425 * function *refuses*; it does not by itself guarantee the dictionary is
1426 * *correct* -- a dictionary missing "encrypt=User-Password" on
1427 * User-Password would send it as plaintext, no differently than any other
1428 * unflagged attribute, and this function has no way to tell that apart
1429 * from a legitimately unflagged one. The n_encrypted out-parameter below
1430 * exists for exactly that: a caller who knows how many attributes in
1431 * their own list *should* be RFC 2865 SS5.2-encrypted can compare against
1432 * it and refuse to send on a mismatch, catching a misloaded or
1433 * mis-edited dictionary that this function's own whitelist logic cannot
1434 * detect from the inside.
1435 */
1436
1437/* RFC 2868 SS3.5 / RFC 2548 SS2.4.2-2.4.3 "salt-encryption" keystream:
1438 * b(1) = MD5(secret || request_authenticator || salt)
1439 * b(i) = MD5(secret || c(i-1)) for i > 1
1440 * p(i) = c(i) XOR b(i)
1441 * ciphertext/plaintext are len bytes, a non-zero multiple of 16; plaintext
1442 * must have room for len bytes. Used only to decrypt (encrypting these
1443 * attributes -- e.g. to originate a CoA/Access-Accept -- is not implemented;
1444 * radcli is a client and has not needed to originate them so far). */
1445/*- Decrypt an RFC 2868 §3.5 / RFC 2548 §2.4.2-2.4.3 salt-encrypted value.
1446 *
1447 * @param plaintext set to the decrypted value; must have room for len bytes.
1448 * @param ciphertext the encrypted value, len bytes (a non-zero multiple of 16).
1449 * @param len ciphertext/plaintext's length in bytes.
1450 * @param secret the shared secret.
1451 * @param request_authenticator the packet's request authenticator.
1452 * @param salt the attribute's 2-byte salt.
1453 -*/
1454static void salt_decrypt(unsigned char *plaintext, const unsigned char *ciphertext, size_t len,
1455 const char *secret, const unsigned char request_authenticator[AUTH_VECTOR_LEN],
1456 const unsigned char salt[2])
1457{
1458 unsigned char keybuf[MAX_SECRET_LENGTH + AUTH_VECTOR_LEN + 2];
1459 unsigned char b[16];
1460 size_t secretlen = rc_secret_len(secret);
1461 size_t i;
1462
1463 memcpy(keybuf, secret, secretlen);
1464 memcpy(keybuf + secretlen, request_authenticator, AUTH_VECTOR_LEN);
1465 memcpy(keybuf + secretlen + AUTH_VECTOR_LEN, salt, 2);
1466 rc_md5_calc(b, keybuf, secretlen + AUTH_VECTOR_LEN + 2);
1467
1468 for (i = 0; i < len; i += 16) {
1469 size_t j;
1470
1471 if (i > 0) {
1472 memcpy(keybuf, secret, secretlen);
1473 memcpy(keybuf + secretlen, ciphertext + i - 16, 16);
1474 rc_md5_calc(b, keybuf, secretlen + 16);
1475 }
1476 for (j = 0; j < 16; j++)
1477 plaintext[i + j] = ciphertext[i + j] ^ b[j];
1478 }
1479}
1480
1481/* RFC 2865 SS5.2 User-Password encryption. Same construction as
1482 * salt_decrypt() with no salt component, but deliberately NOT implemented
1483 * by generalizing that function to share code: the two run in opposite
1484 * directions, and b(i) for i>1 must chain from the *ciphertext* of block
1485 * i-1 either way -- which for encryption is `ciphertext[i-16..i)`, already
1486 * written by the previous loop iteration, not `plaintext[i-16..i)`. A
1487 * shared "in/out" primitive would have to read from `out` here and from
1488 * `in` in the decrypt case, an easy place to introduce a directional bug
1489 * silently; two small, direction-specific functions are safer than one
1490 * clever one. len must be a non-zero multiple of 16; ciphertext must have
1491 * room for len bytes and must not alias plaintext. */
1492/*- Encrypt a value per RFC 2865 §5.2 User-Password encryption.
1493 *
1494 * @param ciphertext set to the encrypted value; must have room for len
1495 * bytes and must not alias plaintext.
1496 * @param plaintext the value to encrypt, len bytes (a non-zero multiple of 16).
1497 * @param len ciphertext/plaintext's length in bytes.
1498 * @param secret the shared secret.
1499 * @param request_authenticator the packet's request authenticator.
1500 -*/
1501static void user_password_encrypt(unsigned char *ciphertext, const unsigned char *plaintext, size_t len,
1502 const char *secret,
1503 const unsigned char request_authenticator[AUTH_VECTOR_LEN])
1504{
1505 unsigned char keybuf[MAX_SECRET_LENGTH + AUTH_VECTOR_LEN];
1506 unsigned char b[16];
1507 size_t secretlen = rc_secret_len(secret);
1508 size_t i;
1509
1510 memcpy(keybuf, secret, secretlen);
1511 memcpy(keybuf + secretlen, request_authenticator, AUTH_VECTOR_LEN);
1512 rc_md5_calc(b, keybuf, secretlen + AUTH_VECTOR_LEN);
1513
1514 for (i = 0; i < len; i += 16) {
1515 size_t j;
1516
1517 if (i > 0) {
1518 memcpy(keybuf, secret, secretlen);
1519 memcpy(keybuf + secretlen, ciphertext + i - 16, 16); /* previously-written block */
1520 rc_md5_calc(b, keybuf, secretlen + 16);
1521 }
1522 for (j = 0; j < 16; j++)
1523 ciphertext[i + j] = plaintext[i + j] ^ b[j];
1524 }
1525}
1526
1527/*- Decode a run of RADIUS attribute TLVs from ptr_in into list, recursing
1528 * into VSA sub-attributes.
1529 *
1530 * @param rh a handle to parsed configuration.
1531 * @param secret the shared secret, needed to decrypt an encrypt= attribute.
1532 * @param request_authenticator the packet's request authenticator.
1533 * @param list destination list; attributes are appended to it.
1534 * @param ptr_in the attribute region to decode.
1535 * @param length ptr_in's length in bytes.
1536 * @param vendorspec 0 when decoding the packet's top-level attributes,
1537 * or the enclosing vendor's PEN when recursing into a VSA's sub-attributes.
1538 * @return 0 on success, -1 on a malformed attribute.
1539 -*/
1540static int avp_decode_into(rc_handle const *rh, const char *secret,
1541 const uint8_t request_authenticator[AUTH_VECTOR_LEN],
1542 struct radcli_avp_list_st *list,
1543 const uint8_t *ptr_in, size_t length, uint32_t vendorspec)
1544{
1545 pkt_buf pb;
1546 const uint8_t *attr_data, *ptr;
1547 int attrlen;
1548 uint32_t lvalue;
1549 const radcli_attr_def *def;
1550
1551 pb_init_read(&pb, (void *)(uintptr_t)ptr_in, length, length);
1552
1553 while (pb_len(&pb) > 0) {
1554 if (pb_len(&pb) < 2) {
1555 rc_log(LOG_ERR, "radcli_avp_decode: received attribute with invalid length");
1556 return -1;
1557 }
1558 attrlen = pb.data[1];
1559 if (attrlen < 2 || (size_t)attrlen > pb_len(&pb)) {
1560 rc_log(LOG_ERR, "radcli_avp_decode: received attribute with invalid length");
1561 return -1;
1562 }
1563
1564 attr_data = pb.data;
1565 if (pb_pull(&pb, attrlen) != 0) {
1566 rc_log(LOG_ERR, "radcli_avp_decode: internal pb_pull failure");
1567 return -1;
1568 }
1569
1570 ptr = attr_data + 2;
1571 attrlen -= 2;
1572
1573 if (vendorspec == 0 && attr_data[0] == PW_VENDOR_SPECIFIC) {
1574 if (attrlen < 4) {
1575 rc_log(LOG_WARNING, "radcli_avp_decode: received VSA attribute with invalid length");
1576 continue;
1577 }
1578 memcpy(&lvalue, ptr, 4);
1579 lvalue = ntohl(lvalue);
1580 if (radcli_dict_vendor_by_pec(rh, lvalue) == NULL) {
1581 rc_log(LOG_WARNING, "radcli_avp_decode: received VSA attribute "
1582 "with unknown Vendor-Id %u", lvalue);
1583 continue;
1584 }
1585 if (avp_decode_into(rh, secret, request_authenticator, list,
1586 ptr + 4, (size_t)(attrlen - 4), lvalue) < 0)
1587 return -1;
1588 continue;
1589 }
1590
1591 def = radcli_dict_lookup_num(rh, attr_data[0], vendorspec);
1592 if (def == NULL) {
1593 if (vendorspec == 0)
1594 rc_log(LOG_WARNING, "radcli_avp_decode: received unknown "
1595 "attribute %u of length %d", (unsigned)attr_data[0], attrlen + 2);
1596 else
1597 rc_log(LOG_WARNING, "radcli_avp_decode: received unknown VSA "
1598 "attribute %u, vendor %u of length %d",
1599 (unsigned)attr_data[0], vendorspec, attrlen + 2);
1600 continue;
1601 }
1602
1603 {
1604 struct radcli_dict_flags *fl = radcli_dict_flags_by_id(rh, ((const struct radcli_dict_attr *)def)->value);
1605
1606 if (fl != NULL && fl->encrypt_type == 2) {
1607 /* RFC 2868 SS3.5 / RFC 2548 SS2.4.2-2.4.3 salt-encryption.
1608 * Whether a one-octet Tag precedes the Salt is dictionary data
1609 * (the "has_tag" ATTRIBUTE option -- RFC 2868 SS3.1), not an
1610 * identity check on Tunnel-Password: Tunnel-Password carries
1611 * both encrypt=Tunnel-Password and has_tag; the MS-MPPE-*-Key
1612 * VSAs carry encrypt=Tunnel-Password alone. Any framing problem
1613 * here is treated the same
1614 * as an unrecognised attribute -- logged and skipped, not a
1615 * hard decode error, since it is a property of this one
1616 * attribute, not of the packet. */
1617 size_t off = fl->has_tag ? 1 : 0;
1618
1619 if (secret == NULL || request_authenticator == NULL) {
1620 rc_log(LOG_WARNING, "radcli_avp_decode: %s is salt-encrypted "
1621 "but no secret/request authenticator was supplied; skipping",
1623 } else if ((size_t)attrlen < off + 2 + 16 ||
1624 ((size_t)attrlen - off - 2) % 16 != 0) {
1625 rc_log(LOG_WARNING, "radcli_avp_decode: %s has an invalid "
1626 "salt-encrypted length", radcli_attr_def_name(def));
1627 } else {
1628 size_t ctlen = (size_t)attrlen - off - 2;
1629 unsigned char plain[AUTH_STRING_LEN];
1630 unsigned char salt[2];
1631 unsigned char lenoct;
1632
1633 memcpy(salt, ptr + off, 2);
1634 salt_decrypt(plain, ptr + off + 2, ctlen, secret,
1635 request_authenticator, salt);
1636 lenoct = plain[0];
1637 if (lenoct > ctlen - 1) {
1638 rc_log(LOG_WARNING, "radcli_avp_decode: %s decrypted to "
1639 "an out-of-range length prefix", radcli_attr_def_name(def));
1640 } else if (radcli_avp_add_bytes((radcli_avp_list *)list, def,
1641 plain + 1, lenoct) != 0) {
1642 return -1; /* allocation failure; already logged */
1643 }
1644 }
1645 continue;
1646 }
1647 }
1648
1649 {
1651
1652 if (t == RADCLI_TYPE_INTEGER64 || t == RADCLI_TYPE_IFID) {
1653 /* RFC 8044 SS3.3/SS3.7: integer64 and ifid are both 8
1654 * octets, network byte order (high 32 bits first), twice
1655 * the width of the four-octet numeric types below -- ifid
1656 * shares this branch because its wire shape is
1657 * identical, even though it is not itself a numeric
1658 * quantity. Same strict length check as that branch: skip
1659 * a malformed instance here, like an unrecognised
1660 * attribute, rather than storing it for a getter to
1661 * reject later. */
1662 uint32_t hi, lo;
1663 uint64_t hostval;
1664
1665 if (attrlen != (int)sizeof(uint64_t)) {
1666 rc_log(LOG_WARNING, "radcli_avp_decode: %s has an invalid "
1667 "integer64/ifid length (%d, expected 8)",
1668 radcli_attr_def_name(def), attrlen);
1669 continue;
1670 }
1671 memcpy(&hi, ptr, sizeof(hi));
1672 memcpy(&lo, ptr + sizeof(hi), sizeof(lo));
1673 hostval = ((uint64_t)ntohl(hi) << 32) | ntohl(lo);
1674 if (radcli_avp_add_bytes((radcli_avp_list *)list, def,
1675 &hostval, sizeof(hostval)) != 0)
1676 return -1; /* allocation failure; already logged */
1677 } else if (t == RADCLI_TYPE_INTEGER || t == RADCLI_TYPE_IPADDR || t == RADCLI_TYPE_DATE) {
1678 /* Same strict-length policy as RADCLI_TYPE_INTEGER64/_IFID
1679 * above: skip a malformed instance rather than storing it
1680 * un-byte-swapped for radcli_avp_get_uint32() to reject
1681 * later. */
1682 uint32_t netval, hostval;
1683
1684 if (attrlen != (int)sizeof(uint32_t)) {
1685 rc_log(LOG_WARNING, "radcli_avp_decode: %s has an invalid "
1686 "integer/ipaddr/date length (%d, expected 4)",
1687 radcli_attr_def_name(def), attrlen);
1688 continue;
1689 }
1690 memcpy(&netval, ptr, sizeof(netval));
1691 hostval = ntohl(netval);
1692 if (radcli_avp_add_bytes((radcli_avp_list *)list, def,
1693 &hostval, sizeof(hostval)) != 0)
1694 return -1; /* allocation failure; already logged */
1695 } else {
1696 if (radcli_avp_add_bytes((radcli_avp_list *)list, def, ptr, (size_t)attrlen) != 0)
1697 return -1; /* allocation failure; already logged */
1698 }
1699 }
1700 }
1701 return 0;
1702}
1703
1704/* Parses the attribute-value region [ptr, ptr+length) of a received RADIUS
1705 * packet into a newly allocated radcli_avp_list; vendorspec is 0 for a
1706 * top-level packet region, or the enclosing vendor's PEN when decoding a
1707 * VSA's sub-attributes. secret/request_authenticator are used only to
1708 * decrypt an attribute the dictionary marks "encrypt=Tunnel-Password"
1709 * (Tunnel-Password, MS-MPPE-Send-Key, MS-MPPE-Recv-Key -- RFC 2868 SS3.5 /
1710 * RFC 2548); pass
1711 * secret == NULL if none of those can occur. */
1712/*- Decode a received RADIUS packet's attribute region into a newly
1713 * allocated radcli_avp_list.
1714 *
1715 * @param rh a handle to parsed configuration.
1716 * @param secret the shared secret, needed to decrypt an encrypt= attribute;
1717 * NULL if none can occur.
1718 * @param request_authenticator the packet's request authenticator.
1719 * @param ptr the attribute region to decode.
1720 * @param length ptr's length in bytes.
1721 * @param vendorspec 0 for a top-level packet region.
1722 * @param out set to the newly allocated list on success (possibly empty,
1723 * if every attribute present was unrecognised/undecryptable and skipped).
1724 * @return 0 on success, -1 on a hard framing error (out left unset) or if
1725 * rh/out is NULL.
1726 -*/
1727int radcli_avp_decode(rc_handle const *rh, const char *secret,
1728 const uint8_t request_authenticator[AUTH_VECTOR_LEN],
1729 const uint8_t *ptr, size_t length,
1730 uint32_t vendorspec, radcli_avp_list **out)
1731{
1732 struct radcli_avp_list_st *list;
1733
1734 if (rh == NULL || out == NULL)
1735 return -1;
1736
1737 list = (struct radcli_avp_list_st *)radcli_avp_list_new();
1738 if (list == NULL)
1739 return -1;
1740
1741 if (avp_decode_into(rh, secret, request_authenticator, list, ptr, length, vendorspec) != 0) {
1742 radcli_avp_list_free((radcli_avp_list *)list);
1743 return -1;
1744 }
1745
1746 *out = (radcli_avp_list *)list;
1747 return 0;
1748}
1749
1750/* Writes list's wire encoding into buf (capacity buflen) -- attribute bytes
1751 * only, no packet header. rh's dictionary decides which attributes need
1752 * special handling, via radcli_dict_flags_by_id(): an unflagged attribute
1753 * is encoded as-is; an "encrypt=User-Password" attribute (RFC 2865 SS5.2 --
1754 * the one obfuscation scheme this function implements, hence the _rfc2865
1755 * suffix) is encrypted using secret/request_authenticator (pass secret ==
1756 * NULL if the list carries none of those -- encoding then fails if it
1757 * does, rather than sending it unencrypted); any other flagged value,
1758 * including "encrypt=Tunnel-Password" (Tunnel-Password, MS-MPPE-Send-Key,
1759 * MS-MPPE-Recv-Key -- RFC 2868 SS3.5 / RFC 2548 salt-encryption, which
1760 * radcli_avp_decode() reverses but this function does not originate), is
1761 * refused outright. This is a whitelist: an attribute is only ever encoded
1762 * unencrypted because the dictionary says it needs no encryption, never
1763 * because this function failed to recognise that it does -- but the
1764 * whitelist can only catch attributes it knows to refuse, not a dictionary
1765 * that is simply missing "encrypt=User-Password" on an attribute that
1766 * should have it (see the wire-codec comment above for why). If
1767 * n_encrypted is non-NULL, it is set to the number of attributes this call
1768 * routed through the RFC 2865 SS5.2 path -- a caller who knows how many
1769 * User-Password-like attributes their own list should contain can compare
1770 * against it and refuse to send on a mismatch, rather than trusting the
1771 * dictionary blindly. Returns the number of bytes written, or -1 on
1772 * overflow, on User-Password without a secret supplied or longer than
1773 * AUTH_PASS_LEN (128) bytes, or on any other encrypt-flagged attribute. */
1774/*- Encode l's wire representation (attribute bytes only, no packet header)
1775 * into buf.
1776 *
1777 * @param rh a handle to parsed configuration.
1778 * @param l the list to encode.
1779 * @param secret the shared secret, needed to encrypt an encrypt=
1780 * User-Password attribute; NULL if the list carries none.
1781 * @param request_authenticator the packet's request authenticator.
1782 * @param buf destination buffer for the encoded attributes.
1783 * @param buflen buf's capacity in bytes.
1784 * @param n_encrypted if non-NULL, set to the number of attributes encoded
1785 * via the RFC 2865 §5.2 User-Password path.
1786 * @return the number of bytes written, or -1 on failure (see the comment
1787 * above for the specific failure cases).
1788 -*/
1789int radcli_avp_encode(rc_handle const *rh, const radcli_avp_list *l, const char *secret,
1790 const uint8_t request_authenticator[AUTH_VECTOR_LEN],
1791 uint8_t *buf, size_t buflen, size_t *n_encrypted)
1792{
1793 const struct radcli_avp_list_st *list = (const struct radcli_avp_list_st *)l;
1794 const struct radcli_avp_st *a = NULL;
1795 pkt_buf pb;
1796 uint8_t *attr_start, *attr_len_ptr, *vsa_len_ptr;
1797 uint32_t vendor, attrid, netval;
1799
1800 if (rh == NULL || list == NULL)
1801 return -1;
1802
1803 if (n_encrypted != NULL)
1804 *n_encrypted = 0;
1805
1806 pb_init(&pb, buf, buflen);
1807
1808 list_for_each(&list->head, a, node) {
1809 const struct radcli_dict_attr *def = (const struct radcli_dict_attr *)a->def;
1810 struct radcli_dict_flags *fl = radcli_dict_flags_by_id(rh, def->value);
1811
1812 vendor = VENDOR(def->value);
1813 attrid = ATTRID(def->value);
1814
1815 /* Whitelist, not a blocklist: only an attribute the dictionary does
1816 * NOT flag for encryption, or flags encrypt=User-Password
1817 * specifically (which this function implements), is safe to send.
1818 * Anything else -- encrypt=Tunnel-Password (Tunnel-Password,
1819 * MS-MPPE-Send-Key, MS-MPPE-Recv-Key today; RFC 2868 SS3.5
1820 * salt-encryption, which this function does not originate), or any
1821 * future encrypt=N this function has no code for -- is refused.
1822 * Driving this off the flags_by_attr_id side table rather than an
1823 * enumerated attribute list means a dictionary addition can never
1824 * silently start sending something in the clear that was supposed
1825 * to be encrypted. */
1826 switch (fl ? fl->encrypt_type : 0) {
1827 case 0:
1828 break;
1829 case 1: {
1830 unsigned char passbuf[AUTH_PASS_LEN];
1831 unsigned char cipher[AUTH_PASS_LEN];
1832 size_t padded_len;
1833
1834 if (secret == NULL || request_authenticator == NULL) {
1835 rc_log(LOG_ERR, "radcli_avp_encode: %s requires the shared "
1836 "secret and request authenticator, which were not supplied",
1837 def->name);
1838 return -1;
1839 }
1840 if (a->len > AUTH_PASS_LEN) {
1841 /* Legacy rc_pack_list() silently truncates an over-length
1842 * password to AUTH_PASS_LEN; that is a footgun (the server
1843 * authenticates a different, shorter password than the
1844 * caller believes it sent), not behaviour worth repeating
1845 * here. Reject instead. */
1846 rc_log(LOG_ERR, "radcli_avp_encode: %s is %zu bytes, longer "
1847 "than the %d-byte RFC 2865 SS5.2 maximum",
1848 def->name, a->len, AUTH_PASS_LEN);
1849 return -1;
1850 }
1851
1852 padded_len = ((a->len + (AUTH_VECTOR_LEN - 1)) / AUTH_VECTOR_LEN) * AUTH_VECTOR_LEN;
1853 if (padded_len == 0)
1854 padded_len = AUTH_VECTOR_LEN; /* RFC 2865 SS5.2: pad to a
1855 * multiple of 16; an empty
1856 * password still sends one
1857 * (all-zero) block. */
1858
1859 memset(passbuf, 0, sizeof(passbuf));
1860 if (a->len > 0)
1861 memcpy(passbuf, a->data, a->len);
1862 user_password_encrypt(cipher, passbuf, padded_len, secret, request_authenticator);
1863
1864 attr_start = pb.tail;
1865 if (pb_put_byte(&pb, (uint8_t)(attrid & 0xff)) < 0) goto too_large;
1866 attr_len_ptr = pb.tail;
1867 if (pb_put_byte(&pb, 2) < 0) goto too_large; /* placeholder; patched below */
1868 if (pb_put_bytes(&pb, cipher, (int)padded_len) < 0) goto too_large;
1869 *attr_len_ptr = (uint8_t)(pb.tail - attr_start);
1870 if (n_encrypted != NULL)
1871 (*n_encrypted)++;
1872 continue;
1873 }
1874 default:
1875 rc_log(LOG_ERR, "radcli_avp_encode: %s requires per-request "
1876 "encryption, which this function does not perform", def->name);
1877 return -1;
1878 }
1879
1880 if (vendor == 0 && attrid > 0xff) {
1881 /* An RFC 6929 extended attribute number: not encodable in the
1882 * classic RFC 2865 TLV this function writes. The bundled
1883 * dictionary carries none today (Phase 1 scope note), so this
1884 * is unreachable in practice; kept as a defensive guard rather
1885 * than an assumption. */
1886 rc_log(LOG_ERR, "radcli_avp_encode: %s has an attribute number "
1887 "outside the classic RADIUS TLV range", def->name);
1888 return -1;
1889 }
1890
1891 vsa_len_ptr = NULL;
1892 if (vendor != 0) {
1893 if (pb_put_byte(&pb, PW_VENDOR_SPECIFIC) < 0) goto too_large;
1894 vsa_len_ptr = pb.tail;
1895 if (pb_put_byte(&pb, 6) < 0) goto too_large;
1896 netval = htonl(vendor);
1897 if (pb_put_bytes(&pb, &netval, sizeof(netval)) < 0) goto too_large;
1898 }
1899
1900 attr_start = pb.tail;
1901 if (pb_put_byte(&pb, (uint8_t)(attrid & 0xff)) < 0) goto too_large;
1902 attr_len_ptr = pb.tail;
1903 if (pb_put_byte(&pb, 2) < 0) goto too_large; /* placeholder; patched below */
1904
1905 t = radcli_attr_def_type(a->def);
1906 if (t == RADCLI_TYPE_INTEGER64 || t == RADCLI_TYPE_IFID) {
1907 /* RFC 8044 SS3.3/SS3.7: 8 octets, network byte order, high 32
1908 * bits first -- mirrors the decode side above. */
1909 uint64_t hostval;
1910 uint32_t hi, lo;
1911
1912 if (a->len != sizeof(uint64_t)) {
1913 rc_log(LOG_ERR, "radcli_avp_encode: %s has the wrong stored "
1914 "length for its type", def->name);
1915 return -1;
1916 }
1917 memcpy(&hostval, a->data, sizeof(hostval));
1918 hi = htonl((uint32_t)(hostval >> 32));
1919 lo = htonl((uint32_t)hostval);
1920 if (pb_put_bytes(&pb, &hi, sizeof(hi)) < 0) goto too_large;
1921 if (pb_put_bytes(&pb, &lo, sizeof(lo)) < 0) goto too_large;
1922 } else if (t == RADCLI_TYPE_INTEGER || t == RADCLI_TYPE_IPADDR || t == RADCLI_TYPE_DATE) {
1923 uint32_t hostval;
1924
1925 if (a->len != sizeof(uint32_t)) {
1926 rc_log(LOG_ERR, "radcli_avp_encode: %s has the wrong stored "
1927 "length for its type", def->name);
1928 return -1;
1929 }
1930 memcpy(&hostval, a->data, sizeof(hostval));
1931 netval = htonl(hostval);
1932 if (pb_put_bytes(&pb, &netval, sizeof(netval)) < 0) goto too_large;
1933 } else {
1934 if (a->len > AUTH_STRING_LEN - (vendor != 0 ? VSA_HDR_LEN : 0)) {
1935 rc_log(LOG_ERR, "radcli_avp_encode: %s value too long (%zu bytes)",
1936 def->name, a->len);
1937 return -1;
1938 }
1939 if (pb_put_bytes(&pb, a->data, (int)a->len) < 0) goto too_large;
1940 }
1941
1942 *attr_len_ptr = (uint8_t)(pb.tail - attr_start);
1943 if (vsa_len_ptr != NULL)
1944 *vsa_len_ptr += *attr_len_ptr;
1945 }
1946
1947 return (int)pb_written(&pb);
1948
1949too_large:
1950 rc_log(LOG_ERR, "radcli_avp_encode: attribute value too large or buffer "
1951 "would exceed %zu bytes", buflen);
1952 return -1;
1953}
1954
int radcli_avp_add_ip4prefix(radcli_avp_list *list, const radcli_attr_def *def, struct in_addr value, unsigned prefix)
Append an IPv4-prefix-typed attribute.
Definition avp.c:429
int radcli_avp_get_uint64(const radcli_avp *a, uint64_t *out)
Read an attribute's value as a 64-bit integer or ifid.
Definition avp.c:742
int radcli_avp_add_username(radcli_avp_list *list, const radcli_ctx *ctx, const char *username, const char *realm)
Append a User-Name AVP, appending a realm as "username@realm" unless username already contains one.
Definition avp.c:610
int radcli_avp_concat_str(char *buf, size_t buflen, const radcli_avp_list *list, const radcli_attr_def *def, const char *sep)
Concatenate every occurrence of an attribute into a bounded buffer.
Definition avp.c:1117
int radcli_avp_get_uint64_by_num(const radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, uint64_t *out)
Look up a 64-bit integer-typed attribute by legacy numeric ID and read its first occurrence.
Definition avp.c:988
int radcli_avp_get_gigawords64_by_num(const radcli_ctx *ctx, const radcli_avp_list *list, uint32_t attrid, uint32_t vendor, uint64_t *out)
Look up the octets attribute by legacy numeric ID and reassemble a 64-bit counter from an Octets/Giga...
Definition avp.c:1373
const radcli_avp * radcli_avp_get_by_num(const radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, unsigned idx)
Look up the idx-th occurrence of an attribute by legacy numeric ID.
Definition avp.c:952
int radcli_avp_add_ip6_by_num(radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, const struct in6_addr *value, unsigned prefix)
Look up an IPv6-address or IPv6-prefix-typed attribute by legacy numeric ID and append it.
Definition avp.c:558
int radcli_avp_list_error(const radcli_avp_list *list)
Check whether any radcli_avp_add_*()/_by_num() call on list has ever failed.
Definition avp.c:1230
int radcli_avp_get_ip4prefix_by_num(const radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, struct in_addr *out, unsigned *prefix)
Look up an IPv4-prefix-typed attribute by legacy numeric ID and read its first occurrence.
Definition avp.c:1027
void radcli_avp_list_free(radcli_avp_list *list)
Free a list and every attribute it holds.
Definition avp.c:159
int radcli_avp_add_uint32_by_num(radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, uint32_t value)
Look up an integer/IPv4-address/date-typed attribute by legacy numeric ID and append it.
Definition avp.c:503
int radcli_avp_add_uint64_by_num(radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, uint64_t value)
Look up a 64-bit integer-typed attribute by legacy numeric ID and append it.
Definition avp.c:521
int radcli_avp_concat_str_by_num(char *buf, size_t buflen, const radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, const char *sep)
Look up an attribute by legacy numeric ID and concatenate every occurrence into a bounded buffer.
Definition avp.c:1189
radcli_avp_iter radcli_avp_list_iter(const radcli_avp_list *list)
Begin iterating list.
Definition avp.c:666
int radcli_avp_add_bytes_by_num(radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, const void *value, size_t len)
Look up an attribute by legacy numeric ID and append its bytes.
Definition avp.c:466
int radcli_avp_add_str_by_num(radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, const char *value)
Look up a string-typed attribute by legacy numeric ID and append it.
Definition avp.c:485
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_get_uint32_by_num(const radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, uint32_t *out)
Look up an integer/IPv4-address/date-typed attribute by legacy numeric ID and read its first occurren...
Definition avp.c:970
int radcli_avp_add_ip4prefix_by_num(radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, struct in_addr value, unsigned prefix)
Look up an IPv4-prefix-typed attribute by legacy numeric ID and append it.
Definition avp.c:578
int radcli_avp_add_ip6(radcli_avp_list *list, const radcli_attr_def *def, const struct in6_addr *value, unsigned prefix)
Append an IPv6-address or IPv6-prefix-typed attribute.
Definition avp.c:386
int radcli_avp_get_gigawords64(const radcli_ctx *ctx, const radcli_avp_list *list, const radcli_attr_def *octets, uint64_t *out)
Reassemble a 64-bit counter from an Octets/Gigawords attribute pair.
Definition avp.c:1312
int radcli_avp_add_ip4(radcli_avp_list *list, const radcli_attr_def *def, struct in_addr value)
Append an IPv4-address-typed attribute from a struct in_addr.
Definition avp.c:363
int radcli_avp_get_ip4prefix(const radcli_avp *a, struct in_addr *out, unsigned *prefix)
Read an attribute's value as an IPv4 prefix.
Definition avp.c:813
const char * radcli_avp_get_cstr_by_num(const radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor)
Look up an attribute by legacy numeric ID and read its first occurrence as a NUL-terminated string.
Definition avp.c:1068
int radcli_avp_add_gigawords64(radcli_ctx *ctx, radcli_avp_list *list, const radcli_attr_def *octets, uint64_t value)
Append a 64-bit counter as an Octets/Gigawords attribute pair.
Definition avp.c:1271
int radcli_avp_get_bytes_by_num(const radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, const void **out, size_t *len)
Look up an attribute by legacy numeric ID and read its first occurrence's raw bytes.
Definition avp.c:1047
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
int radcli_avp_add_ip4_by_num(radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, struct in_addr value)
Look up an IPv4-address-typed attribute by legacy numeric ID and append it.
Definition avp.c:539
radcli_avp_list * radcli_avp_list_new(void)
Create an empty attribute-value pair list.
Definition avp.c:145
int radcli_avp_add_gigawords64_by_num(radcli_ctx *ctx, radcli_avp_list *list, uint32_t attrid, uint32_t vendor, uint64_t value)
Look up the octets attribute by legacy numeric ID and append a 64-bit counter as an Octets/Gigawords ...
Definition avp.c:1355
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
int radcli_avp_get_ip6_by_num(const radcli_avp_list *list, const radcli_ctx *ctx, uint32_t attrid, uint32_t vendor, struct in6_addr *out, unsigned *prefix)
Look up an IPv6-address or IPv6-prefix-typed attribute by legacy numeric ID and read its first occurr...
Definition avp.c:1007
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
int radcli_avp_add_str(radcli_avp_list *list, const radcli_attr_def *def, const char *value)
Append a string- or text-typed attribute.
Definition avp.c:299
int radcli_avp_add_uint64(radcli_avp_list *list, const radcli_attr_def *def, uint64_t value)
Append a 64-bit integer- or ifid-typed attribute.
Definition avp.c:345
const char * radcli_avp_get_cstr(const radcli_avp *a)
Read an attribute's value as a NUL-terminated string, with no allocation or copy.
Definition avp.c:896
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
const char * radcli_attr_def_name(const radcli_attr_def *def)
Return an attribute definition's canonical name.
Definition dict2.c:615
radcli_attr_type
Definition radcli2.h:173
radcli_attr_type radcli_attr_def_type(const radcli_attr_def *def)
Return an attribute definition's wire type.
Definition dict2.c:624
@ RADCLI_TYPE_IPV6PREFIX
Definition radcli2.h:200
@ RADCLI_TYPE_TEXT
Definition radcli2.h:215
@ RADCLI_TYPE_STRING
Definition radcli2.h:174
@ RADCLI_TYPE_DATE
Definition radcli2.h:190
@ RADCLI_TYPE_IFID
Definition radcli2.h:224
@ RADCLI_TYPE_INTEGER64
Definition radcli2.h:205
@ RADCLI_TYPE_IPADDR
Definition radcli2.h:183
@ RADCLI_TYPE_IPV6ADDR
Definition radcli2.h:197
@ RADCLI_TYPE_IPV4PREFIX
Definition radcli2.h:211
@ RADCLI_TYPE_INTEGER
Definition radcli2.h:180