Radcli library 2.0.0
A simple radius library -- legacy API reference
Loading...
Searching...
No Matches
send.c
1/*
2 * Copyright (C) 1995,1996,1997 Lars Fenneberg
3 * Copyright (C) 2015,2016,2026 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/* rc_pack_list()/rc_send_server()/rc_send_server_ctx() (legacy,
15 * VALUE_PAIR-based): split out of lib/sendserver.c because they call
16 * lib/legacy/avpair.c's rc_avpair_*() directly, unlike
17 * radcli_transport_exchange() and its helpers (still in lib/sendserver.c),
18 * which are VALUE_PAIR-free and shared with lib/request.c's new-API path.
19 */
20
21#include <includes.h>
22#include <radcli/radcli.h>
23#include <poll.h>
24#include "dict2.h"
25#include "util.h"
26#include "avp.h"
27#include "rc-crypto.h"
28#include "rc-random.h"
29
30#if defined(HAVE_GNUTLS)
31# include <gnutls/gnutls.h>
32# include <gnutls/crypto.h>
33#endif
34
35#if defined(__linux__)
36#include <linux/in6.h>
37#endif
38
39/* Resets fd to -1 after closing so a later unconditional cleanup (e.g. the
40 * shared `cleanup:` label's `if (sockfd >= 0) SCLOSE(sockfd)`) cannot close
41 * the same descriptor a second time. */
42#define SCLOSE(fd) do { if (sfuncs->close_fd) sfuncs->close_fd(fd); (fd) = -1; } while (0)
43
50
51/* No longer called by rc_send_server_ctx(), which builds its packet via
52 * radcli_value_pairs_to_avp_list() + radcli_avp_encode() (lib/avp.c)
53 * instead -- one encoder implementing the encrypt=N whitelist and the
54 * wire format, not two. Kept, and still tested directly (tests/pack.c),
55 * as the documented behaviour of an already-shipped internal symbol. */
56/*- Pack an attribute value pair list into a buffer.
57 *
58 * @param rh a handle to parsed configuration (used for the dictionary
59 * encrypt=N lookup that refuses an attribute this function cannot
60 * encrypt -- see the check at the top of the loop below).
61 * @param vp a pointer to a VALUE_PAIR.
62 * @param secret the secret used by the server.
63 * @param auth a pointer to AUTH_HDR.
64 * @param max_len maximum total packet length in bytes (header + attributes);
65 * callers must subtract any bytes appended after this call (e.g. 18
66 * bytes for Message-Authenticator on auth requests).
67 * @return The number of octets packed, or -1 if any attribute value exceeds
68 * 253 bytes, the packet would exceed max_len, or an attribute
69 * requires encryption this function does not implement (any
70 * dictionary encrypt=N flag other than User-Password's own
71 * RFC 2865 SS5.2 handling below).
72 -*/
73int rc_pack_list(rc_handle *rh, VALUE_PAIR * vp, char *secret, AUTH_HDR * auth, int max_len)
74{
75 int length, i, pc, padded_length;
76 size_t secretlen;
77 uint32_t lvalue, vendor;
78 unsigned char passbuf[RC_MAX(AUTH_PASS_LEN, CHAP_VALUE_LENGTH)];
79 unsigned char md5buf[MAX_SECRET_LENGTH + AUTH_VECTOR_LEN];
80 unsigned char *vector;
81 pkt_buf pb;
82 uint8_t *attr_start, *attr_len_ptr, *vsa_len_ptr;
83
84 /* head = start of RADIUS packet; tail starts after the fixed header;
85 * pb_written() will return the total packet length (header + attrs). */
86 pb.head = (uint8_t *)auth;
87 pb.data = (uint8_t *)auth;
88 pb.tail = auth->data;
89 pb.end = (uint8_t *)auth + max_len;
90
91 while (vp != NULL) {
92 vsa_len_ptr = NULL;
93 unsigned max_vlen = AUTH_STRING_LEN; /* 253: RFC 2865 per-attribute value limit */
94
95 /* PW_USER_PASSWORD has its own encryption below (RFC 2865 SS5.2);
96 * every other attribute the dictionary flags "encrypt=N" -- today
97 * that means Tunnel-Password and the two MS-MPPE-*-Key VSAs,
98 * encrypt=2, RFC 2868 SS3.5 salt-encryption -- has no
99 * implementation in this function, and MUST NOT fall through to
100 * the plain string/integer encoding below: doing so would send
101 * the attribute's real value on the wire completely unencrypted.
102 * A whitelist, not a blocklist: refusal is the default for any
103 * flagged attribute this function does not specifically know how
104 * to encrypt, including one a future dictionary change adds. */
105 if (vp->attribute != PW_USER_PASSWORD) {
106 struct radcli_dict_attr *def = radcli_dict_attr_by_id(rh, vp->attribute);
107 struct radcli_dict_flags *fl = def != NULL ? radcli_dict_flags_by_id(rh, def->value) : NULL;
108
109 if (fl != NULL && fl->encrypt_type != 0) {
110 rc_log(LOG_ERR, "rc_pack_list: %s requires encryption this "
111 "function does not implement; refusing to send it "
112 "unencrypted", def->name);
113 return -1;
114 }
115 }
116
117 if (VENDOR(vp->attribute) != 0) {
118 max_vlen = AUTH_STRING_LEN - VSA_HDR_LEN; /* 247: VSA envelope consumes 6 bytes */
119 if (pb_put_byte(&pb, PW_VENDOR_SPECIFIC) < 0) goto too_large;
120 vsa_len_ptr = pb.tail;
121 if (pb_put_byte(&pb, 6) < 0) goto too_large;
122 vendor = htonl(VENDOR(vp->attribute));
123 if (pb_put_bytes(&pb, &vendor, sizeof(uint32_t)) < 0) goto too_large;
124 }
125
126 attr_start = pb.tail;
127 if (pb_put_byte(&pb, vp->attribute & 0xff) < 0) goto too_large;
128 attr_len_ptr = pb.tail;
129 if (pb_put_byte(&pb, 2) < 0) goto too_large; /* placeholder; patched below */
130
131 switch (vp->attribute) {
132 case PW_USER_PASSWORD:
133 length = vp->lvalue;
134 if (length > AUTH_PASS_LEN)
135 length = AUTH_PASS_LEN;
136 padded_length =
137 (length + (AUTH_VECTOR_LEN - 1)) & ~(AUTH_VECTOR_LEN - 1);
138
139 if (pb.tail + padded_length > pb.end) goto too_large;
140
141 /* Pad the password with zeros */
142 memset((char *)passbuf, '\0', AUTH_PASS_LEN);
143 memcpy((char *)passbuf, vp->strvalue, (size_t) length);
144
145 secretlen = rc_secret_len(secret);
146 vector = (unsigned char *)auth->vector;
147 for (i = 0; i < padded_length; i += AUTH_VECTOR_LEN) {
148 /* Build hash input: secret || vector */
149 memcpy(md5buf, secret, secretlen);
150 memcpy(md5buf + secretlen, vector, AUTH_VECTOR_LEN);
151 rc_md5_calc(pb.tail, md5buf, secretlen + AUTH_VECTOR_LEN);
152
153 /* Remember the start of the digest */
154 vector = pb.tail;
155
156 /* Xor the password into the MD5 digest */
157 for (pc = i; pc < (i + AUTH_VECTOR_LEN); pc++)
158 *pb.tail++ ^= passbuf[pc];
159 }
160 break;
161
162 default:
163 switch (vp->type) {
164 case PW_TYPE_STRING:
166 if (vp->lvalue > max_vlen) goto too_large;
167 if (pb_put_bytes(&pb, vp->strvalue, (int)vp->lvalue) < 0)
168 goto too_large;
169 break;
170
171 case PW_TYPE_IPV6ADDR:
172 if (pb_put_bytes(&pb, vp->strvalue, 16) < 0)
173 goto too_large;
174 break;
175
176 case PW_TYPE_INTEGER:
177 case PW_TYPE_IPADDR:
178 case PW_TYPE_DATE:
179 lvalue = htonl(vp->lvalue);
180 if (pb_put_bytes(&pb, &lvalue, sizeof(uint32_t)) < 0)
181 goto too_large;
182 break;
183
184 default:
185 break;
186 }
187 break;
188 }
189
190 /* Patch back lengths: attr_len = type(1) + len(1) + value */
191 *attr_len_ptr = (uint8_t)(pb.tail - attr_start);
192 if (vsa_len_ptr != NULL)
193 *vsa_len_ptr += *attr_len_ptr;
194
195 vp = vp->next;
196 }
197 return (int)pb_written(&pb); /* total packet bytes: AUTH_HDR_LEN + attrs */
198
199too_large:
200 rc_log(LOG_ERR, "rc_pack_list: attribute value too large or packet would exceed %d bytes", max_len);
201 return -1;
202}
203
204/*- Append a string to the provided buffer.
205 *
206 * @param dest the destination buffer.
207 * @param max_size the maximum size available in the destination buffer.
208 * @param pos the current position in the dest buffer; initially must be zero.
209 * @param src the source buffer to append.
210 -*/
211static void strappend(char *dest, unsigned max_size, int *pos, const char *src)
212{
213 unsigned len = strlen(src) + 1;
214
215 if (*pos == -1)
216 return;
217
218 if (len + *pos > max_size) {
219 *pos = -1;
220 return;
221 }
222
223 memcpy(&dest[*pos], src, len);
224 *pos += len - 1;
225 return;
226}
227
238int rc_send_server(rc_handle * rh, SEND_DATA * data, char *msg, rc_type type)
239{
240 return rc_send_server_ctx(rh, NULL, data, msg, type, 0);
241}
242
243/*- Send a request to a RADIUS server and wait for the reply.
244 *
245 * The send/retry/receive step, including DNS-resolved address-level
246 * failover, is delegated to radcli_transport_exchange(); this function
247 * keeps only the VALUE_PAIR-specific work: management-secret detection,
248 * NAS-IP-Address/NAS-Identifier auto-injection, encoding send_pairs into
249 * the packet (rc_pack_list()), and decoding the reply back into
250 * receive_pairs/msg (rc_avpair_gen()).
251 *
252 * @param rh a handle to parsed configuration.
253 * @param ctx if non-NULL, receives the context of the sent request; release with rc_aaa_ctx_free().
254 * @param data a pointer to a SEND_DATA structure.
255 * @param msg must be an array of PW_MAX_MSG_SIZE or NULL; will contain the concatenation of
256 * any PW_REPLY_MESSAGE received.
257 * @param type must be AUTH or ACCT.
258 * @param no_wait if non-zero, the request is transmitted once and this
259 * function returns OK_RC immediately without waiting for or expecting a
260 * reply; data->timeout and data->retries are not consulted in that
261 * case. Used by rc_acct_async() for best-effort, non-blocking
262 * notifications. TIMEOUT_RC is never returned when no_wait is set.
263 * @return OK_RC (0) on success, CHALLENGE_RC when an Access-Challenge
264 * response is received, TIMEOUT_RC on timeout, REJECT_RC on access reject,
265 * or negative on failure as return value.
266 -*/
267int rc_send_server_ctx(rc_handle * rh, RC_AAA_CTX ** ctx, SEND_DATA * data,
268 char *msg, rc_type type, int no_wait)
269{
270 AUTH_HDR *auth;
271 char *server_name, *p;
272 struct sockaddr_storage our_sockaddr;
273 struct addrinfo *auth_addr = NULL;
274 int result = 0;
275 int total_length;
276 int length, pos;
277 const rc_sockets_override *sfuncs;
278 unsigned discover_local_ip;
279 size_t secretlen;
280 char secret[MAX_SECRET_LENGTH + 1];
281 unsigned char vector[AUTH_VECTOR_LEN];
282 uint8_t recv_buffer[RC_BUFFER_LEN];
283 uint8_t send_buffer[RC_BUFFER_LEN];
284 uint16_t tlen;
285 size_t recv_len = 0;
286 VALUE_PAIR *vp;
287 struct sockaddr_storage *ss_set = NULL;
288 int mgmt_secret = 0;
289 radcli_avp_list *avp_list;
290 int encoded_len;
291
292 server_name = data->server;
293 if (server_name == NULL || server_name[0] == '\0')
294 return ERROR_RC;
295
296 if ((vp = rc_avpair_get(data->send_pairs, PW_SERVICE_TYPE, 0)) &&
297 (vp->lvalue == PW_ADMINISTRATIVE)) {
298 mgmt_secret = 1;
299 strlcpy(secret, MGMT_POLL_SECRET, sizeof(secret));
300 auth_addr =
301 rc_getaddrinfo(server_name,
302 type == AUTH ? PW_AI_AUTH : PW_AI_ACCT);
303 if (auth_addr == NULL)
304 return ERROR_RC;
305 } else {
306 if (data->secret != NULL) {
307 strlcpy(secret, data->secret, sizeof(secret));
308 }
309 if (radcli2_priv_find_server_addr
310 (rh, server_name, &auth_addr, secret, type) != 0) {
311 rc_log(LOG_ERR,
312 "rc_send_server: unable to find server: %s",
313 server_name);
314 return ERROR_RC;
315 }
316 }
317
318 sfuncs = &rh->so;
319
320 if (sfuncs->static_secret) {
321 /* any static secret set in sfuncs overrides the configured */
322 strlcpy(secret, sfuncs->static_secret, sizeof(secret));
323 }
324
325 /* Discover our own source address, used below to fill in
326 * NAS-IP-Address/NAS-IPv6-Address when the caller didn't set one;
327 * radcli_transport_exchange() re-does this per address it actually
328 * sends from, so this is only for the auto-injected attribute's
329 * value, not for building a socket. */
330 rc_own_bind_addr(rh, &our_sockaddr);
331 discover_local_ip = 0;
332 if (our_sockaddr.ss_family == AF_INET) {
333 if (((struct sockaddr_in *)(&our_sockaddr))->sin_addr.s_addr ==
334 INADDR_ANY) {
335 discover_local_ip = 1;
336 }
337 }
338
339 if (discover_local_ip) {
340 result = rc_get_srcaddr(SA(&our_sockaddr), auth_addr->ai_addr);
341 if (result != OK_RC) {
342 memset(secret, '\0', sizeof(secret));
343 rc_log(LOG_ERR,
344 "rc_send_server: cannot figure our own address");
345 freeaddrinfo(auth_addr);
346 return result;
347 }
348 }
349
350 freeaddrinfo(auth_addr);
351 auth_addr = NULL;
352
353 /*
354 * Fill in NAS-IP-Address (if needed)
355 */
356 if (rh->nas_addr_set) {
357 rc_avpair_remove(&(data->send_pairs), PW_NAS_IP_ADDRESS, 0);
358 rc_avpair_remove(&(data->send_pairs), PW_NAS_IPV6_ADDRESS, 0);
359
360 ss_set = &rh->nas_addr;
361 } else if (rc_avpair_get(data->send_pairs, PW_NAS_IP_ADDRESS, 0) == NULL &&
362 rc_avpair_get(data->send_pairs, PW_NAS_IPV6_ADDRESS, 0) == NULL) {
363
364 ss_set = &our_sockaddr;
365 }
366
367 if (ss_set) {
368 if (ss_set->ss_family == AF_INET) {
369 uint32_t ip;
370 ip = *((uint32_t
371 *) (&((struct sockaddr_in *)ss_set)->
372 sin_addr));
373 ip = ntohl(ip);
374
375 rc_avpair_add(rh, &(data->send_pairs),
376 PW_NAS_IP_ADDRESS, &ip, 0, 0);
377 } else {
378 void *p2;
379 p2 = &((struct sockaddr_in6 *)ss_set)->sin6_addr;
380
381 rc_avpair_add(rh, &(data->send_pairs),
382 PW_NAS_IPV6_ADDRESS, p2, 16, 0);
383 }
384 }
385
386 /*
387 * Fill in NAS-Identifier (if needed)
388 */
389 p = rc_conf_str(rh, "nas-identifier");
390 if (p != NULL) {
391 rc_avpair_remove(&(data->send_pairs), PW_NAS_IDENTIFIER, 0);
392 rc_avpair_add(rh, &(data->send_pairs),
393 PW_NAS_IDENTIFIER, p, -1, 0);
394 }
395
396 /* Build a request. Encodes via the same radcli_avp_encode()
397 * the new API uses -- converting data->send_pairs to a
398 * radcli_avp_list first -- rather than rc_pack_list(), so there is
399 * one encoder implementing the encrypt=N whitelist and the wire
400 * format, not two. The one caller-visible difference from
401 * rc_pack_list(): per RFC 2865 SS5.2, an over-length User-Password
402 * (> AUTH_PASS_LEN, 128 octets) is now rejected rather than silently
403 * truncated to a different, shorter password the caller did not ask
404 * to send -- a deliberate divergence, not an oversight (see
405 * radcli_avp_encode()'s own comment, lib/avp.c). */
406 if (radcli_value_pairs_to_avp_list(rh, data->send_pairs, &avp_list) != 0) {
407 memset(secret, '\0', sizeof(secret));
408 return ERROR_RC;
409 }
410
411 auth = (AUTH_HDR *) send_buffer;
412 auth->code = data->code;
413 auth->id = data->seq_nbr;
414
415 if (data->code == PW_ACCOUNTING_REQUEST) {
416 encoded_len = radcli_avp_encode(rh, avp_list, secret, auth->vector,
417 auth->data, RC_MAX_PACKET_LEN - AUTH_HDR_LEN, NULL);
418 radcli_avp_list_free(avp_list);
419 if (encoded_len < 0) {
420 memset(secret, '\0', sizeof(secret));
421 return ERROR_RC;
422 }
423 total_length = AUTH_HDR_LEN + encoded_len;
424
425 tlen = htons((unsigned short)total_length);
426 memcpy(&auth->length, &tlen, sizeof(uint16_t));
427
428 memset((char *)auth->vector, 0, AUTH_VECTOR_LEN);
429 secretlen = rc_secret_len(secret);
430 memcpy((char *)auth + total_length, secret, secretlen);
431 rc_md5_calc(vector, (unsigned char *)auth,
432 total_length + secretlen);
433 memcpy((char *)auth->vector, (char *)vector, AUTH_VECTOR_LEN);
434 } else {
435 rc_get_random_bytes(vector, AUTH_VECTOR_LEN);
436 memcpy((char *)auth->vector, (char *)vector, AUTH_VECTOR_LEN);
437
438 /* Leave 2+MD5_DIGEST_SIZE bytes for Message-Authenticator (added below) */
439 encoded_len = radcli_avp_encode(rh, avp_list, secret, vector, auth->data,
440 RC_MAX_PACKET_LEN - AUTH_HDR_LEN - (2 + MD5_DIGEST_SIZE), NULL);
441 radcli_avp_list_free(avp_list);
442 if (encoded_len < 0) {
443 memset(secret, '\0', sizeof(secret));
444 return ERROR_RC;
445 }
446 total_length = AUTH_HDR_LEN + encoded_len;
447
448 total_length = add_msg_auth_attr(rh, secret, auth, total_length);
449
450 auth->length = htons((unsigned short)total_length);
451 }
452
453 result = radcli_transport_exchange(rh, ctx, server_name,
454 (unsigned short)data->svc_port,
455 secret, mgmt_secret,
456 data->timeout, data->retries, no_wait, type,
457 send_buffer, total_length,
458 recv_buffer, sizeof(recv_buffer), &recv_len, NULL);
459
460 /* radcli_transport_exchange() no longer scrubs secret itself (it
461 * cannot know when a caller is truly done with it -- see its own doc
462 * comment); nothing below this point uses secret again (rc_avpair_gen()
463 * takes no secret parameter), so this is the correct, single place to
464 * clear it for every return path from here on. */
465 memset(secret, '\0', sizeof(secret));
466
467 if (no_wait ||
468 (result != OK_RC && result != CHALLENGE_RC &&
469 result != REJECT_RC && result != BADRESP_RC)) {
470 return result;
471 }
472
473 length = (int)recv_len;
474 if (length > 0) {
475 data->receive_pairs = rc_avpair_gen(rh, NULL, recv_buffer,
476 length, 0);
477 } else {
478 data->receive_pairs = NULL;
479 }
480
481 if (msg) {
482 *msg = '\0';
483 pos = 0;
484 vp = data->receive_pairs;
485 while (vp) {
486 if ((vp = rc_avpair_get(vp, PW_REPLY_MESSAGE, 0))) {
487 strappend(msg, PW_MAX_MSG_SIZE, &pos,
488 vp->strvalue);
489 strappend(msg, PW_MAX_MSG_SIZE, &pos, "\n");
490 vp = vp->next;
491 }
492 }
493 }
494
495 return result;
496}
497
void rc_avpair_remove(VALUE_PAIR **list, uint32_t attrid, uint32_t vendorspec)
Removes an attribute-value pair from the given list.
Definition avpair.c:70
rc_type
Definition radcli.h:81
struct rc_aaa_ctx_st RC_AAA_CTX
Definition radcli.h:295
VALUE_PAIR * rc_avpair_gen(rc_handle const *rh, VALUE_PAIR *pair, unsigned char const *ptr, int length, uint32_t vendorspec)
Decode a raw RADIUS attribute buffer into a VALUE_PAIR list.
Definition avpair.c:444
int rc_send_server(rc_handle *rh, SEND_DATA *data, char *msg, rc_type type)
Sends a request to a RADIUS server and waits for the reply.
Definition send.c:238
VALUE_PAIR * rc_avpair_add(rc_handle const *rh, VALUE_PAIR **list, uint32_t attrid, void const *pval, int len, uint32_t vendorspec)
Adds an attribute-value pair to the given list.
Definition avpair.c:47
VALUE_PAIR * rc_avpair_get(VALUE_PAIR *vp, uint32_t attrid, uint32_t vendorspec)
Find the first attribute value-pair (which matches the given attribute) from the specified value-pair...
Definition avpair.c:465
@ AUTH
Request for authentication server.
Definition radcli.h:82
@ PW_TYPE_IPADDR
The attribute is an IPv4 address in host-byte order.
Definition radcli.h:130
@ PW_TYPE_IPV6ADDR
The attribute is an 128-bit IPv6 address.
Definition radcli.h:132
@ PW_TYPE_IPV6PREFIX
The attribute is an IPv6 prefix; the lvalue will indicate its size.
Definition radcli.h:133
@ PW_TYPE_INTEGER
The attribute is a 32-bit integer.
Definition radcli.h:129
@ PW_TYPE_DATE
The attribute contains a 32-bit number indicating the seconds since epoch.
Definition radcli.h:131
@ PW_TYPE_STRING
The attribute is a printable string.
Definition radcli.h:128
rc_attr_type type
attribute type.
Definition radcli.h:258
uint64_t attribute
attribute numeric value of type rc_attr_id including vendor; use VENDOR() and ATTRID() to separate.
Definition radcli.h:257
uint32_t lvalue
attribute value if type is PW_TYPE_INTEGER, PW_TYPE_DATE or PW_TYPE_IPADDR.
Definition radcli.h:259
char strvalue[AUTH_STRING_LEN+1]
contains attribute value in other cases.
Definition radcli.h:260
int timeout
Session timeout in seconds.
Definition radcli.h:277
char * secret
Shared secret of RADIUS server.
Definition radcli.h:276
uint8_t seq_nbr
Packet sequence number.
Definition radcli.h:273
int svc_port
RADIUS protocol destination port.
Definition radcli.h:275
char * server
Name/address of RADIUS server.
Definition radcli.h:274
VALUE_PAIR * send_pairs
More a/v pairs to send.
Definition radcli.h:279
VALUE_PAIR * receive_pairs
Where to place received a/v pairs.
Definition radcli.h:280
uint8_t code
RADIUS packet code.
Definition radcli.h:272