Radcli library 2.0.0
A simple radius library -- legacy API reference
Loading...
Searching...
No Matches
sendserver.c
1/*
2 * Copyright (C) 1995,1996,1997 Lars Fenneberg
3 * Copyright (C) 2015,2016 Nikos Mavrogiannopoulos
4 *
5 * Copyright 1992 Livingston Enterprises, Inc.
6 *
7 * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan
8 * and Merit Network, Inc. All Rights Reserved
9 *
10 * See the file COPYRIGHT for the respective terms and conditions.
11 *
12 */
13
14#include <includes.h>
15#include <radcli/radcli.h>
16#include <poll.h>
17#include "dict2.h"
18#include "options.h"
19#include "util.h"
20#include "avp.h"
21#include "rc-crypto.h"
22#include "rc-random.h"
23
24#if defined(HAVE_GNUTLS)
25# include <gnutls/gnutls.h>
26# include <gnutls/crypto.h>
27#endif
28
29#if defined(__linux__)
30#include <linux/in6.h>
31#endif
32
33
34/* Resets fd to -1 after closing so a later unconditional cleanup (e.g. the
35 * shared `cleanup:` label's `if (sockfd >= 0) SCLOSE(sockfd)`) cannot close
36 * the same descriptor a second time. */
37#define SCLOSE(fd) do { if (sfuncs->close_fd) sfuncs->close_fd(fd); (fd) = -1; } while (0)
38
39/* rc_check_reply(), populate_ctx(), add_msg_auth_attr(),
40 * validate_message_authenticator() are declared in include/includes.h (no
41 * longer static): they operate purely on raw bytes/AUTH_HDR/secret/vector,
42 * never on VALUE_PAIR, so radcli_transport_exchange() below reuses them
43 * directly instead of reimplementing the same RFC 2865 SS3 Response
44 * Authenticator and Message-Authenticator/Blast-RADIUS logic a second
45 * time. */
46
47/*- Allocate and fill an RC_AAA_CTX capturing the secret/vector used for a
48 * sent request, if the caller asked for one.
49 *
50 * @param ctx if non-NULL and *ctx is NULL, allocated and filled; a no-op
51 * if ctx is NULL.
52 * @param secret the shared secret used for the request.
53 * @param vector the request authenticator vector used for the request.
54 * @return OK_RC on success (including the ctx == NULL no-op case),
55 * ERROR_RC if *ctx is already non-NULL or allocation failed.
56 -*/
57int populate_ctx(RC_AAA_CTX ** ctx, char secret[MAX_SECRET_LENGTH + 1],
58 uint8_t vector[AUTH_VECTOR_LEN])
59{
60 if (ctx) {
61 if (*ctx != NULL)
62 return ERROR_RC;
63
64 *ctx = malloc(sizeof(RC_AAA_CTX));
65 if (*ctx) {
66 memcpy((*ctx)->secret, secret, sizeof((*ctx)->secret));
67 memcpy((*ctx)->request_vector, vector,
68 sizeof((*ctx)->request_vector));
69 } else {
70 return ERROR_RC;
71 }
72 }
73 return OK_RC;
74}
75/*- Verify a reply packet's length, sequence number, and Response
76 * Authenticator digest.
77 *
78 * @param auth a pointer to AUTH_HDR.
79 * @param bufferlen the available buffer length.
80 * @param secret the secret used by the server.
81 * @param vector a random vector of AUTH_VECTOR_LEN.
82 * @param seq_nbr a unique sequence number.
83 * @return OK_RC upon success, BADRESP_RC if anything looks funny.
84 -*/
85int rc_check_reply(AUTH_HDR * auth, int bufferlen, char const *secret,
86 unsigned char const *vector, uint8_t seq_nbr)
87{
88 int secretlen;
89 int totallen;
90 unsigned char calc_digest[AUTH_VECTOR_LEN];
91 unsigned char reply_digest[AUTH_VECTOR_LEN];
92
93 totallen = ntohs(auth->length);
94 secretlen = (int)strlen(secret);
95
96 /* Do sanity checks on packet length */
97 if ((totallen < 20) || (totallen > 4096)) {
98 rc_log(LOG_ERR,
99 "rc_check_reply: received RADIUS server response with invalid length");
100 return BADRESP_RC;
101 }
102
103 /* Verify buffer space, should never trigger with current buffer size and check above */
104 if ((totallen + secretlen) > bufferlen) {
105 rc_log(LOG_ERR,
106 "rc_check_reply: not enough buffer space to verify RADIUS server response");
107 return BADRESP_RC;
108 }
109
110 /* Verify that id (seq. number) matches what we sent */
111 if (auth->id != seq_nbr) {
112 rc_log(LOG_ERR,
113 "rc_check_reply: received non-matching id in RADIUS server response");
114 return BADRESPID_RC;
115 }
116 /* Verify the reply digest */
117 memcpy((char *)reply_digest, (char *)auth->vector, AUTH_VECTOR_LEN);
118 memcpy((char *)auth->vector, (char *)vector, AUTH_VECTOR_LEN);
119 memcpy((char *)auth + totallen, secret, secretlen);
120 rc_md5_calc(calc_digest, (unsigned char *)auth, totallen + secretlen);
121
122 if (rc_memcmp((char *)reply_digest, (char *)calc_digest,
123 AUTH_VECTOR_LEN) != 0) {
124 rc_log(LOG_ERR,
125 "rc_check_reply: received invalid reply digest from RADIUS server");
126 return BADRESP_RC;
127 }
128
129 return OK_RC;
130
131}
132
133/*- Add a Message-Authenticator attribute to a message. Mandatory, for
134 * example, when sending a message containing an EAP-Message attribute.
135 *
136 * @param rh a handle to parsed configuration.
137 * @param secret the server's secret string.
138 * @param auth pointer to the AUTH_HDR structure.
139 * @param total_length total packet length before Message-Authenticator is
140 * added.
141 * @return total packet length after Message-Authenticator is added.
142 -*/
143int add_msg_auth_attr(rc_handle * rh, char * secret,
144 AUTH_HDR *auth, int total_length)
145{
146 size_t secretlen = rc_secret_len(secret);
147 uint8_t *msg_auth = (uint8_t *)auth + total_length;
148 msg_auth[0] = PW_MESSAGE_AUTHENTICATOR;
149 msg_auth[1] = 18;
150 memset(&msg_auth[2], 0, MD5_DIGEST_SIZE);
151 total_length += 18;
152 auth->length = htons((unsigned short)total_length);
153
154 /* Calculate HMAC-MD5 [RFC2104] hash */
155 uint8_t digest[MD5_DIGEST_SIZE];
156 rc_hmac_md5((uint8_t *)auth, (size_t)total_length, (uint8_t *)secret, secretlen, digest);
157 memcpy(&msg_auth[2], digest, MD5_DIGEST_SIZE);
158
159 return total_length;
160}
161
162/*- Validate a reply's Message-Authenticator attribute (RFC 2869 §5.14,
163 * RFC 3579 §3.2).
164 *
165 * @param recv_buffer the original packet.
166 * @param length the length of the attribute data (packet length minus
167 * AUTH_HDR_LEN).
168 * @param secret the RADIUS secret.
169 * @param req_auth the request authenticator from the Access-Request (RFC
170 * 3579 §3.2 requires MA in responses to be computed over the packet with
171 * the Request Authenticator in the Authenticator field, not the Response
172 * Authenticator).
173 * @return zero on success, other values for failure.
174 -*/
175int validate_message_authenticator(const uint8_t *recv_buffer,
176 size_t length, const char *secret,
177 const unsigned char *req_auth)
178{
179 uint8_t verify_buffer[RC_BUFFER_LEN];
180 pkt_buf vb;
181 uint8_t ma_copy[MD5_DIGEST_SIZE];
182 uint8_t digest[MD5_DIGEST_SIZE];
183 uint8_t attr_type, attr_len;
184 int ma_found = 0;
185
186 if (AUTH_HDR_LEN + length > sizeof(verify_buffer)) {
187 rc_log(LOG_ERR, "%s: packet too large for verification buffer", __func__);
188 return -1;
189 }
190
191 /* Copy the packet, substitute the Request Authenticator per RFC 3579 §3.2,
192 * and zero the Message-Authenticator value before computing HMAC-MD5. */
193 memcpy(verify_buffer, recv_buffer, AUTH_HDR_LEN + length);
194 memcpy(verify_buffer + 4, req_auth, AUTH_VECTOR_LEN);
195 pb_init_read(&vb, verify_buffer + AUTH_HDR_LEN, length, length);
196
197 while (pb_len(&vb) >= 2) {
198 attr_type = vb.data[0];
199 attr_len = vb.data[1];
200 if (attr_len < 2 || (size_t)attr_len > pb_len(&vb))
201 break; /* malformed; already rejected by upstream attr-loop */
202
203 if (attr_type == PW_MESSAGE_AUTHENTICATOR) {
204 if (attr_len != 2 + MD5_DIGEST_SIZE) {
205 rc_log(LOG_ERR, "%s: Message-Authenticator has wrong length %u",
206 __func__, (unsigned)(attr_len - 2));
207 return -1;
208 }
209 /* Save original value before zeroing in the verification copy */
210 memcpy(ma_copy, vb.data + 2, MD5_DIGEST_SIZE);
211 memset(vb.data + 2, '\0', MD5_DIGEST_SIZE);
212 ma_found = 1;
213 break;
214 }
215 assert(pb_pull(&vb, attr_len) == 0);
216 }
217
218 if (!ma_found)
219 return -1;
220
221 rc_hmac_md5(verify_buffer, AUTH_HDR_LEN + length, (uint8_t *)secret, rc_secret_len(secret), digest);
222 return rc_memcmp(ma_copy, digest, MD5_DIGEST_SIZE);
223}
224
225/*- Representation-agnostic RADIUS request/reply exchange.
226 *
227 * Resolves server_name to every A/AAAA address it has and tries each in
228 * turn -- a fresh socket and re-derived source address per attempt, since
229 * a name can resolve to a mix of address families -- retrying up to
230 * `retries` times before moving to the next address. send_buf is a
231 * complete, pre-encoded packet (header included); code, Identifier, and
232 * Request Authenticator are read straight from its header rather than
233 * passed separately. mgmt_secret picks the resolution path: non-zero
234 * resolves server_name to an address only (rc_getaddrinfo()) and uses
235 * secret as given (the "management poll" case); zero resolves both
236 * address and secret via radcli2_priv_find_server_addr(), which overwrites secret
237 * if server_name matches a configured authserver/acctserver entry.
238 * no_wait is fire-and-forget (REQ-NET-NET-017): send once to the first
239 * resolved address and return without waiting for a reply.
240 *
241 * On a reply, validates framing, the Response Authenticator
242 * (rc_check_reply()), and -- for AUTH over UDP/TCP -- the
243 * Message-Authenticator and its Blast-RADIUS first-attribute position
244 * (validate_message_authenticator()). On success the reply's attribute
245 * region (header stripped) is left in recv_buf[0 .. *recv_len); this
246 * function does not encode or decode individual attributes.
247 *
248 * Deliberately does NOT scrub secret before returning: secret is caller
249 * memory (radcli_do_exchange() passes radcli2's own persistent r->secret
250 * through here by reference, not a copy), and this function has no way to
251 * know whether the caller is done with it -- radcli_do_exchange()'s own
252 * caller (radcli_request_perform()) still needs it one call later, to
253 * decode the very reply this function just validated. Wiping it here (as
254 * an earlier version of this function did) zeroed r->secret before that
255 * decode ran, silently corrupting any salt-encrypted reply attribute
256 * (Tunnel-Password, MS-MPPE-*-Key -- RFC 2868 SS3.5). Each caller that
257 * owns a secret buffer is responsible for clearing it once IT is actually
258 * done: rc_send_server_ctx() (lib/legacy/send.c) at its own end of
259 * function, radcli_request_free() (lib/request.c) at r's end of life.
260 *
261 * @param rh a handle to parsed configuration.
262 * @param ctx if non-NULL, receives the context of the sent request; release with rc_aaa_ctx_free().
263 * @param server_name the server to resolve and contact.
264 * @param svc_port overrides the resolved port when non-zero.
265 * @param secret the shared secret; see mgmt_secret for how it is used/resolved.
266 * @param mgmt_secret non-zero for the "management poll" resolution path (see above).
267 * @param timeout per-address, per-attempt reply wait, in seconds.
268 * @param retries additional attempts per address after the first (0 = one attempt per address, no retry).
269 * @param no_wait fire-and-forget; see above.
270 * @param type AUTH or ACCT; selects the Message-Authenticator/Blast-RADIUS check.
271 * @param send_buf the complete, pre-built, pre-encoded packet to send.
272 * @param send_len send_buf's length in bytes.
273 * @param recv_buf destination for the reply's attribute region.
274 * @param recv_buf_cap recv_buf's capacity in bytes.
275 * @param recv_len set to the reply's attribute region length on success.
276 * @param out_code if non-NULL, set to the reply's raw wire Code octet
277 * (e.g. PW_ACCESS_ACCEPT) whenever recv_len is also set, i.e. on
278 * OK_RC/REJECT_RC/CHALLENGE_RC/BADRESP_RC; left untouched otherwise.
279 * @return OK_RC (0) on success, CHALLENGE_RC on Access-Challenge, TIMEOUT_RC
280 * if every address's retries are exhausted, REJECT_RC on reject, or
281 * negative on failure.
282 -*/
283/*- Validate and decode a reply already known to have the right Identifier
284 * and Response Authenticator (rc_check_reply() returned OK_RC) into
285 * recv_buf's attribute region, exactly as radcli_transport_exchange()'s own
286 * `got_reply:` block used to do inline. Factored out so
287 * radcli_transport_service_async() below can reuse the identical RFC
288 * 2865/2869/Blast-RADIUS validation instead of a second copy.
289 * secret/vector/type/server_name/svc_port are as radcli_transport_exchange()
290 * received them; recv_buf/recv_buf_cap/recv_len/out_code are as documented
291 * on radcli_transport_exchange() itself.
292 *
293 * Despite the above, this function does not simply trust its callers for the
294 * one property that would otherwise abort() the process if violated: whether
295 * recv_auth->length is at least AUTH_HDR_LEN. Both call sites only bound
296 * recv_auth->length against the bytes actually received, not against
297 * AUTH_HDR_LEN itself, and rc_check_reply()'s OK_RC does establish it -- but
298 * this function is static with exactly two callers, and REQ-GEN-STYLE-009
299 * treats a cross-function invariant on wire-controlled data as one reorg
300 * away from silently breaking, not as a fact to assert. Checked explicitly
301 * below instead.
302 *
303 * @return OK_RC/REJECT_RC/CHALLENGE_RC/BADRESP_RC/ERROR_RC -- never
304 * TIMEOUT_RC or BADRESPID_RC, which are decided by the caller before this
305 * is reached. A BADRESP_RC return here is this function's own verdict (an
306 * unrecognized reply code), unrelated to rc_check_reply()'s BADRESP_RC,
307 * which the caller must have already turned away before calling in.
308 -*/
309static int decode_reply(rc_handle *rh, RC_AAA_CTX **ctx, const char *server_name,
310 unsigned short svc_port, rc_type type,
311 char secret[MAX_SECRET_LENGTH + 1], const unsigned char *vector,
312 uint8_t *recv_buf, size_t recv_buf_cap,
313 size_t *recv_len, uint8_t *out_code)
314{
315 AUTH_HDR *recv_auth = (AUTH_HDR *)recv_buf;
316 int length = ntohs(recv_auth->length);
317 pkt_buf rb;
318 uint8_t attr_type, attr_len;
319 int result;
320
321 if ((size_t)length > recv_buf_cap)
322 length = (int)recv_buf_cap;
323
324 /* Verify it's a well-formed RADIUS packet before doing ANYTHING with it. */
325 pb_init_read(&rb, recv_buf, length, recv_buf_cap);
326 if (pb_pull(&rb, AUTH_HDR_LEN) != 0) {
327 rc_log(LOG_ERR, "%s: %s:%d: reply shorter than the RADIUS header",
328 __func__, server_name, svc_port);
329 return ERROR_RC;
330 }
331 while (pb_len(&rb) > 0) {
332 if (pb_peek_byte(&rb, 0, &attr_type) < 0 || pb_peek_byte(&rb, 1, &attr_len) < 0) {
333 rc_log(LOG_ERR, "%s: %s:%d: truncated attribute", __func__, server_name, svc_port);
334 return ERROR_RC;
335 }
336 if (attr_type == 0) {
337 rc_log(LOG_ERR, "%s: %s:%d: attribute zero is invalid", __func__, server_name, svc_port);
338 return ERROR_RC;
339 }
340 if (attr_len < 2) {
341 rc_log(LOG_ERR, "%s: %s:%d: attribute length is too small", __func__, server_name, svc_port);
342 return ERROR_RC;
343 }
344 if (attr_len > pb_len(&rb)) {
345 rc_log(LOG_ERR, "%s: %s:%d: attribute overflows the packet", __func__, server_name, svc_port);
346 return ERROR_RC;
347 }
348 assert(pb_pull(&rb, attr_len) == 0);
349 }
350
351 length = ntohs(recv_auth->length) - AUTH_HDR_LEN;
352 if (length < 0)
353 length = 0;
354
355 result = populate_ctx(ctx, secret, (unsigned char *)vector);
356 if (result != OK_RC)
357 return result;
358
359 /* Per draft-ietf-radext-deprecating-radius, Message-Authenticator MUST
360 * be the first attribute in Access-Request responses (BLAST RADIUS).
361 * Not required for Accounting-Response. Unchanged from
362 * rc_send_server_ctx()'s own, identical check (f6f2487). */
363 if (type == AUTH) {
364 pkt_buf mb;
365 uint8_t mtype, mlen;
366 int has_ma = 0;
367
368 pb_init_read(&mb, recv_buf + AUTH_HDR_LEN, (size_t)length, (size_t)length);
369 while (pb_len(&mb) > 0) {
370 assert(pb_peek_byte(&mb, 0, &mtype) == 0);
371 assert(pb_peek_byte(&mb, 1, &mlen) == 0);
372 if (mtype == PW_MESSAGE_AUTHENTICATOR) {
373 has_ma = 1;
374 break;
375 }
376 assert(pb_pull(&mb, mlen) == 0);
377 }
378
379 if (has_ma) {
380 if (validate_message_authenticator(recv_buf, (size_t)length, secret, vector)) {
381 rc_log(LOG_ERR, "%s: %s:%d: received attribute Message-Authenticator is incorrect",
382 __func__, server_name, svc_port);
383 return ERROR_RC;
384 }
385 }
386
387 if (rh->so_type != RC_SOCKET_TLS && rh->so_type != RC_SOCKET_DTLS) {
388 if (length == 0 || recv_buf[AUTH_HDR_LEN] != PW_MESSAGE_AUTHENTICATOR) {
389 char *p = rc_conf_str_id(rh, OPT_REQUIRE_MESSAGE_AUTHENTICATOR);
390 if (p == NULL || (strcasecmp(p, "false") != 0 &&
391 strcasecmp(p, "no") != 0)) {
392 rc_log(LOG_ERR, "%s: %s:%d: required attribute Message-Authenticator "
393 "is missing or not first", __func__, server_name, svc_port);
394 return ERROR_RC;
395 }
396 }
397 }
398 }
399
400 {
401 uint8_t code = recv_auth->code; /* memmove() below invalidates recv_auth */
402
403 *recv_len = (size_t)length;
404 if (out_code)
405 *out_code = code;
406 if (recv_buf_cap > (size_t)AUTH_HDR_LEN)
407 memmove(recv_buf, recv_buf + AUTH_HDR_LEN, (size_t)length);
408
409 switch (code) {
410 case PW_ACCESS_ACCEPT:
411 case PW_PASSWORD_ACK:
412 case PW_ACCOUNTING_RESPONSE:
413 return OK_RC;
414 case PW_ACCESS_REJECT:
415 case PW_PASSWORD_REJECT:
416 return REJECT_RC;
417 case PW_ACCESS_CHALLENGE:
418 return CHALLENGE_RC;
419 default:
420 rc_log(LOG_ERR, "%s: received RADIUS server response neither ACCEPT nor "
421 "REJECT, code=%d is invalid", __func__, code);
422 return BADRESP_RC;
423 }
424 }
425}
426
427/*- Representation-agnostic send/retry/receive core, shared by
428 * rc_send_server_ctx() (legacy VALUE_PAIR API) and lib/request.c's
429 * radcli_request_perform() (radcli2 API): resolves server_name (or takes
430 * mgmt_secret's pre-resolved addr_info-only path for a management/CoA
431 * response), holds the configured network namespace switched for the
432 * whole call, sends send_buf once per address/retry, and, for each address
433 * in resolution order, retries up to retries times waiting timeout seconds
434 * per attempt -- moving to the next address only once the current one is
435 * exhausted. On a datagram, validates it via rc_check_reply() (RFC 2865
436 * SS3 Response Authenticator + Identifier match) and, unless rh's
437 * transport is TLS/DTLS, validate_message_authenticator() (RFC 2869
438 * SS5.14), discarding anything that fails either and continuing to wait.
439 *
440 * @param rh a handle to parsed configuration.
441 * @param ctx if non-NULL and *ctx is NULL, filled via populate_ctx() with
442 * the secret/vector actually used.
443 * @param server_name the server to resolve and contact.
444 * @param svc_port overrides the resolved port when non-zero.
445 * @param secret the shared secret; radcli2_priv_find_server_addr() may
446 * overwrite it when mgmt_secret is 0.
447 * @param mgmt_secret nonzero to skip server-name lookup in rh's configured
448 * server list and resolve server_name directly (the CoA/Disconnect reply
449 * path, whose peer is the DAC that sent the request, not a configured
450 * RADIUS server).
451 * @param timeout per-attempt reply wait, in seconds.
452 * @param retries additional retransmit attempts after the first, per
453 * address.
454 * @param no_wait nonzero to send once and return without waiting for a
455 * reply (accounting fire-and-forget).
456 * @param type AUTH or ACCT.
457 * @param send_buf the complete, pre-built, pre-encoded packet to send;
458 * its Identifier (send_buf[1]) and Request Authenticator (send_buf+4)
459 * are reused to validate the reply.
460 * @param send_len send_buf's length in bytes.
461 * @param recv_buf filled with the reply's attributes (header stripped) on
462 * a terminal OK_RC/REJECT_RC/CHALLENGE_RC.
463 * @param recv_buf_cap recv_buf's capacity in bytes.
464 * @param recv_len set to the number of attribute bytes written to recv_buf.
465 * @param out_code if non-NULL, set to the reply's RADIUS code on a
466 * terminal result.
467 * @return OK_RC/REJECT_RC/CHALLENGE_RC on a validated reply, TIMEOUT_RC if
468 * every address's retries were exhausted, ERROR_RC on failure.
469 -*/
470int radcli_transport_exchange(rc_handle *rh, RC_AAA_CTX **ctx,
471 char *server_name, unsigned short svc_port,
472 char secret[MAX_SECRET_LENGTH + 1], int mgmt_secret,
473 int timeout, int retries, int no_wait, rc_type type,
474 const uint8_t *send_buf, int send_len,
475 uint8_t *recv_buf, size_t recv_buf_cap, size_t *recv_len,
476 uint8_t *out_code)
477{
478 struct addrinfo *auth_addr = NULL, *cur_addr;
479 const rc_sockets_override *sfuncs;
480 int sockfd = -1;
481 int result = 0;
482 char *ns = NULL;
483 int ns_def_hdl = 0;
484 char *server_type = (type == ACCT) ? "acct" : "auth";
485 const unsigned char *vector = send_buf + 4; /* AUTH_HDR: code(1) id(1) length(2) vector(16) */
486 uint8_t seq_nbr = send_buf[1];
487
488 if (server_name == NULL || server_name[0] == '\0')
489 return ERROR_RC;
490 if (send_len < AUTH_HDR_LEN)
491 return ERROR_RC;
492
493 ns = rc_conf_str_id(rh, OPT_NAMESPACE);
494 if (ns != NULL) {
495 if (-1 == rc_set_netns(ns, &ns_def_hdl)) {
496 rc_log(LOG_ERR, "radcli_transport_exchange: namespace %s set failed", ns);
497 return ERROR_RC;
498 }
499 }
500
501 if (mgmt_secret) {
502 auth_addr = rc_getaddrinfo(server_name, type == AUTH ? PW_AI_AUTH : PW_AI_ACCT);
503 if (auth_addr == NULL) {
504 result = ERROR_RC;
505 goto exit_error;
506 }
507 } else {
508 if (radcli2_priv_find_server_addr(rh, server_name, &auth_addr, secret, type) != 0) {
509 rc_log(LOG_ERR, "radcli_transport_exchange: unable to find server: %s",
510 server_name);
511 result = ERROR_RC;
512 goto exit_error;
513 }
514 }
515
516 sfuncs = &rh->so;
517
518 if (sfuncs->static_secret) {
519 /* any static secret set in sfuncs overrides the configured/resolved one */
520 strlcpy(secret, sfuncs->static_secret, MAX_SECRET_LENGTH + 1);
521 }
522
523 if (sfuncs->lock) {
524 if (sfuncs->lock(sfuncs->ptr) != 0) {
525 rc_log(LOG_ERR, "%s: lock error", __func__);
526 result = ERROR_RC;
527 goto exit_error;
528 }
529 }
530
531 result = TIMEOUT_RC; /* if every address is unreachable/times out */
532
533 for (cur_addr = auth_addr; cur_addr != NULL; cur_addr = cur_addr->ai_next) {
534 struct sockaddr_storage our_sockaddr;
535 unsigned discover_local_ip;
536 int retry_max = retries;
537 int this_retries = 0;
538
539 if (svc_port) {
540 if (cur_addr->ai_family == AF_INET)
541 ((struct sockaddr_in *)cur_addr->ai_addr)->sin_port = htons(svc_port);
542 else
543 ((struct sockaddr_in6 *)cur_addr->ai_addr)->sin6_port = htons(svc_port);
544 }
545
546 rc_own_bind_addr(rh, &our_sockaddr);
547 discover_local_ip = 0;
548 if (our_sockaddr.ss_family == AF_INET &&
549 ((struct sockaddr_in *)(&our_sockaddr))->sin_addr.s_addr == INADDR_ANY)
550 discover_local_ip = 1;
551
552 if (discover_local_ip) {
553 result = radcli2_priv_get_srcaddr(SA(&our_sockaddr), cur_addr->ai_addr);
554 if (result != OK_RC) {
555 rc_log(LOG_ERR, "radcli_transport_exchange: cannot figure our own address");
556 continue; /* try the next resolved address, if any */
557 }
558 }
559
560 if (sfuncs->get_fd) {
561 sockfd = sfuncs->get_fd(sfuncs->ptr, SA(&our_sockaddr));
562 if (sockfd < 0) {
563 rc_log(LOG_ERR, "radcli_transport_exchange: socket: %s", strerror(errno));
564 result = ERROR_RC;
565 continue;
566 }
567 }
568
569 if (our_sockaddr.ss_family == AF_INET6) {
570 char *non_temp_addr = rc_conf_str_id(rh, OPT_USE_PUBLIC_ADDR);
571 if (non_temp_addr && strcasecmp(non_temp_addr, "true") == 0) {
572#if defined(__linux__)
573 int sock_opt = IPV6_PREFER_SRC_PUBLIC;
574 if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADDR_PREFERENCES,
575 &sock_opt, sizeof(sock_opt)) != 0) {
576 rc_log(LOG_ERR, "radcli_transport_exchange: setsockopt: %s",
577 strerror(errno));
578 result = ERROR_RC;
579 SCLOSE(sockfd);
580 continue;
581 }
582#elif defined(BSD) || defined(__APPLE__)
583 int sock_opt = 0;
584 if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_PREFER_TEMPADDR,
585 &sock_opt, sizeof(sock_opt)) != 0) {
586 rc_log(LOG_ERR, "radcli_transport_exchange: setsockopt: %s",
587 strerror(errno));
588 result = ERROR_RC;
589 SCLOSE(sockfd);
590 continue;
591 }
592#else
593 rc_log(LOG_INFO, "radcli_transport_exchange: Usage of non-temporary "
594 "IPv6 address is not supported in this system");
595#endif
596 }
597 }
598
599 if (rh->debug) {
600 char our_addr_txt[50] = "", addr_txt[50] = "";
601
602 getnameinfo(SA(&our_sockaddr), SS_LEN(&our_sockaddr), NULL, 0,
603 our_addr_txt, sizeof(our_addr_txt), NI_NUMERICHOST);
604 getnameinfo(cur_addr->ai_addr, cur_addr->ai_addrlen, NULL, 0,
605 addr_txt, sizeof(addr_txt), NI_NUMERICHOST);
606 DEBUG(rh, LOG_ERR,
607 "DEBUG: radcli_transport_exchange: timeout=%d retries=%d local %s : 0, "
608 "remote %s : %u\n", timeout, retry_max, our_addr_txt, addr_txt, svc_port);
609 }
610
611 for (;;) {
612 socklen_t salen;
613 int recv_length;
614 struct pollfd pfd;
615 double start_time, poll_timeout;
616
617 do {
618 result = sfuncs->sendto(sfuncs->ptr, sockfd, (const char *)send_buf,
619 (unsigned int)send_len, 0,
620 SA(cur_addr->ai_addr), cur_addr->ai_addrlen);
621 } while (result == -1 && errno == EINTR);
622 if (result == -1) {
623 result = errno == ENETUNREACH ? NETUNREACH_RC : ERROR_RC;
624 rc_log(LOG_ERR, "%s: socket: %s", __FUNCTION__, strerror(errno));
625 break; /* try the next address */
626 }
627
628 if (no_wait) {
629 SCLOSE(sockfd);
630 result = populate_ctx(ctx, secret, (unsigned char *)vector);
631 goto cleanup; /* first address only -- no reply to judge a retry by */
632 }
633
634 if (sfuncs->get_active_fd) {
635 int new_fd = sfuncs->get_active_fd(sfuncs->ptr);
636 if (new_fd >= 0)
637 sockfd = new_fd;
638 }
639 pfd.fd = sockfd;
640 pfd.events = POLLIN;
641 pfd.revents = 0;
642 start_time = rc_getmtime();
643 for (poll_timeout = timeout; poll_timeout > 0;
644 poll_timeout -= rc_getmtime() - start_time) {
645 result = poll(&pfd, 1, poll_timeout * 1000);
646 if (result != -1 || errno != EINTR)
647 break;
648 }
649
650 if (result == -1) {
651 rc_log(LOG_ERR, "radcli_transport_exchange: poll: %s", strerror(errno));
652 SCLOSE(sockfd);
653 result = ERROR_RC;
654 goto cleanup;
655 }
656
657 if (result == 1 && (pfd.revents & POLLIN) != 0) {
658 salen = cur_addr->ai_addrlen;
659 do {
660 recv_length = sfuncs->recvfrom(sfuncs->ptr, sockfd,
661 (char *)recv_buf,
662 (int)recv_buf_cap, 0,
663 SA(cur_addr->ai_addr), &salen);
664 } while (recv_length == -1 && errno == EINTR);
665
666 if (recv_length <= 0) {
667 int e = errno;
668 rc_log(LOG_ERR, "radcli_transport_exchange: recvfrom: %s:%d: %s",
669 server_name, svc_port, strerror(e));
670 if (recv_length == -1 && (e == EAGAIN || e == EINTR))
671 continue;
672 SCLOSE(sockfd);
673 result = ERROR_RC;
674 goto cleanup;
675 }
676
677 {
678 AUTH_HDR *recv_auth = (AUTH_HDR *)recv_buf;
679
680 if (recv_length < AUTH_HDR_LEN ||
681 recv_length < ntohs(recv_auth->length)) {
682 rc_log(LOG_ERR, "radcli_transport_exchange: recvfrom: "
683 "%s:%d: reply is too short", server_name, svc_port);
684 SCLOSE(sockfd);
685 result = ERROR_RC;
686 goto cleanup;
687 }
688
689 result = rc_check_reply(recv_auth, (int)recv_buf_cap, secret,
690 vector, seq_nbr);
691 if (result == OK_RC)
692 goto got_reply; /* out of both loops */
693 /* BADRESPID_RC (some other packet arrived, e.g. a stale
694 * retransmit's answer) and BADRESP_RC (bad length or
695 * Response Authenticator -- possibly spoofed) are both
696 * treated as "not our reply yet": keep waiting rather
697 * than handing an unverified packet to decode_reply(),
698 * which trusts its caller to have already validated it
699 * (REQ-GEN-STYLE-009). */
700 }
701 }
702
703 if (this_retries++ >= retry_max) {
704 char server_ip[128];
705 struct sockaddr_in *si = (struct sockaddr_in *)cur_addr->ai_addr;
706
707 inet_ntop(cur_addr->ai_family, &si->sin_addr, server_ip, sizeof(server_ip));
708 rc_log(LOG_ERR, "radcli_transport_exchange: no reply from RADIUS "
709 "%s server %s:%u", server_type, server_ip, svc_port);
710 result = TIMEOUT_RC;
711 break; /* try the next address */
712 }
713 }
714
715 SCLOSE(sockfd);
716 }
717
718 /* Every resolved address was tried without a valid reply. */
719 goto cleanup_nosock;
720
721 got_reply:
722 result = decode_reply(rh, ctx, server_name, svc_port, type, secret, vector,
723 recv_buf, recv_buf_cap, recv_len, out_code);
724
725 cleanup:
726 if (sockfd >= 0)
727 SCLOSE(sockfd);
728 cleanup_nosock:
729 if (auth_addr)
730 freeaddrinfo(auth_addr);
731 if (sfuncs->unlock) {
732 if (sfuncs->unlock(sfuncs->ptr) != 0)
733 rc_log(LOG_ERR, "%s: unlock error", __func__);
734 }
735 exit_error:
736 if (ns != NULL) {
737 if (-1 == rc_reset_netns(&ns_def_hdl)) {
738 rc_log(LOG_ERR, "radcli_transport_exchange: namespace %s reset failed", ns);
739 result = ERROR_RC;
740 }
741 }
742
743 return result;
744}
745
746/* Compares two sockaddrs' family+address+port -- REQ-NET2-SEND-016's
747 * explicit reply-source check on the shared, unconnected UDP request
748 * socket (replacing the kernel-level filtering a connect()ed per-request
749 * socket used to give for free). */
750static int reqreg_peer_matches(const struct sockaddr *from, const struct sockaddr *expected)
751{
752 if (from->sa_family != expected->sa_family)
753 return 0;
754 if (from->sa_family == AF_INET) {
755 const struct sockaddr_in *a = (const struct sockaddr_in *)from;
756 const struct sockaddr_in *b = (const struct sockaddr_in *)expected;
757 return a->sin_port == b->sin_port &&
758 memcmp(&a->sin_addr, &b->sin_addr, sizeof(a->sin_addr)) == 0;
759 }
760 {
761 const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)from;
762 const struct sockaddr_in6 *b = (const struct sockaddr_in6 *)expected;
763 return a->sin6_port == b->sin6_port &&
764 memcmp(&a->sin6_addr, &b->sin6_addr, sizeof(a->sin6_addr)) == 0;
765 }
766}
767
768/* Lazily allocates rh->reqreg (REQ-NET2-SEND-016), guarded by
769 * rh->reqreg_init_lock -- a small, always-initialized (radcli2_priv_new())
770 * per-ctx lock dedicated to this one-time allocation, distinct from
771 * reqreg->lock itself (which does not exist yet the first time this runs). */
772static int reqreg_ensure(rc_handle *rh)
773{
774 if (rh->reqreg != NULL)
775 return 0;
776
777 pthread_mutex_lock(&rh->reqreg_init_lock);
778 if (rh->reqreg == NULL) {
779 struct radcli_reqreg *reg = calloc(1, sizeof(*reg));
780 if (reg == NULL) {
781 pthread_mutex_unlock(&rh->reqreg_init_lock);
782 return -1;
783 }
784 pthread_mutex_init(&reg->lock, NULL);
785 rh->reqreg = reg;
786 }
787 pthread_mutex_unlock(&rh->reqreg_init_lock);
788 return 0;
789}
790
791/*- Reserve a slot in rh's in-flight registry (REQ-NET2-SEND-016),
792 * allocating the registry itself on first use. The Identifier is chosen by
793 * least-recently-used selection among currently-free slots (RFC 5080
794 * SS2.1.1), never randomly and never a fixed counter -- see
795 * REQ-NET2-SEND-010/016. The slot is marked valid (excluded from future
796 * reservation) but not yet armed: radcli_transport_send_async() arms it
797 * once the packet -- built using the returned id, per
798 * radcli_encode_request()'s forced_id parameter -- has actually been sent.
799 *
800 * @param rh a handle to parsed configuration.
801 * @param owner stored on the slot; written to directly by drain()/
802 * service_timeouts() on delivery.
803 * @param out_id set to the reserved Identifier (== the slot index) on success.
804 * @return the reserved slot index (0..RADCLI_CTX_MAX_INFLIGHT-1) on success,
805 * -1 if the registry is full or allocation failed.
806 -*/
807int radcli2_priv_reqreg_reserve(rc_handle *rh, struct radcli_async_send_st *owner, uint8_t *out_id)
808{
809 struct radcli_reqreg *reg;
810 int best = -1;
811 uint64_t best_seq = 0;
812 int i;
813
814 if (rh == NULL || owner == NULL || out_id == NULL)
815 return -1;
816 if (reqreg_ensure(rh) != 0)
817 return -1;
818 reg = rh->reqreg;
819
820 pthread_mutex_lock(&reg->lock);
821 for (i = 0; i < RADCLI_CTX_MAX_INFLIGHT; i++) {
822 if (reg->slots[i].valid)
823 continue;
824 /* LRU: the slot free the longest wins (RFC 5080 SS2.1.1);
825 * free_seq == 0 (never used yet) sorts first automatically. */
826 if (best == -1 || reg->slots[i].free_seq < best_seq) {
827 best = i;
828 best_seq = reg->slots[i].free_seq;
829 }
830 }
831 if (best == -1) {
832 pthread_mutex_unlock(&reg->lock);
833 rc_log(LOG_ERR, "%s: no free Identifier (%d requests already in flight)",
834 __func__, RADCLI_CTX_MAX_INFLIGHT);
835 return -1;
836 }
837 reg->slots[best].valid = 1;
838 reg->slots[best].armed = 0;
839 reg->slots[best].owner = owner;
840 pthread_mutex_unlock(&reg->lock);
841
842 *out_id = (uint8_t)best;
843 return best;
844}
845
846/*- Unconditionally vacate slot (valid=0, armed=0, owner=NULL, secret
847 * scrubbed, LRU-stamped free per REQ-NET2-SEND-016), making its Identifier
848 * available for reuse. Used both to undo a reservation
849 * radcli_transport_send_async() failed to arm, and by
850 * radcli_transport_async_abort() for a still-active, undelivered exchange. -*/
851void radcli2_priv_reqreg_release(rc_handle *rh, int slot)
852{
853 struct radcli_reqreg *reg;
854
855 if (rh == NULL || rh->reqreg == NULL || slot < 0 || slot >= RADCLI_CTX_MAX_INFLIGHT)
856 return;
857 reg = rh->reqreg;
858
859 pthread_mutex_lock(&reg->lock);
860 reg->slots[slot].valid = 0;
861 reg->slots[slot].armed = 0;
862 reg->slots[slot].owner = NULL;
863 memset(reg->slots[slot].secret, 0, sizeof(reg->slots[slot].secret));
864 reg->slots[slot].free_seq = ++reg->free_seq_ctr;
865 pthread_mutex_unlock(&reg->lock);
866}
867
868/*- Milliseconds remaining until the *earliest* deadline among every
869 * currently armed slot on rh (0 if one is already due), or -1 if rh has no
870 * registry yet or nothing is in flight. Used by lib/dae.c's
871 * radcli_ctx_get_poll() to fold RADCLI_REQUEST_SENDONLY's retransmit/
872 * timeout deadlines into its own timeout_ms, alongside DAE/watchdog
873 * deadlines (REQ-NET2-NET-001). -*/
874int radcli2_priv_reqreg_earliest_deadline_ms(rc_handle *rh)
875{
876 struct radcli_reqreg *reg;
877 double earliest = 0;
878 int have_one = 0;
879 int i;
880
881 if (rh == NULL || rh->reqreg == NULL)
882 return -1;
883 reg = rh->reqreg;
884
885 pthread_mutex_lock(&reg->lock);
886 for (i = 0; i < RADCLI_CTX_MAX_INFLIGHT; i++) {
887 if (!reg->slots[i].valid || !reg->slots[i].armed)
888 continue;
889 if (!have_one || reg->slots[i].deadline < earliest) {
890 earliest = reg->slots[i].deadline;
891 have_one = 1;
892 }
893 }
894 pthread_mutex_unlock(&reg->lock);
895
896 if (!have_one)
897 return -1;
898
899 {
900 double remaining = earliest - rc_getmtime();
901
902 if (remaining <= 0)
903 return 0;
904 return (int)(remaining * 1000) + 1;
905 }
906}
907
908/*- Drain every ready datagram on ctx's shared request socket (UDP,
909 * rh->req_fd) or session (TLS/DTLS, sfuncs->get_active_fd()), matching each
910 * against rh->reqreg by Identifier and -- for UDP, whose socket is shared
911 * and unconnected rather than connect()ed to one peer -- explicit source
912 * address validation against the slot's own recorded destination
913 * (REQ-NET2-SEND-016; TLS/DTLS needs no such check, the session itself is
914 * the authenticated peer). A validated reply resolves its slot immediately
915 * (vacating it for reuse, RFC 5080 SS2.1.1) and writes the outcome directly
916 * onto the owning struct radcli_async_send_st. A no-op if rh->reqreg is
917 * NULL (nothing ever registered). Never blocks. -*/
918void radcli2_priv_reqreg_drain(rc_handle *rh)
919{
920 struct radcli_reqreg *reg;
921 const rc_sockets_override *sfuncs;
922 int is_radsec;
923 char *ns;
924
925 if (rh == NULL || rh->reqreg == NULL)
926 return;
927 reg = rh->reqreg;
928 sfuncs = &rh->so;
929 is_radsec = (rh->so_type == RC_SOCKET_TLS || rh->so_type == RC_SOCKET_DTLS);
930 ns = rc_conf_str_id(rh, OPT_NAMESPACE);
931
932 for (;;) {
933 int sockfd;
934 uint8_t recv_buf[RC_BUFFER_LEN];
935 struct sockaddr_storage from;
936 socklen_t fromlen = sizeof(from);
937 int recv_length;
938 AUTH_HDR *recv_auth;
939 uint8_t id;
940 struct radcli_reqreg_slot *rslot;
941 int ns_def_hdl = 0;
942 int rc_result;
943
944 sockfd = is_radsec ? (sfuncs->get_active_fd ? sfuncs->get_active_fd(sfuncs->ptr) : -1)
945 : rh->req_fd;
946 if (sockfd == -1)
947 return;
948
949 if (ns != NULL && -1 == rc_set_netns(ns, &ns_def_hdl)) {
950 rc_log(LOG_ERR, "%s: namespace %s set failed", __func__, ns);
951 return;
952 }
953
954 /* Locked only around this one recv, not the whole exchange --
955 * see radcli_transport_send_async()'s doc comment on why the
956 * old hold-across-the-whole-lifetime discipline cannot survive
957 * a socket/session now shared by many concurrent slots. */
958 if (sfuncs->lock)
959 sfuncs->lock(sfuncs->ptr);
960
961 if (is_radsec) {
962 recv_length = radcli2_priv_tls_try_recv(rh, recv_buf, sizeof(recv_buf));
963 } else {
964 do {
965 recv_length = sfuncs->recvfrom(sfuncs->ptr, sockfd, (char *)recv_buf,
966 sizeof(recv_buf), 0, SA(&from), &fromlen);
967 } while (recv_length == -1 && errno == EINTR);
968 if (recv_length == -1 && errno == EAGAIN)
969 recv_length = 0;
970 }
971
972 if (sfuncs->unlock)
973 sfuncs->unlock(sfuncs->ptr);
974 if (ns != NULL)
975 rc_reset_netns(&ns_def_hdl);
976
977 if (recv_length <= 0)
978 return; /* nothing more ready (0), or a transport-level
979 * error (<0, already logged by the transport)
980 * neither this nor any other slot can act on here */
981
982 if ((size_t)recv_length < AUTH_HDR_LEN)
983 continue; /* too short to even carry an Identifier -- discard, keep draining */
984
985 recv_auth = (AUTH_HDR *)recv_buf;
986 id = recv_auth->id;
987
988 pthread_mutex_lock(&reg->lock);
989 rslot = &reg->slots[id];
990 if (!rslot->valid || !rslot->armed) {
991 pthread_mutex_unlock(&reg->lock);
992 continue; /* no in-flight exchange for this Identifier -- discard */
993 }
994 if (!is_radsec && !reqreg_peer_matches(SA(&from), SA(&rslot->peer))) {
995 pthread_mutex_unlock(&reg->lock);
996 continue;
997 }
998
999 rc_result = rc_check_reply(recv_auth, (int)sizeof(recv_buf), rslot->secret,
1000 rslot->vector, id);
1001 if (rc_result != OK_RC) {
1002 /* BADRESPID_RC (unreachable: id already matched above)
1003 * or BADRESP_RC (bad length or Response Authenticator --
1004 * possibly spoofed) -- keep the slot waiting rather than
1005 * handing an unverified packet to decode_reply(),
1006 * matching the pre-registry single-exchange semantics
1007 * (REQ-GEN-STYLE-009). */
1008 pthread_mutex_unlock(&reg->lock);
1009 continue;
1010 }
1011
1012 {
1013 struct radcli_async_send_st *owner = rslot->owner;
1014 char secret_copy[MAX_SECRET_LENGTH + 1];
1015 unsigned char vector_copy[AUTH_VECTOR_LEN];
1016 char server_name_copy[128];
1017 unsigned short svc_port_copy;
1018 rc_type type_copy;
1019 size_t recv_len = 0;
1020 uint8_t reply_code = 0;
1021 int decode_result;
1022 radcli_avp_list *attrs = NULL;
1023
1024 memcpy(secret_copy, rslot->secret, sizeof(secret_copy));
1025 memcpy(vector_copy, rslot->vector, sizeof(vector_copy));
1026 memcpy(server_name_copy, rslot->server_name, sizeof(server_name_copy));
1027 svc_port_copy = rslot->svc_port;
1028 type_copy = rslot->type;
1029
1030 /* Vacate now, before decode_reply()/radcli_avp_decode()
1031 * run: RFC 5080 SS2.1.1 permits reuse as soon as a valid
1032 * response is received, not once the application
1033 * collects it (REQ-NET2-SEND-016). */
1034 rslot->valid = 0;
1035 rslot->armed = 0;
1036 rslot->owner = NULL;
1037 memset(rslot->secret, 0, sizeof(rslot->secret));
1038 rslot->free_seq = ++reg->free_seq_ctr;
1039 pthread_mutex_unlock(&reg->lock);
1040
1041 decode_result = decode_reply(rh, NULL, server_name_copy, svc_port_copy,
1042 type_copy, secret_copy, vector_copy,
1043 recv_buf, sizeof(recv_buf), &recv_len, &reply_code);
1044 if (decode_result == OK_RC || decode_result == REJECT_RC ||
1045 decode_result == CHALLENGE_RC) {
1046 if (recv_len > 0 &&
1047 radcli_avp_decode(rh, secret_copy, vector_copy, recv_buf, recv_len, 0,
1048 &attrs) != 0)
1049 decode_result = ERROR_RC;
1050 }
1051 memset(secret_copy, 0, sizeof(secret_copy));
1052
1053 owner->result = decode_result;
1054 owner->reply_code = reply_code;
1055 owner->reply_attrs = attrs;
1056 owner->delivered = 1;
1057 }
1058 }
1059}
1060
1061/*- Service every registry slot whose retransmit/timeout deadline has
1062 * passed: retransmit (if retries remain) or resolve as TIMEOUT_RC
1063 * (vacating the slot), writing the outcome directly onto the owning struct
1064 * radcli_async_send_st. A no-op if rh->reqreg is NULL. Never blocks. -*/
1065void radcli2_priv_reqreg_service_timeouts(rc_handle *rh)
1066{
1067 struct radcli_reqreg *reg;
1068 const rc_sockets_override *sfuncs;
1069 int is_radsec;
1070 char *ns;
1071 int i;
1072
1073 if (rh == NULL || rh->reqreg == NULL)
1074 return;
1075 reg = rh->reqreg;
1076 sfuncs = &rh->so;
1077 is_radsec = (rh->so_type == RC_SOCKET_TLS || rh->so_type == RC_SOCKET_DTLS);
1078 ns = rc_conf_str_id(rh, OPT_NAMESPACE);
1079
1080 for (i = 0; i < RADCLI_CTX_MAX_INFLIGHT; i++) {
1081 struct radcli_reqreg_slot *rslot = &reg->slots[i];
1082
1083 pthread_mutex_lock(&reg->lock);
1084 if (!rslot->valid || !rslot->armed || rc_getmtime() < rslot->deadline) {
1085 pthread_mutex_unlock(&reg->lock);
1086 continue;
1087 }
1088
1089 if (rslot->retries_left-- <= 0) {
1090 struct radcli_async_send_st *owner = rslot->owner;
1091 char server_name_copy[128];
1092 unsigned short svc_port_copy = rslot->svc_port;
1093
1094 memcpy(server_name_copy, rslot->server_name, sizeof(server_name_copy));
1095 rslot->valid = 0;
1096 rslot->armed = 0;
1097 rslot->owner = NULL;
1098 memset(rslot->secret, 0, sizeof(rslot->secret));
1099 rslot->free_seq = ++reg->free_seq_ctr;
1100 pthread_mutex_unlock(&reg->lock);
1101
1102 rc_log(LOG_ERR, "%s: no reply from RADIUS server %s:%u",
1103 __func__, server_name_copy, svc_port_copy);
1104 owner->result = TIMEOUT_RC;
1105 owner->reply_code = 0;
1106 owner->reply_attrs = NULL;
1107 owner->delivered = 1;
1108 continue;
1109 }
1110
1111 {
1112 /* Snapshot what the retransmit needs, then release the
1113 * lock before sendto(): a concurrent drain() resolving
1114 * this exact slot in the meantime is a benign race
1115 * (worst case one harmless extra retransmit after the
1116 * reply already arrived). */
1117 uint8_t send_buf_copy[RC_BUFFER_LEN];
1118 int send_len_copy = rslot->send_len;
1119 struct sockaddr_storage peer_copy = rslot->peer;
1120 socklen_t peer_len_copy = rslot->peer_len;
1121 int ns_def_hdl = 0;
1122 int sockfd;
1123 int sresult;
1124
1125 memcpy(send_buf_copy, rslot->send_buf, (size_t)send_len_copy);
1126 pthread_mutex_unlock(&reg->lock);
1127
1128 sockfd = is_radsec ? (sfuncs->get_active_fd ? sfuncs->get_active_fd(sfuncs->ptr) : -1)
1129 : rh->req_fd;
1130 if (sockfd == -1)
1131 continue;
1132
1133 if (ns != NULL && -1 == rc_set_netns(ns, &ns_def_hdl)) {
1134 rc_log(LOG_ERR, "%s: namespace %s set failed", __func__, ns);
1135 continue;
1136 }
1137 if (sfuncs->lock)
1138 sfuncs->lock(sfuncs->ptr);
1139
1140 do {
1141 sresult = sfuncs->sendto(sfuncs->ptr, sockfd, (const char *)send_buf_copy,
1142 (unsigned int)send_len_copy, 0,
1143 SA(&peer_copy), peer_len_copy);
1144 } while (sresult == -1 && errno == EINTR);
1145
1146 if (sfuncs->unlock)
1147 sfuncs->unlock(sfuncs->ptr);
1148 if (ns != NULL)
1149 rc_reset_netns(&ns_def_hdl);
1150
1151 if (sresult == -1) {
1152 rc_log(LOG_ERR, "%s: sendto: %s", __func__, strerror(errno));
1153 continue; /* leave deadline as-is; retried again next call */
1154 }
1155
1156 pthread_mutex_lock(&reg->lock);
1157 if (rslot->valid && rslot->armed) /* still the same exchange */
1158 rslot->deadline = rc_getmtime() + rslot->timeout;
1159 pthread_mutex_unlock(&reg->lock);
1160 }
1161 }
1162}
1163
1164/*- Begin an async (poll-driven) send: reserve slot in rh's in-flight
1165 * registry, resolve server_name to its first address only (no DNS
1166 * fail-over, matching radcli_transport_exchange()'s own no_wait
1167 * simplification), send send_buf once over ctx's shared, persistent
1168 * request socket (UDP, opened lazily here if not already; TLS/DTLS reuses
1169 * sfuncs->get_active_fd() instead), and arm the slot for
1170 * radcli_transport_service_async() to drive to completion -- REQ-NET2-SEND-016.
1171 *
1172 * Unlike radcli_transport_exchange(), does not itself hold the configured
1173 * network namespace (lib/util.c's rc_set_netns()) switched for its whole
1174 * duration: namespace membership is per-thread, and this call, unlike a
1175 * blocking exchange, returns to a caller's event loop that may run other
1176 * socket I/O on the same thread before radcli_transport_service_async()
1177 * is next called -- switching once and resetting only at the very end
1178 * would leak the RADIUS server's namespace into that other I/O. Instead,
1179 * each of this function and radcli_transport_service_async() brackets
1180 * only its own, brief syscall(s) with a set/reset pair.
1181 *
1182 * @param rh a handle to parsed configuration.
1183 * @param slot the registry slot radcli2_priv_reqreg_reserve() already
1184 * reserved for this exchange -- its Identifier MUST already be baked into
1185 * send_buf (radcli_encode_request()'s forced_id), not patched in here.
1186 * @param server_name the server to resolve and contact.
1187 * @param svc_port overrides the resolved port when non-zero.
1188 * @param secret the shared secret; radcli2_priv_find_server_addr() may
1189 * overwrite it, exactly as radcli_transport_exchange() does.
1190 * @param type AUTH or ACCT.
1191 * @param send_buf the complete, pre-built, pre-encoded packet to send.
1192 * @param send_len send_buf's length in bytes; must fit RC_BUFFER_LEN.
1193 * @param timeout per-attempt reply wait, in seconds.
1194 * @param retries additional retransmit attempts after the first.
1195 * @param out zeroed, then filled in on success; left inactive (out->active
1196 * == 0) on failure -- the caller MUST then release slot itself
1197 * (radcli2_priv_reqreg_release()), since this function does not on failure
1198 * (the reservation is the caller's, made before encoding).
1199 * @return OK_RC once the first packet is on the wire, ERROR_RC on failure.
1200 -*/
1201int radcli_transport_send_async(rc_handle *rh, int slot, char *server_name, unsigned short svc_port,
1202 char secret[MAX_SECRET_LENGTH + 1], rc_type type,
1203 const uint8_t *send_buf, int send_len,
1204 int timeout, int retries,
1205 struct radcli_async_send_st *out)
1206{
1207 struct addrinfo *auth_addr = NULL;
1208 const rc_sockets_override *sfuncs;
1209 struct sockaddr_storage our_sockaddr;
1210 unsigned discover_local_ip;
1211 struct radcli_reqreg *reg;
1212 struct radcli_reqreg_slot *rslot;
1213 char *ns = NULL;
1214 int ns_def_hdl = 0;
1215 int sockfd = -1;
1216 int is_radsec;
1217 int result;
1218
1219 memset(out, 0, sizeof(*out));
1220
1221 if (rh == NULL || rh->reqreg == NULL || slot < 0 || slot >= RADCLI_CTX_MAX_INFLIGHT)
1222 return ERROR_RC;
1223 reg = rh->reqreg;
1224 rslot = &reg->slots[slot];
1225 is_radsec = (rh->so_type == RC_SOCKET_TLS || rh->so_type == RC_SOCKET_DTLS);
1226
1227 if (server_name == NULL || server_name[0] == '\0')
1228 return ERROR_RC;
1229 if (send_len < AUTH_HDR_LEN || (size_t)send_len > sizeof(rslot->send_buf))
1230 return ERROR_RC;
1231
1232 ns = rc_conf_str_id(rh, OPT_NAMESPACE);
1233 if (ns != NULL) {
1234 if (-1 == rc_set_netns(ns, &ns_def_hdl)) {
1235 rc_log(LOG_ERR, "%s: namespace %s set failed", __func__, ns);
1236 return ERROR_RC;
1237 }
1238 }
1239
1240 if (radcli2_priv_find_server_addr(rh, server_name, &auth_addr, secret, type) != 0) {
1241 rc_log(LOG_ERR, "%s: unable to find server: %s", __func__, server_name);
1242 result = ERROR_RC;
1243 goto exit_error;
1244 }
1245
1246 sfuncs = &rh->so;
1247
1248 if (sfuncs->static_secret)
1249 strlcpy(secret, sfuncs->static_secret, MAX_SECRET_LENGTH + 1);
1250
1251 /* Locked only around this one send, not the whole exchange: REQ-NET2-
1252 * SEND-016's shared socket/session must let every other concurrently
1253 * in-flight slot make its own progress independently -- unlike the
1254 * pre-registry design, which held sfuncs->lock() from here through
1255 * service_async()'s terminal result, serializing an entire
1256 * multi-round-trip exchange (harmless when each exchange had its own
1257 * socket, but would now block every other slot on a shared one). */
1258 if (sfuncs->lock) {
1259 if (sfuncs->lock(sfuncs->ptr) != 0) {
1260 rc_log(LOG_ERR, "%s: lock error", __func__);
1261 result = ERROR_RC;
1262 goto fail_unlocked;
1263 }
1264 }
1265
1266 if (svc_port) {
1267 if (auth_addr->ai_family == AF_INET)
1268 ((struct sockaddr_in *)auth_addr->ai_addr)->sin_port = htons(svc_port);
1269 else
1270 ((struct sockaddr_in6 *)auth_addr->ai_addr)->sin6_port = htons(svc_port);
1271 }
1272
1273 if (is_radsec) {
1274 sockfd = sfuncs->get_active_fd ? sfuncs->get_active_fd(sfuncs->ptr) : -1;
1275 if (sockfd < 0) {
1276 rc_log(LOG_ERR, "%s: no established RadSec session", __func__);
1277 result = ERROR_RC;
1278 goto fail;
1279 }
1280 } else if (rh->req_fd != -1) {
1281 sockfd = rh->req_fd;
1282 } else {
1283 rc_own_bind_addr(rh, &our_sockaddr);
1284 discover_local_ip = 0;
1285 if (our_sockaddr.ss_family == AF_INET &&
1286 ((struct sockaddr_in *)(&our_sockaddr))->sin_addr.s_addr == INADDR_ANY)
1287 discover_local_ip = 1;
1288
1289 if (discover_local_ip) {
1290 /* REQ-NET2-SEND-016: this fixes the source address at
1291 * first use (from whichever destination happens to
1292 * trigger it), unlike the old per-request socket,
1293 * which rediscovered it per destination -- acceptable
1294 * on a single-homed host; a multi-homed one with no
1295 * explicit bindaddr may get a suboptimal source
1296 * address for a later request to a different
1297 * destination (e.g. acctserver after authserver).
1298 * Configure bindaddr explicitly to avoid this. */
1299 result = radcli2_priv_get_srcaddr(SA(&our_sockaddr), auth_addr->ai_addr);
1300 if (result != OK_RC) {
1301 rc_log(LOG_ERR, "%s: cannot figure our own address", __func__);
1302 result = ERROR_RC;
1303 goto fail;
1304 }
1305 }
1306
1307 if (sfuncs->get_fd) {
1308 sockfd = sfuncs->get_fd(sfuncs->ptr, SA(&our_sockaddr));
1309 if (sockfd < 0) {
1310 rc_log(LOG_ERR, "%s: socket: %s", __func__, strerror(errno));
1311 result = ERROR_RC;
1312 goto fail;
1313 }
1314 /* REQ-NET2-SEND-016: radcli2_priv_reqreg_drain() loops
1315 * recvfrom() on this socket until EAGAIN -- a blocking
1316 * socket would hang the caller's entire event loop on
1317 * the last, empty call instead of returning promptly. */
1318 if (radcli2_priv_set_nonblock_cloexec(sockfd) != 0) {
1319 rc_log(LOG_ERR, "%s: fcntl: %s", __func__, strerror(errno));
1320 result = ERROR_RC;
1321 if (sfuncs->close_fd)
1322 sfuncs->close_fd(sockfd);
1323 goto fail;
1324 }
1325 /* Commit immediately, not at the end of this branch: any
1326 * later step failing (the IPv6 setsockopt below) must
1327 * still leave a good, already-nonblocking socket in
1328 * rh->req_fd for the next call to reuse, rather than
1329 * leaking a freshly opened one that fail: below
1330 * deliberately never closes (it may be an *already*
1331 * persistent rh->req_fd from a previous call, which
1332 * must never be closed just because this one send
1333 * failed). */
1334 rh->req_fd = sockfd;
1335 }
1336
1337 if (sockfd >= 0 && our_sockaddr.ss_family == AF_INET6) {
1338 char *non_temp_addr = rc_conf_str_id(rh, OPT_USE_PUBLIC_ADDR);
1339 if (non_temp_addr && strcasecmp(non_temp_addr, "true") == 0) {
1340#if defined(__linux__)
1341 int sock_opt = IPV6_PREFER_SRC_PUBLIC;
1342 if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADDR_PREFERENCES,
1343 &sock_opt, sizeof(sock_opt)) != 0) {
1344 rc_log(LOG_ERR, "%s: setsockopt: %s", __func__, strerror(errno));
1345 result = ERROR_RC;
1346 goto fail;
1347 }
1348#elif defined(BSD) || defined(__APPLE__)
1349 int sock_opt = 0;
1350 if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_PREFER_TEMPADDR,
1351 &sock_opt, sizeof(sock_opt)) != 0) {
1352 rc_log(LOG_ERR, "%s: setsockopt: %s", __func__, strerror(errno));
1353 result = ERROR_RC;
1354 goto fail;
1355 }
1356#else
1357 rc_log(LOG_INFO, "%s: Usage of non-temporary IPv6 address is not "
1358 "supported in this system", __func__);
1359#endif
1360 }
1361 }
1362 }
1363
1364 do {
1365 result = sfuncs->sendto(sfuncs->ptr, sockfd, (const char *)send_buf,
1366 (unsigned int)send_len, 0,
1367 SA(auth_addr->ai_addr), auth_addr->ai_addrlen);
1368 } while (result == -1 && errno == EINTR);
1369 if (result == -1) {
1370 rc_log(LOG_ERR, "%s: sendto: %s", __func__, strerror(errno));
1371 result = ERROR_RC;
1372 goto fail;
1373 }
1374
1375 pthread_mutex_lock(&reg->lock);
1376 memcpy(&rslot->peer, auth_addr->ai_addr, auth_addr->ai_addrlen);
1377 rslot->peer_len = auth_addr->ai_addrlen;
1378 memcpy(rslot->send_buf, send_buf, (size_t)send_len);
1379 rslot->send_len = send_len;
1380 memcpy(rslot->vector, send_buf + 4, AUTH_VECTOR_LEN); /* AUTH_HDR: code(1) id(1) length(2) vector(16) */
1381 strlcpy(rslot->secret, secret, sizeof(rslot->secret));
1382 strlcpy(rslot->server_name, server_name, sizeof(rslot->server_name));
1383 rslot->svc_port = svc_port;
1384 rslot->type = type;
1385 rslot->timeout = timeout > 0 ? timeout : 1;
1386 rslot->retries_left = retries;
1387 rslot->deadline = rc_getmtime() + rslot->timeout;
1388 rslot->armed = 1;
1389 pthread_mutex_unlock(&reg->lock);
1390
1391 if (sfuncs->unlock)
1392 sfuncs->unlock(sfuncs->ptr);
1393
1394 out->rh = rh;
1395 out->active = 1;
1396 out->slot = slot;
1397 out->delivered = 0;
1398
1399 result = OK_RC;
1400 goto exit_ok;
1401
1402 fail:
1403 if (sfuncs->unlock)
1404 sfuncs->unlock(sfuncs->ptr);
1405 fail_unlocked:
1406 memset(secret, '\0', MAX_SECRET_LENGTH + 1);
1407 exit_ok:
1408 if (auth_addr)
1409 freeaddrinfo(auth_addr);
1410 exit_error:
1411 if (ns != NULL) {
1412 if (-1 == rc_reset_netns(&ns_def_hdl))
1413 rc_log(LOG_ERR, "%s: namespace %s reset failed", __func__, ns);
1414 }
1415 return result;
1416}
1417
1418/*- Advance one async exchange by one non-blocking step: drains every ready
1419 * datagram on ctx's shared request socket/session (delivering each to
1420 * whichever exchange's slot it actually resolves, not necessarily st's
1421 * own), then services every registry slot whose retransmit/timeout deadline
1422 * has passed (again, not just st's), then reports st's own outcome.
1423 *
1424 * Call after the caller's poll()/select() reports ctx's fd ready (fd_ready
1425 * nonzero), or after it returns with the fd not ready because
1426 * radcli_ctx_get_poll()'s timeout_ms elapsed instead (fd_ready zero).
1427 *
1428 * On a validated reply, decodes it via the same logic
1429 * radcli_transport_exchange() uses (RFC 2865 Response Authenticator, RFC
1430 * 2869/Blast-RADIUS Message-Authenticator), storing the outcome directly on
1431 * st (see struct radcli_async_send_st's own doc comment) rather than
1432 * returning raw bytes -- decoding happens once, inside the drain, for
1433 * whichever exchange a given datagram actually resolves, not necessarily
1434 * the one whose service_async() call triggered the drain.
1435 *
1436 * @param st state from a successful radcli_transport_send_async().
1437 * @param fd_ready nonzero if the caller's poll()/select() reported ctx's fd
1438 * ready.
1439 * @return RADCLI_ASYNC_AGAIN if still waiting, or whatever
1440 * radcli_transport_exchange() itself would return for a terminal outcome
1441 * (OK_RC/REJECT_RC/CHALLENGE_RC/TIMEOUT_RC/ERROR_RC).
1442 -*/
1443int radcli_transport_service_async(struct radcli_async_send_st *st, int fd_ready)
1444{
1445 if (st == NULL || !st->active)
1446 return ERROR_RC;
1447
1448 if (fd_ready)
1449 radcli2_priv_reqreg_drain(st->rh);
1450 if (!st->delivered)
1451 radcli2_priv_reqreg_service_timeouts(st->rh);
1452 if (!st->delivered)
1453 return RADCLI_ASYNC_AGAIN;
1454
1455 st->active = 0;
1456 return st->result;
1457}
1458
1459/*- Release st's registry slot without waiting for a terminal result. A
1460 * no-op if st is not active (never sent, or already terminal/delivered).
1461 * Used by lib/request.c's radcli_request_free() for the fire-and-forget
1462 * case: a RADCLI_REQUEST_SENDONLY request whose caller never drove it to a
1463 * terminal result via radcli_ctx_dispatch()/radcli_request_done(). If st
1464 * was already delivered but never read via radcli_request_done(), frees
1465 * st->reply_attrs instead (the slot is already vacated by then --
1466 * REQ-NET2-SEND-016 vacates on delivery, not on collection). -*/
1467void radcli_transport_async_abort(struct radcli_async_send_st *st)
1468{
1469 if (st == NULL || !st->active)
1470 return;
1471
1472 if (!st->delivered)
1473 radcli2_priv_reqreg_release(st->rh, st->slot);
1474 else
1475 radcli_avp_list_free(st->reply_attrs);
1476 st->active = 0;
1477}
1478
rc_type
Definition radcli.h:81
struct rc_aaa_ctx_st RC_AAA_CTX
Definition radcli.h:295
@ ACCT
Request for accounting server.
Definition radcli.h:83
@ AUTH
Request for authentication server.
Definition radcli.h:82
@ RC_SOCKET_DTLS
DTLS socket.
Definition radcli.h:115
@ RC_SOCKET_TLS
TLS socket.
Definition radcli.h:114