Radcli library 2.0.0
A simple radius library -- new API reference
Loading...
Searching...
No Matches
aaa2.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 radcli_aaa(): the new API's counterpart to radcli.h's
30 * rc_aaa()/rc_aaa_ctx() (lib/buildreq.c) -- Acct-Delay-Time autofill plus
31 * fail-over across every configured authserver/acctserver entry, layered on
32 * top of radcli_request_*'s single-server building block the same way
33 * lib/aaa_ctx.c layers over rc_auth() today. Added as a separate wrapper
34 * rather than an extension of radcli_request_perform(), so that function's
35 * single-server contract is untouched. Unlike the legacy rc_aaa(), this has
36 * no NAS-Port autofill: that value is known to the caller before the call,
37 * same as any other attribute, so it belongs in send via
38 * radcli_avp_add_uint32_by_num() rather than as a special-cased parameter.
39 *
40 * Deliberately does not go through radcli_request_new()/_perform(): those
41 * are pinned to the single-server-per-request rule (REQ-NET2-INIT-003)
42 * and have no way to name a specific server. Instead
43 * this calls lib/request.c's radcli_do_exchange() directly -- once per
44 * server attempted -- so the wire-format/Response-Authenticator/
45 * Message-Authenticator logic is the exact code radcli_request_perform()
46 * itself uses, not a second copy of it. */
47
48#include <config.h>
49#include <includes.h>
50#include <radcli/radcli.h>
51#include <radcli/radcli2.h>
52#include "avp.h"
53#include "util.h"
54#include "options.h"
55
56/* Mirrors rc_fill_acct_pairs()'s (lib/buildreq.c) Acct-Delay-Time semantics,
57 * but as a fresh radcli_avp_list per attempt rather than one VALUE_PAIR
58 * mutated in place, since radcli_avp_list is add-only by design
59 * (lib/avp.c). Any original Acct-Delay-Time in send is dropped from the
60 * copy -- its value is already folded into start_time by the caller -- so
61 * the freshly computed one is the only one that reaches the wire. */
62/*- Build the per-attempt attribute list for one radcli_aaa() server attempt.
63 *
64 * @param send the caller-supplied attributes to copy into the attempt.
65 * @param code the request code; Acct-Delay-Time is only added for
66 * RADCLI_CODE_ACCOUNTING_REQUEST.
67 * @param d_adt the Acct-Delay-Time attribute definition, or NULL if the
68 * dictionary doesn't define it.
69 * @param start_time the elapsed-time origin, in seconds, used to compute
70 * Acct-Delay-Time.
71 * @return a newly allocated radcli_avp_list, or NULL on allocation failure.
72 -*/
73static radcli_avp_list *build_attempt(const radcli_avp_list *send,
74 radcli_code code,
75 const radcli_attr_def *d_adt, double start_time)
76{
77 radcli_avp_list *attempt;
79 const radcli_avp *a;
80
81 attempt = radcli_avp_list_new();
82 if (attempt == NULL)
83 return NULL;
84
85 it = radcli_avp_list_iter(send);
86 while ((a = radcli_avp_iter_next(&it)) != NULL) {
87 const void *data;
88 size_t len;
89
90 if (radcli_avp_def(a) == d_adt)
91 continue;
92
93 if (radcli_avp_get_bytes(a, &data, &len) != 0 ||
94 radcli_avp_add_bytes(attempt, radcli_avp_def(a), data, len) != 0) {
95 radcli_avp_list_free(attempt);
96 return NULL;
97 }
98 }
99
100 if (code == RADCLI_CODE_ACCOUNTING_REQUEST && d_adt != NULL) {
101 uint32_t dtime = (uint32_t)(rc_getmtime() - start_time);
102
103 if (radcli_avp_add_uint32(attempt, d_adt, dtime) != 0) {
104 radcli_avp_list_free(attempt);
105 return NULL;
106 }
107 }
108
109 return attempt;
110}
111
117
160int radcli_aaa(radcli_ctx *ctx, radcli_code code, const radcli_avp_list *send,
161 radcli_code *out_code, radcli_avp_list **out_attrs)
162{
163 rc_handle *rh = (rc_handle *)ctx;
164 const char *optname;
165 rc_type type;
166 SERVER *servers;
167 const radcli_attr_def *d_adt;
168 double start_time;
169 int timeout, retries;
170 int servernum, result = ERROR_RC;
171
172 if (rh == NULL || send == NULL)
173 return RADCLI_ERROR;
174
175 if (code != RADCLI_CODE_ACCESS_REQUEST && code != RADCLI_CODE_ACCOUNTING_REQUEST) {
176 rc_log(LOG_ERR, "radcli_aaa: code must be RADCLI_CODE_ACCESS_REQUEST "
177 "or RADCLI_CODE_ACCOUNTING_REQUEST");
178 return RADCLI_ERROR;
179 }
180
181 /* Same TLS/DTLS + request-type rule as radcli_request_new()
182 * (REQ-NET2-INIT-002) / rc_select_aaa_server() (lib/buildreq.c). */
183 if (rh->so_type == RC_SOCKET_TLS || rh->so_type == RC_SOCKET_DTLS ||
184 code == RADCLI_CODE_ACCESS_REQUEST) {
185 optname = "authserver";
186 type = AUTH;
187 } else {
188 optname = "acctserver";
189 type = ACCT;
190 }
191
192 servers = radcli2_priv_conf_srv(rh, optname);
193 if (servers == NULL || servers->max == 0) {
194 rc_log(LOG_ERR, "radcli_aaa: no %s configured", optname);
195 return RADCLI_ERROR;
196 }
197
198 timeout = rc_conf_int_id(rh, OPT_RADIUS_TIMEOUT);
199 retries = rc_conf_int_id(rh, OPT_RADIUS_RETRIES);
200
201 d_adt = radcli_dict_lookup_num(ctx, PW_ACCT_DELAY_TIME, 0);
202
203 /* start_time is measured once, before the first attempt -- not reset
204 * on fail-over -- so Acct-Delay-Time keeps accumulating real elapsed
205 * time across every server tried, exactly as rc_aaa_ctx_server()'s
206 * single, retry-spanning start_time does. A pre-existing
207 * Acct-Delay-Time in send is folded in the same way
208 * rc_fill_acct_pairs() folds one found already on the VALUE_PAIR
209 * list: as an initial offset, not a value copied onto the wire as-is. */
210 start_time = rc_getmtime();
211 if (code == RADCLI_CODE_ACCOUNTING_REQUEST && d_adt != NULL) {
212 const radcli_avp *existing = radcli_avp_get(send, d_adt, 0);
213
214 if (existing != NULL) {
215 uint32_t v;
216
217 if (radcli_avp_get_uint32(existing, &v) == 0)
218 start_time -= v;
219 }
220 }
221
222 servernum = 0;
223 do {
224 radcli_avp_list *attempt;
225 uint8_t recv_buffer[RC_BUFFER_LEN];
226 unsigned char vector[AUTH_VECTOR_LEN];
227 size_t recv_len = 0;
228 uint8_t reply_code = 0;
229 char server[AUTH_ID_LEN + 1] = "";
230 char secret[MAX_SECRET_LENGTH + 1] = "";
231
232 attempt = build_attempt(send, code, d_adt, start_time);
233 if (attempt == NULL)
234 return RADCLI_ERROR;
235
236 strlcpy(server, servers->name[servernum], sizeof(server));
237 if (servers->secret[servernum] != NULL)
238 strlcpy(secret, servers->secret[servernum], sizeof(secret));
239
240 result = radcli_do_exchange(rh, (uint8_t)code, attempt, server,
241 servers->port[servernum], secret,
242 timeout, retries, 0, type,
243 recv_buffer, sizeof(recv_buffer), &recv_len,
244 vector, &reply_code);
245
246 radcli_avp_list_free(attempt);
247 memset(secret, 0, sizeof(secret));
248
249 if (result == OK_RC || result == REJECT_RC || result == CHALLENGE_RC) {
250 radcli_avp_list *decoded = NULL;
251
252 if (recv_len > 0) {
253 if (radcli_avp_decode(rh, servers->secret[servernum] ? servers->secret[servernum] : "",
254 vector, recv_buffer, recv_len, 0, &decoded) != 0)
255 return RADCLI_ERROR;
256 }
257
258 if (out_code != NULL)
259 *out_code = (radcli_code)reply_code;
260 if (out_attrs != NULL)
261 *out_attrs = decoded;
262 else
263 radcli_avp_list_free(decoded);
264
265 DEBUG(rh, LOG_INFO, "radcli_aaa: succeeded against server %u (%s)",
266 servernum, server);
267 return RADCLI_OK;
268 }
269
270 DEBUG(rh, LOG_INFO, "radcli_aaa: attempt against server %u (%s) failed "
271 "(%d); remaining: %d", servernum, server, result,
272 servers->max - servernum - 1);
273 servernum++;
274 } while (servernum < servers->max && (result == TIMEOUT_RC || result == NETUNREACH_RC));
275
276 return result == TIMEOUT_RC ? RADCLI_TIMEOUT : RADCLI_ERROR;
277}
278
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_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
int radcli_aaa(radcli_ctx *ctx, radcli_code code, const radcli_avp_list *send, radcli_code *out_code, radcli_avp_list **out_attrs)
Perform an authentication or accounting exchange with Acct-Delay-Time autofill and fail-over across e...
Definition aaa2.c:160
radcli_code
Definition radcli2.h:506
@ RADCLI_TIMEOUT
No reply from any address the server name resolved to.
Definition radcli2.h:565
@ RADCLI_ERROR
Malformed input, a verification failure, or no server configured.
Definition radcli2.h:564
@ RADCLI_OK
A validated reply was received; see radcli_request_code() for which one.
Definition radcli2.h:563