The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_dpsk.c
Go to the documentation of this file.
1/*
2 * Copyright (C) 2025 Network RADIUS SARL (legal@networkradius.com)
3 *
4 * This software may not be redistributed in any form without the prior
5 * written consent of Network RADIUS.
6 *
7 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
8 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
9 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
10 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
11 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
12 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
13 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
14 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
15 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
16 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
17 * SUCH DAMAGE.
18 */
19
20/**
21 * $Id: 22915e17205d86fdb475b81c02c33c00dc84b73f $
22 * @file rlm_dpsk.c
23 * @brief Dynamic PSK for WiFi
24 *
25 * @copyright 2025 Network RADIUS SAS (legal@networkradius.com)
26 */
27RCSID("$Id: 22915e17205d86fdb475b81c02c33c00dc84b73f $")
28
29#include <freeradius-devel/server/base.h>
30#include <freeradius-devel/server/module_rlm.h>
31#include <freeradius-devel/server/tmpl_dcursor.h>
32#include <freeradius-devel/unlang/xlat_func.h>
33#include <freeradius-devel/util/base16.h>
34#include <freeradius-devel/util/rb.h>
35
36#include <openssl/ssl.h>
37#include <openssl/evp.h>
38#include <openssl/hmac.h>
39
40#include <ctype.h>
41
42/*
43 Header: 02030075
44
45 descriptor 02
46 information 010a
47 length 0010
48 replay counter 000000000000001
49 snonce c3bb319516614aacfb44e933bf1671131fb1856e5b2721952d414ce3f5aa312b
50 IV 0000000000000000000000000000000
51 rsc 0000000000000000
52 reserved 0000000000000000
53 mic 35cddcedad0dfb6a12a2eca55c17c323
54 data length 0016
55 data 30140100000fac040100000fac040100000fac028c00
56
57 30
58 14 length of data
59 01 ...
60*/
61
62typedef struct eapol_key_frame_t {
63 uint8_t descriptor; // message number 2
65 uint16_t length; // always 0010, for 16 octers
66 uint8_t replay_counter[8]; // usually "1"
67 uint8_t nonce[32]; // random token
68 uint8_t iv[16]; // zeroes
69 uint8_t rsc[8]; // zeros
70 uint8_t reserved[8]; // zeroes
71 uint8_t mic[16]; // calculated data
72 uint16_t data_len; // various other things we don't need.
73} CC_HINT(__packed__) eapol_key_frame_t;
74
75typedef struct eapol_attr_t {
76 uint8_t header[4]; // 02030075
78} CC_HINT(__packed__) eapol_attr_t;
79
80
81typedef struct rlm_dpsk_s rlm_dpsk_t;
82
83typedef struct {
85 uint8_t mac[6];
86 uint8_t pmk[32];
87
89 size_t ssid_len;
90
91 char *identity;
93
94 char *psk;
95 size_t psk_len;
97
101
108
117
119
122 { .out = &dict_freeradius, .proto = "freeradius" },
124};
125
127
130 { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
131
133};
134
135static const conf_parser_t module_config[] = {
136 { FR_CONF_OFFSET("cache_size", rlm_dpsk_t, cache_size) },
137 { FR_CONF_OFFSET("cache_lifetime", rlm_dpsk_t, cache_lifetime) },
138
140};
141
146
165
166#ifdef WITH_TLS
167static const call_env_method_t dpsk_autz_method_env = {
169 .inst_type = "dpsk_autz_call_env_t",
170 .env = (call_env_parser_t[]) {
172 dpsk_autz_call_env_t, anonce_tmpl), .pair.dflt = "FreeRADIUS-EV5.802_1X-Anonce",
173 .pair.dflt_quote = T_BARE_WORD },
175 dpsk_autz_call_env_t, key_msg_tmpl), .pair.dflt = "FreeRADIUS-EV5.802_1X-EAPoL-Key-Msg",
176 .pair.dflt_quote = T_BARE_WORD },
178 }
179};
180
181static const call_env_method_t dpsk_auth_method_env = {
183 .inst_type = "dpsk_auth_call_env_t",
184 .env = (call_env_parser_t[]) {
187 dpsk_auth_call_env_t, ssid, ssid_tmpl), .pair.dflt = "Called-Station-SSID",
188 .pair.dflt_quote = T_BARE_WORD },
191 dpsk_auth_call_env_t, anonce, anonce_tmpl), .pair.dflt = "FreeRADIUS-EV5.802_1X-Anonce",
192 .pair.dflt_quote = T_BARE_WORD },
195 dpsk_auth_call_env_t, key_msg, key_msg_tmpl), .pair.dflt = "FreeRADIUS-EV5.802_1X-EAPoL-Key-Msg",
196 .pair.dflt_quote = T_BARE_WORD },
198 dpsk_auth_call_env_t, username), .pair.dflt = "User-Name", .pair.dflt_quote = T_BARE_WORD },
200 dpsk_auth_call_env_t, calledstation), .pair.dflt = "Called-Station-MAC", .pair.dflt_quote = T_BARE_WORD },
202 dpsk_auth_call_env_t, masterkey, masterkey_tmpl), .pair.dflt = "control.Pairwise-Master-Key", .pair.dflt_quote = T_BARE_WORD },
204 dpsk_auth_call_env_t, psk, psk_tmpl), .pair.dflt = "control.Pre-Shared-Key", .pair.dflt_quote = T_BARE_WORD },
206 dpsk_auth_call_env_t, psk_identity), .pair.dflt = "control.PSK-Identity", .pair.dflt_quote = T_BARE_WORD },
208 dpsk_auth_call_env_t, psk_dest_tmpl), .pair.dflt = "reply.Pre-Shared-Key", .pair.dflt_quote = T_BARE_WORD },
210 dpsk_auth_call_env_t, psk_identity_dest_tmpl), .pair.dflt = "reply.PSK-Identity", .pair.dflt_quote = T_BARE_WORD },
213 }
214};
215
216/*
217 * mod_authorize() - authorize user if we can authenticate
218 * it later. Add Auth-Type attribute if present in module
219 * configuration (usually Auth-Type must be "DPSK")
220 */
221static unlang_action_t CC_HINT(nonnull) mod_authorize(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
222{
224 dpsk_autz_call_env_t *env = talloc_get_type_abort(mctx->env_data, dpsk_autz_call_env_t);
226 fr_dcursor_t cursor;
227 fr_pair_t *vp;
228
229 vp = tmpl_dcursor_init(NULL, NULL, &cc, &cursor, request, env->anonce_tmpl);
232
233 vp = tmpl_dcursor_init(NULL, NULL, &cc, &cursor, request, env->key_msg_tmpl);
236
237 if (!inst->auth_type) {
238 WARN("No 'authenticate %s {...}' section or 'Auth-Type = %s' set. Cannot setup DPSK authentication.",
239 mctx->mi->name, mctx->mi->name);
241 }
242
244
246}
247
248static rlm_dpsk_cache_t *dpsk_cache_find(request_t *request, rlm_dpsk_t const *inst, uint8_t *buffer, size_t buflen,
249 fr_value_box_t *ssid, uint8_t const *mac)
250{
251 rlm_dpsk_cache_t *entry, my_entry;
252
253 memcpy(my_entry.mac, mac, sizeof(my_entry.mac));
254 memcpy(&my_entry.ssid, &ssid->vb_octets, sizeof(my_entry.ssid)); /* const issues */
255 my_entry.ssid_len = ssid->vb_length;
256
257 entry = fr_rb_find(&inst->mutable->cache, &my_entry);
258 if (entry) {
259 if fr_time_gt(entry->expires, fr_time()) {
260 RDEBUG3("Cache entry found");
261 memcpy(buffer, entry->pmk, buflen);
262 return entry;
263 }
264
265 RDEBUG3("Cache entry has expired");
266 fr_rb_delete(&inst->mutable->cache, entry);
267 }
268
269 return NULL;
270}
271
272
273static int generate_pmk(request_t *request, uint8_t *buffer, size_t buflen, fr_value_box_t *ssid, char const *psk, size_t psk_len)
274{
275 fr_assert(buflen == 32);
276
277 if (PKCS5_PBKDF2_HMAC_SHA1((const char *) psk, psk_len, (const unsigned char *) ssid->vb_strvalue,
278 ssid->vb_length, 4096, buflen, buffer) == 0) {
279 RERROR("Failed calling OpenSSL to calculate the PMK");
280 return -1;
281 }
282
283 return 0;
284}
285
286/*
287 * Verify the DPSK information.
288 */
289static unlang_action_t CC_HINT(nonnull) mod_authenticate(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
290{
291 rlm_dpsk_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_dpsk_t);
292 dpsk_auth_call_env_t *env = talloc_get_type_abort(mctx->env_data, dpsk_auth_call_env_t);
293 rlm_dpsk_cache_t *entry = NULL;
294 int lineno = 0;
295 int stage = 0;
297 size_t psk_len = 0;
298 unsigned int digest_len, mic_len;
299 eapol_attr_t const *eapol;
300 eapol_attr_t *zeroed;
301 FILE *fp = NULL;
302 char const *filename = (env->filename.type == FR_TYPE_STRING) ? env->filename.vb_strvalue : NULL;
303 char const *psk_identity = NULL, *psk = NULL;
304 uint8_t *p;
305 uint8_t const *snonce, *ap_mac;
306 uint8_t const *min_mac, *max_mac;
307 uint8_t const *min_nonce, *max_nonce;
308 uint8_t pmk[32];
309 uint8_t s_mac[6], message[sizeof("Pairwise key expansion") + 6 + 6 + 32 + 32 + 1], frame[128];
310 uint8_t digest[EVP_MAX_MD_SIZE], mic[EVP_MAX_MD_SIZE];
311 char token_identity[256];
312 char token_psk[256];
313
314 /*
315 * Search for the information in a bunch of attributes.
316 */
317 if (env->anonce.vb_length != 32) {
318 RWARN("%s has incorrect length (%zu, not 32)", env->anonce_tmpl->name, env->anonce.vb_length);
320 }
321
322 if (env->key_msg.vb_length < sizeof(*eapol)) {
323 RWARN("%s has incorrect length (%zu < %zu)", env->key_msg_tmpl->name, env->key_msg.vb_length, sizeof(*eapol));
325 }
326
327 if (env->key_msg.vb_length > sizeof(frame)) {
328 RWARN("%s has incorrect length (%zu > %zu)", env->key_msg_tmpl->name, env->key_msg.vb_length, sizeof(frame));
330 }
331
332 /*
333 * At this point, the request has the relevant DPSK
334 * attributes. The module now should return FAIL for
335 * missing / invalid attributes, or REJECT for
336 * authentication failure.
337 *
338 * If the entry is found in a VP or a cache, the module
339 * returns OK. This means that the caller should not
340 * save &control:Pre-Shared-Key somewhere.
341 *
342 * If the module found a matching entry in the file, it
343 * returns UPDATED to indicate that the caller should
344 * update the database with the PSK which was found.
345 */
346
347#ifdef __COVERITY__
348 /*
349 * Coverity doesn't see that fr_base16_decode will populate s_mac
350 */
351 memset(s_mac, 0, 6);
352#endif
353 /*
354 * Get supplicant MAC address from the User-Name
355 */
356 if (fr_base16_decode(NULL, &FR_DBUFF_TMP(s_mac, sizeof(s_mac)),
357 &FR_SBUFF_IN(env->username.vb_strvalue, env->username.vb_length), false) != 6) {
358 RERROR("User-Name is not a recognizable hex MAC address");
360 }
361
362 if (env->calledstation.vb_length != 6) {
363 RERROR("Called-Station-MAC is not a recognizable MAC address");
365 }
366
367 ap_mac = env->calledstation.vb_octets;
368
369 /*
370 * Sort the MACs
371 */
372 if (memcmp(s_mac, ap_mac, 6) <= 0) {
373 min_mac = s_mac;
374 max_mac = ap_mac;
375 } else {
376 min_mac = ap_mac;
377 max_mac = s_mac;
378 }
379
380 eapol = (eapol_attr_t const *) env->key_msg.vb_octets;
381
382 /*
383 * Get supplicant nonce and AP nonce.
384 *
385 * Then sort the nonces.
386 */
387 snonce = env->key_msg.vb_octets + 17;
388 if (memcmp(snonce, env->anonce.vb_octets, 32) <= 0) {
389 min_nonce = snonce;
390 max_nonce = env->anonce.vb_octets;
391 } else {
392 min_nonce = env->anonce.vb_octets;
393 max_nonce = snonce;
394 }
395
396 /*
397 * Create the base message which we will hash.
398 */
399 memcpy(message, "Pairwise key expansion", sizeof("Pairwise key expansion")); /* including trailing NUL */
400 p = &message[sizeof("Pairwise key expansion")];
401
402 memcpy(p, min_mac, 6);
403 memcpy(p + 6, max_mac, 6);
404 p += 12;
405
406 memcpy(p, min_nonce, 32);
407 memcpy(p + 32, max_nonce, 32);
408 p += 64;
409 *p = '\0';
410 fr_assert(sizeof(message) == (p + 1 - message));
411
412 /*
413 * If we're caching, then check the cache first, before
414 * trying the file. This check allows us to avoid the
415 * PMK calculation in many situations, as that can be
416 * expensive.
417 */
418 if (inst->cache_size) {
419 entry = dpsk_cache_find(request, inst, pmk, sizeof(pmk), &env->ssid, s_mac);
420 if (entry) {
421 psk_identity = entry->identity;
422 psk = entry->psk;
423 psk_len = entry->psk_len;
424 goto make_digest;
425 }
426 }
427
428 /*
429 * No cache, or no cache entry. Look for an external PMK
430 * taken from a database.
431 */
432stage1:
433 stage = 1;
434
435 if (env->masterkey.type == FR_TYPE_OCTETS) {
436 if (env->masterkey.vb_length != sizeof(pmk)) {
437 RWARN("%s has incorrect length (%zu != %zu) - ignoring it", env->masterkey_tmpl->name,
438 env->masterkey.vb_length, sizeof(pmk));
439 } else {
440 RDEBUG2("Using %s", env->masterkey_tmpl->name);
441 memcpy(pmk, env->masterkey.vb_octets, sizeof(pmk));
442 goto make_digest;
443 }
444 }
445
446 /*
447 * No external PMK. Try an external PSK.
448 */
449 if (env->psk.type == FR_TYPE_STRING) {
450 RDEBUG3("Trying %s", env->psk_tmpl->name);
451 if (generate_pmk(request, pmk, sizeof(pmk), &env->ssid, env->psk.vb_strvalue, env->psk.vb_length) < 0) {
452 fr_assert(!fp);
454 }
455
456 if (env->psk_identity.type == FR_TYPE_STRING) {
457 psk_identity = env->psk_identity.vb_strvalue;
458 } else {
459 psk_identity = env->username.vb_strvalue;
460 }
461
462 psk = env->psk.vb_strvalue;
463 psk_len = env->psk.vb_length;
464
465 goto make_digest;
466 }
467
468 /*
469 * No external PSK was found. If there's no file, then
470 * we can't do anything else.
471 */
472stage2:
473 stage = 2;
474
475 if (!filename) {
476 RERROR("No %s was found, and no 'filename' was configured", env->psk_tmpl->name);
478 }
479
480 /*
481 * If there's an PSK from an external database, then we
482 * never read the filename.
483 */
484 if (filename) {
485 char token_mac[256];
486 char buffer[1024];
487 fr_sbuff_t sbuff;
489 size_t len;
490 fr_sbuff_term_t const terms = FR_SBUFF_TERMS(L("\n"),L("\r"),L(","));
491 fr_sbuff_term_t const quoted_terms = FR_SBUFF_TERMS(L("\""));
492 bool quoted = false;
493
494 RDEBUG3("Looking for PSK in file %s", filename);
495
496 fp = fopen(filename, "r");
497 if (!fp) {
498 REDEBUG("Failed opening %s - %s", filename, fr_syserror(errno));
500 }
501
502 fr_sbuff_init_file(&sbuff, &fctx, buffer, sizeof(buffer), fp, SIZE_MAX);
503
504stage2a:
505 lineno++;
506 fr_sbuff_adv_past_whitespace(&sbuff, SIZE_MAX, NULL);
507 quoted = fr_sbuff_next_if_char(&sbuff, '"');
508 len = fr_sbuff_out_bstrncpy_until(&FR_SBUFF_OUT(token_identity, sizeof(token_identity)), &sbuff,
509 sizeof(token_identity), quoted ? &quoted_terms : &terms, NULL);
510 if (len == 0) {
511 RDEBUG("Failed to find matching PSK or MAC in %s", filename);
512 fail_file:
513 fclose(fp);
515 }
516 if (quoted) {
517 fr_sbuff_next_if_char(&sbuff, '"');
518 fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL);
519 }
520
521 if (!fr_sbuff_next_if_char(&sbuff, ',')) {
522 RDEBUG("%s[%d] Failed to find ',' after identity", filename, lineno);
523 goto fail_file;
524 }
525
526 fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL);
527 quoted = fr_sbuff_next_if_char(&sbuff, '"');
528 len = fr_sbuff_out_bstrncpy_until(&FR_SBUFF_OUT(token_psk, sizeof(token_psk)), &sbuff,
529 sizeof(token_identity), quoted ? &quoted_terms : &terms, NULL);
530 if (len == 0) {
531 RDEBUG("%s[%d] Failed parsing PSK", filename, lineno);
532 goto fail_file;
533 }
534 if (quoted) {
535 fr_sbuff_next_if_char(&sbuff, '"');
536 fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL);
537 }
538
539 /*
540 * The MAC is optional. If there is a MAC, we
541 * loop over the file until we find a matching
542 * one.
543 */
544 if (fr_sbuff_next_if_char(&sbuff, ',')) {
545
546 fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL);
547 quoted = fr_sbuff_next_if_char(&sbuff, '"');
548 len = fr_sbuff_out_bstrncpy_until(&FR_SBUFF_OUT(token_mac, sizeof(token_mac)), &sbuff,
549 sizeof(token_identity), quoted ? &quoted_terms : &terms, NULL);
550 if (len == 0) {
551 RERROR("%s[%d] Failed parsing MAC", filename, lineno);
552 goto fail_file;
553 }
554
555 /*
556 * See if the MAC matches. If not, skip
557 * this entry. That's a basic negative cache.
558 */
559 if ((len != 12) ||
560 (fr_base16_decode(NULL, &FR_DBUFF_TMP((uint8_t *)token_mac, 6),
561 &FR_SBUFF_IN(token_mac, 12), false) != 6)) {
562 RERROR("%s[%d] Failed parsing MAC", filename, lineno);
563 goto fail_file;
564 }
565 if (quoted) fr_sbuff_next_if_char(&sbuff, '"');
566
567 /*
568 * The MAC doesn't match, don't even bother trying to generate the PMK.
569 */
570 if (memcmp(s_mac, token_mac, 6) != 0) {
571 goto stage2a;
572 }
573
574 RDEBUG3("Found matching MAC");
575 stage = 3;
576 }
577
578 /*
579 * Generate the PMK using the SSID, this MAC, and the PSK we just read.
580 */
581 psk = token_psk;
582 psk_len = strlen(token_psk);
583 psk_identity = token_identity;
584
585 RDEBUG3("%s[%d] Trying PSK %s", filename, lineno, token_psk);
586 if (generate_pmk(request, pmk, sizeof(pmk), &env->ssid, psk, psk_len) < 0) {
587 goto fail_file;
588 }
589 }
590
591 /*
592 * HMAC = HMAC_SHA1(pmk, message);
593 *
594 * We need the first 16 octets of this.
595 */
596make_digest:
597 digest_len = sizeof(digest);
598#ifdef __COVERITY__
599 /*
600 * Coverity doesn't see that HMAC will populate digest
601 */
602 memset(digest, 0, digest_len);
603#endif
604 HMAC(EVP_sha1(), pmk, sizeof(pmk), message, sizeof(message), digest, &digest_len);
605
606 RHEXDUMP3(message, sizeof(message), "message:");
607 RHEXDUMP3(pmk, sizeof(pmk), "pmk :");
608 RHEXDUMP3(digest, 16, "kck :");
609
610 /*
611 * Create the frame with the middle field zero, and hash it with the KCK digest we calculated from the key expansion.
612 */
613 memcpy(frame, env->key_msg.vb_octets, env->key_msg.vb_length);
614 zeroed = (eapol_attr_t *) &frame[0];
615 memset(&zeroed->frame.mic[0], 0, 16);
616
617 RHEXDUMP3(frame, env->key_msg.vb_length, "zeroed:");
618
619 mic_len = sizeof(mic);
620#ifdef __COVERITY__
621 /*
622 * Coverity doesn't see that HMAC will populate mic
623 */
624 memset(mic, 0, mic_len);
625#endif
626 HMAC(EVP_sha1(), digest, 16, frame, env->key_msg.vb_length, mic, &mic_len);
627
628 /*
629 * The MICs don't match.
630 */
631 if (memcmp(&eapol->frame.mic[0], mic, 16) != 0) {
632 RDEBUG3("Stage %d", stage);
633 RHEXDUMP3(mic, 16, "calculated mic:");
634 RHEXDUMP3(eapol->frame.mic, 16, "packet mic :");
635
636 psk_identity = NULL;
637 psk = NULL;
638 psk_len = 0;
639
640 /*
641 * Found a cached entry, but it didn't match. Go
642 * check external PMK / PSK.
643 */
644 if (stage == 0) {
645 fr_assert(entry != NULL);
646 fr_rb_delete(&inst->mutable->cache, entry); /* locks and unlinks the entry */
647 entry = NULL;
648 goto stage1;
649 }
650
651 /*
652 * Found an external PMK or PSK, but it didn't
653 * match. Go check the file.
654 */
655 if (stage == 1) {
656 if (env->psk.type == FR_TYPE_STRING) RWARN("%s did not match", env->psk_tmpl->name);
657
658 if (filename) {
659 RDEBUG("Checking file %s for PSK and MAC", filename);
660 goto stage2;
661 }
662
663 RWARN("No 'filename' was configured.");
665 }
666
667 /*
668 * The file is open, so we keep reading it until
669 * we find a matching entry.
670 */
671 fr_assert(fp);
672
673 if (stage == 2) goto stage2a;
674
675 fclose(fp);
676
677 /*
678 * We found a PSK associated with this MAC in the
679 * file. But it didn't match, so we're done.
680 */
681 fr_assert(stage == 3);
682
683 RWARN("Found matching MAC in %s, but the PSK does not match", filename);
685 }
686
687 /*
688 * We found a matching PSK. If we read it from the file,
689 * then close the file, and ensure that we return
690 * UPDATED. This tells the caller to write the entry
691 * into the database, so that we don't need to scan the
692 * file again.
693 */
694 if (fp) {
695 rcode = RLM_MODULE_UPDATED;
696 fr_assert(psk == token_psk);
697 fr_assert(psk_identity == token_identity);
698 fclose(fp);
699 }
700
701 /*
702 * Extend the lifetime of the cache entry, or add the
703 * cache entry if necessary. We only add / update the
704 * cache entry if the PSK was not found in a VP.
705 *
706 * If the caller gave us only a PMK, then don't cache anything.
707 */
708 if (inst->cache_size && psk && psk_identity) {
709 rlm_dpsk_cache_t my_entry;
710
711 /*
712 * We've found an entry. Just update it.
713 */
714 if (entry) goto update_entry;
715
716 /*
717 * No cached entry, or the PSK in the cached
718 * entry didn't match. We need to create one.
719 */
720 memcpy(my_entry.mac, s_mac, sizeof(my_entry.mac));
721 memcpy(&my_entry.ssid, env->ssid.vb_octets, sizeof(my_entry.ssid)); /* const ptr issues */
722 my_entry.ssid_len = env->ssid.vb_length;
723
724 entry = fr_rb_find(&inst->mutable->cache, &my_entry);
725 if (!entry) {
726 /*
727 * Maybe there are oo many entries in the
728 * cache. If so, delete the oldest one.
729 */
730 if (fr_rb_num_elements(&inst->mutable->cache) > inst->cache_size) {
731 pthread_mutex_lock(&inst->mutable->mutex);
732 entry = fr_dlist_head(&inst->mutable->head);
733 pthread_mutex_unlock(&inst->mutable->mutex);
734
735 fr_rb_delete(&inst->mutable->cache, entry); /* locks and unlinks the entry */
736 }
737
738 MEM(entry = talloc_zero(&inst->mutable->cache, rlm_dpsk_cache_t));
739
740 memcpy(entry->mac, s_mac, sizeof(entry->mac));
741 memcpy(entry->pmk, pmk, sizeof(entry->pmk));
742
743 entry->inst = inst;
744
745 /*
746 * Save the SSID, PSK, and PSK identity in the cache entry.
747 */
748 MEM(entry->ssid = talloc_memdup(entry, env->ssid.vb_octets, env->ssid.vb_length));
749 entry->ssid_len = env->ssid.vb_length;
750
751 MEM(entry->psk = talloc_strdup(entry, psk));
752 entry->psk_len = psk_len;
753
754 entry->identity_len = strlen(psk_identity);
755 MEM(entry->identity = talloc_strdup(entry, psk_identity));
756
757 /*
758 * Cache it.
759 */
760 if (!fr_rb_insert(&inst->mutable->cache, entry)) {
761 TALLOC_FREE(entry);
762 goto update_attributes;
763 }
764 RDEBUG3("Cache entry saved");
765 }
766
767 update_entry:
768 pthread_mutex_lock(&inst->mutable->mutex);
769 entry->expires = fr_time_add(fr_time(), inst->cache_lifetime);
770 if (fr_dlist_entry_in_list(&entry->dlist)) fr_dlist_remove(&inst->mutable->head, entry);
771 fr_dlist_insert_tail(&inst->mutable->head, entry);
772 pthread_mutex_unlock(&inst->mutable->mutex);
773
774 /*
775 * Add the PSK to the reply items, if it was cached.
776 */
777 if (entry->psk) {
778 tmpl_t psk_rhs;
779 map_t psk_map = {
780 .lhs = env->psk_dest_tmpl,
781 .op = T_OP_SET,
782 .rhs = &psk_rhs
783 };
784
786 fr_value_box_bstrndup_shallow(&psk_map.rhs->data.literal,
787 NULL, entry->psk, entry->psk_len, true);
788 if (map_to_request(request, &psk_map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
789 }
790 }
791
792update_attributes:
793 /*
794 * We found a cache entry, or an external PSK. Don't
795 * create new attributes.
796 */
797 if (rcode == RLM_MODULE_OK) RETURN_UNLANG_OK;
798
799 fr_assert(psk != NULL);
800 fr_assert(psk_identity != NULL);
801
802 {
803 tmpl_t rhs;
804 map_t map = {
805 .lhs = env->psk_dest_tmpl,
806 .op = T_OP_SET,
807 .rhs = &rhs
808 };
809
810 RDEBUG2("Creating %s and %s", env->psk_dest_tmpl->name, env->psk_identity_dest_tmpl->name);
812 fr_value_box_bstrndup_shallow(&map.rhs->data.literal, NULL, psk, psk_len, true);
813 if (map_to_request(request, &map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
814
815 map.lhs = env->psk_identity_dest_tmpl;
816 fr_value_box_bstrndup_shallow(&map.rhs->data.literal, NULL, psk_identity, strlen(psk_identity), true);
817 if (map_to_request(request, &map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
818 }
819
821}
822
823static xlat_arg_parser_t const dpsk_pmk_xlat_arg[] = {
824 { .required = true, .concat = true, .type = FR_TYPE_STRING },
825 { .required = true, .concat = true, .type = FR_TYPE_STRING },
827};
828
829/** xlat to generate the PMK from SSID and Pre-Shared-Key
830 *
831 * Example:
832 @verbatim
833 %dpsk.pmk(Calling-Station-SSID, Pre-Shared-Key)
834 @endverbatim
835 *
836 * @ingroup xlat_functions
837 */
838static xlat_action_t dpsk_pmk_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, UNUSED xlat_ctx_t const *xctx,
839 request_t *request, fr_value_box_list_t *in)
840{
841 fr_value_box_t *ssid, *psk, *vb;
842 uint8_t buffer[32];
843
844 XLAT_ARGS(in, &ssid, &psk);
845
846 if (PKCS5_PBKDF2_HMAC_SHA1(psk->vb_strvalue, psk->vb_length,
847 (const unsigned char *) ssid->vb_strvalue, ssid->vb_length,
848 4096, sizeof(buffer), buffer) == 0) {
849 RERROR("Failed calling OpenSSL to calculate the PMK");
850 return XLAT_ACTION_FAIL;
851 }
852
853 MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_OCTETS, NULL));
854 fr_value_box_memdup(vb, vb, NULL, buffer, sizeof(buffer), true);
855
857
858 return XLAT_ACTION_DONE;
859}
860
861static int mod_load(void)
862{
863 xlat_t *xlat;
864 if (unlikely((xlat = xlat_func_register(NULL, "dpsk.pmk", dpsk_pmk_xlat, FR_TYPE_OCTETS)) == NULL)) return -1;
865 xlat_func_args_set(xlat, dpsk_pmk_xlat_arg);
866
867 return 0;
868}
869
870static void mod_unload(void)
871{
872 xlat_func_unregister("dpsk.pmk");
873}
874
875static int8_t cmp_cache_entry(void const *one, void const *two)
876{
877 rlm_dpsk_cache_t const *a = (rlm_dpsk_cache_t const *) one;
878 rlm_dpsk_cache_t const *b = (rlm_dpsk_cache_t const *) two;
879 int rcode;
880
881 rcode = memcmp(a->mac, b->mac, sizeof(a->mac));
882 if (rcode != 0) return rcode;
883
884 if (a->ssid_len < b->ssid_len) return -1;
885 if (a->ssid_len > b->ssid_len) return +1;
886
887 return CMP(memcmp(a->ssid, b->ssid, a->ssid_len), 0);
888}
889
890static void free_cache_entry(void *data)
891{
893
894 pthread_mutex_lock(&entry->inst->mutable->mutex);
896 pthread_mutex_unlock(&entry->inst->mutable->mutex);
897
898 talloc_free(entry);
899}
900
901static int mod_detach(const module_detach_ctx_t *mctx)
902{
903 rlm_dpsk_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_dpsk_t);
904
905 if (!inst->cache_size) return 0;
906
907 pthread_mutex_destroy(&inst->mutable->mutex);
908
909 talloc_free(inst->mutable);
910
911 return 0;
912}
913#endif
914
915static int mod_instantiate(module_inst_ctx_t const *mctx)
916{
917#ifdef WITH_TLS
918 rlm_dpsk_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_dpsk_t);
919
920 inst->auth_type = fr_dict_enum_by_name(attr_auth_type, mctx->mi->name, -1);
921 if (!inst->auth_type) {
922 WARN("Failed to find 'authenticate %s {...}' section. DPSK will likely not work",
923 mctx->mi->name);
924 }
925
926 /*
927 * We can still use a cache if we're getting PSKs from a
928 * database. The PMK calculation can take time, so
929 * caching the PMK still saves us time.
930 */
931 if (!inst->cache_size) return 0;
932
933 FR_INTEGER_BOUND_CHECK("cache_size", inst->cache_size, <=, ((uint32_t) 1) << 16);
934
935 FR_TIME_DELTA_BOUND_CHECK("cache_lifetime", inst->cache_lifetime, <=, fr_time_delta_from_sec(7 * 86400));
936 FR_TIME_DELTA_BOUND_CHECK("cache_lifetime", inst->cache_lifetime, >=, fr_time_delta_from_sec(3600));
937
938 inst->mutable = talloc_zero(NULL, rlm_dpsk_mutable_t);
939
940 fr_rb_inline_init(&inst->mutable->cache, rlm_dpsk_cache_t, node, cmp_cache_entry, free_cache_entry);
941
942 fr_dlist_init(&inst->mutable->head, rlm_dpsk_cache_t, dlist);
943
944 if (pthread_mutex_init(&inst->mutable->mutex, NULL) < 0) {
945 cf_log_err(mctx->mi->conf, "Failed creating mutex");
946 return -1;
947 }
948
949 return 0;
950#else
951 cf_log_err(mctx->mi->conf, "rlm_dpsk requires OpenSSL");
952 return 0;
953#endif
954}
955
956/*
957 * The module name should be the only globally exported symbol.
958 * That is, everything else should be 'static'.
959 *
960 * If the module needs to temporarily modify it's instantiation
961 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
962 * The server will then take care of ensuring that the module
963 * is single-threaded.
964 */
967 .common = {
968 .magic = MODULE_MAGIC_INIT,
969 .name = "dpsk",
970 .inst_size = sizeof(rlm_dpsk_t),
971 .instantiate = mod_instantiate,
973#ifdef WITH_TLS
974 .detach = mod_detach,
975 .onload = mod_load,
976 .unload = mod_unload,
977#endif
978 },
979#ifdef WITH_TLS
980 .method_group = {
981 .bindings = (module_method_binding_t[]){
982 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_authorize, .method_env = &dpsk_autz_method_env },
983 { .section = SECTION_NAME("authenticate", CF_IDENT_ANY), .method = mod_authenticate, .method_env = &dpsk_auth_method_env },
984
986 }
987 }
988#endif
989};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
static int const char char buffer[256]
Definition acutest.h:578
#define fr_base16_decode(_err, _out, _in, _no_trailing)
Definition base16.h:95
#define RCSID(id)
Definition build.h:487
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:209
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:112
#define unlikely(_x)
Definition build.h:383
#define UNUSED
Definition build.h:317
#define CALL_ENV_TERMINATOR
Definition call_env.h:236
#define FR_CALL_ENV_PARSE_OFFSET(_name, _cast_type, _flags, _struct, _field, _parse_field)
Specify a call_env_parser_t which writes out runtime results and the result of the parsing phase to t...
Definition call_env.h:365
size_t inst_size
Size of per call env.
Definition call_env.h:245
@ CALL_ENV_FLAG_ATTRIBUTE
Tmpl MUST contain an attribute reference.
Definition call_env.h:86
@ CALL_ENV_FLAG_NONE
Definition call_env.h:74
@ CALL_ENV_FLAG_REQUIRED
Associated conf pair or section is required.
Definition call_env.h:75
@ CALL_ENV_FLAG_NULLABLE
Tmpl expansions are allowed to produce no output.
Definition call_env.h:80
#define FR_CALL_ENV_OFFSET(_name, _cast_type, _flags, _struct, _field)
Specify a call_env_parser_t which writes out runtime results to the specified field.
Definition call_env.h:340
#define FR_CALL_ENV_PARSE_ONLY_OFFSET(_name, _cast_type, _flags, _struct, _parse_field)
Specify a call_env_parser_t which writes out the result of the parsing phase to the field specified.
Definition call_env.h:389
Per method call config.
Definition call_env.h:180
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:660
#define FR_INTEGER_BOUND_CHECK(_name, _var, _op, _bound)
Definition cf_parse.h:520
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:283
#define FR_TIME_DELTA_BOUND_CHECK(_name, _var, _op, _bound)
Definition cf_parse.h:531
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:597
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:286
#define CF_IDENT_ANY
Definition cf_util.h:78
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:524
static int fr_dcursor_append(fr_dcursor_t *cursor, void *v)
Insert a single item at the end of the list.
Definition dcursor.h:408
#define MEM(x)
Definition debug.h:36
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:294
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:307
#define DICT_AUTOLOAD_TERMINATOR
Definition dict.h:313
fr_dict_enum_value_t const * fr_dict_enum_by_name(fr_dict_attr_t const *da, char const *name, ssize_t len)
Definition dict_util.c:3705
static fr_slen_t in
Definition dict.h:884
Specifies an attribute which must be present for the module to function.
Definition dict.h:293
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:306
Value of an enumerated attribute.
Definition dict.h:255
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
#define fr_dlist_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:260
static void * fr_dlist_head(fr_dlist_head_t const *list_head)
Return the HEAD item of a list or NULL if the list is empty.
Definition dlist.h:486
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition dlist.h:638
static bool fr_dlist_entry_in_list(fr_dlist_t const *entry)
Check if a list entry is part of a list.
Definition dlist.h:163
static void fr_dlist_entry_unlink(fr_dlist_t *entry)
Remove an item from the dlist when we don't have access to the head.
Definition dlist.h:146
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition dlist.h:378
Head of a doubly linked list.
Definition dlist.h:51
Entry in a doubly linked list.
Definition dlist.h:41
#define RDEBUG3(fmt,...)
Definition log.h:343
#define RWARN(fmt,...)
Definition log.h:297
#define RERROR(fmt,...)
Definition log.h:298
#define RHEXDUMP3(_data, _len, _fmt,...)
Definition log.h:705
int map_to_vp(TALLOC_CTX *ctx, fr_pair_list_t *out, request_t *request, map_t const *map, UNUSED void *uctx)
Convert a map to a fr_pair_t.
Definition map.c:1596
int map_to_request(request_t *request, map_t const *map, radius_map_getvalue_t func, void *ctx)
Convert map_t to fr_pair_t (s) and add them to a request_t.
Definition map.c:1876
talloc_free(reap)
unsigned short uint16_t
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_OCTETS
Raw octets.
unsigned int uint32_t
size_t fr_sbuff_out_bstrncpy_until(fr_sbuff_t *out, fr_sbuff_t *in, size_t len, fr_sbuff_term_t const *tt, fr_sbuff_unescape_rules_t const *u_rules)
unsigned char uint8_t
void * env_data
Per call environment data.
Definition module_ctx.h:44
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
module_instance_t * mi
Module instance to detach.
Definition module_ctx.h:57
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for detach calls.
Definition module_ctx.h:56
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
bool module_rlm_section_type_set(request_t *request, fr_dict_attr_t const *type_da, fr_dict_enum_value_t const *enumv)
Set the next section type if it's not already set.
Definition module_rlm.c:417
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
static const conf_parser_t config[]
Definition base.c:186
static int mod_load(void)
Definition proto_arp.c:236
static void mod_unload(void)
Definition proto_arp.c:245
#define fr_assert(_expr)
Definition rad_assert.h:38
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define RDEBUG(fmt,...)
Definition radclient.h:53
#define WARN(fmt,...)
Definition radclient.h:47
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
Return how many nodes there are in a tree.
Definition rb.c:781
void * fr_rb_find(fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
Definition rb.c:577
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition rb.c:626
bool fr_rb_delete(fr_rb_tree_t *tree, void const *data)
Remove node and free data (if a free function was specified)
Definition rb.c:741
#define fr_rb_inline_init(_tree, _type, _field, _data_cmp, _data_free)
Initialises a red black tree.
Definition rb.h:180
The main red black tree structure.
Definition rb.h:73
#define RETURN_UNLANG_UPDATED
Definition rcode.h:66
#define RETURN_UNLANG_FAIL
Definition rcode.h:59
#define RETURN_UNLANG_REJECT
Definition rcode.h:58
#define RETURN_UNLANG_OK
Definition rcode.h:60
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:45
@ RLM_MODULE_UPDATED
OK (pairs modified).
Definition rcode.h:51
#define RETURN_UNLANG_NOOP
Definition rcode.h:65
static int mod_detach(module_detach_ctx_t const *mctx)
Definition rlm_always.c:137
static unlang_action_t mod_authorize(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition rlm_chap.c:176
static unlang_action_t mod_authenticate(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition rlm_chap.c:228
uint8_t pmk[32]
Definition rlm_dpsk.c:86
tmpl_t * psk_identity_dest_tmpl
Definition rlm_dpsk.c:162
uint8_t rsc[8]
Definition rlm_dpsk.c:69
tmpl_t * psk_dest_tmpl
Definition rlm_dpsk.c:161
fr_rb_tree_t cache
Definition rlm_dpsk.c:103
uint16_t information
Definition rlm_dpsk.c:64
fr_value_box_t key_msg
Definition rlm_dpsk.c:152
fr_rb_node_t node
Definition rlm_dpsk.c:84
size_t ssid_len
Definition rlm_dpsk.c:89
struct rlm_dpsk_s rlm_dpsk_t
Definition rlm_dpsk.c:81
module_rlm_t rlm_dpsk
Definition rlm_dpsk.c:966
uint8_t reserved[8]
Definition rlm_dpsk.c:70
fr_value_box_t username
Definition rlm_dpsk.c:154
static fr_dict_t const * dict_freeradius
Definition rlm_dpsk.c:118
tmpl_t * key_msg_tmpl
Definition rlm_dpsk.c:144
uint8_t mac[6]
Definition rlm_dpsk.c:85
fr_value_box_t masterkey
Definition rlm_dpsk.c:156
fr_dlist_head_t head
Definition rlm_dpsk.c:106
static fr_dict_attr_t const * attr_auth_type
Definition rlm_dpsk.c:126
fr_dict_autoload_t rlm_dpsk_dict[]
Definition rlm_dpsk.c:121
fr_value_box_t psk_identity
Definition rlm_dpsk.c:160
fr_value_box_t ssid
Definition rlm_dpsk.c:148
rlm_dpsk_t const * inst
Definition rlm_dpsk.c:99
fr_dict_attr_autoload_t rlm_dpsk_dict_attr[]
Definition rlm_dpsk.c:129
pthread_mutex_t mutex
Definition rlm_dpsk.c:105
uint16_t length
Definition rlm_dpsk.c:65
uint8_t * ssid
Definition rlm_dpsk.c:88
uint8_t mic[16]
Definition rlm_dpsk.c:71
uint8_t iv[16]
Definition rlm_dpsk.c:68
uint8_t nonce[32]
Definition rlm_dpsk.c:67
tmpl_t * masterkey_tmpl
Definition rlm_dpsk.c:157
char * identity
Definition rlm_dpsk.c:91
uint8_t descriptor
Definition rlm_dpsk.c:63
uint32_t cache_size
Definition rlm_dpsk.c:112
fr_dlist_t dlist
Definition rlm_dpsk.c:98
fr_value_box_t filename
Definition rlm_dpsk.c:163
eapol_key_frame_t frame
Definition rlm_dpsk.c:77
tmpl_t * key_msg_tmpl
Definition rlm_dpsk.c:153
uint8_t header[4]
Definition rlm_dpsk.c:76
static const conf_parser_t module_config[]
Definition rlm_dpsk.c:135
rlm_dpsk_mutable_t * mutable
Definition rlm_dpsk.c:115
size_t identity_len
Definition rlm_dpsk.c:92
uint16_t data_len
Definition rlm_dpsk.c:72
fr_dict_enum_value_t const * auth_type
Definition rlm_dpsk.c:110
fr_time_delta_t cache_lifetime
Definition rlm_dpsk.c:113
fr_value_box_t calledstation
Definition rlm_dpsk.c:155
static int mod_instantiate(module_inst_ctx_t const *mctx)
Definition rlm_dpsk.c:915
fr_value_box_t anonce
Definition rlm_dpsk.c:150
fr_value_box_t psk
Definition rlm_dpsk.c:158
uint8_t replay_counter[8]
Definition rlm_dpsk.c:66
fr_time_t expires
Definition rlm_dpsk.c:96
username
bool fr_sbuff_next_if_char(fr_sbuff_t *sbuff, char c)
Return true if the current char matches, and if it does, advance.
Definition sbuff.c:2116
#define FR_SBUFF_IN(_start, _len_or_end)
#define fr_sbuff_adv_past_whitespace(_sbuff, _len, _tt)
#define FR_SBUFF_TERMS(...)
Initialise a terminal structure with a list of sorted strings.
Definition sbuff.h:193
#define fr_sbuff_adv_past_blank(_sbuff, _len, _tt)
#define FR_SBUFF_OUT(_start, _len_or_end)
Set of terminal elements.
File sbuff extension structure.
Definition sbuff.h:151
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:40
char const * name
Instance name e.g. user_database.
Definition module.h:355
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:349
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:291
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:152
Named methods exported by a module.
Definition module.h:174
@ TMPL_TYPE_DATA
Value in native boxed format.
Definition tmpl.h:138
tmpl_t * tmpl_init_shallow(tmpl_t *vpt, tmpl_type_t type, fr_token_t quote, char const *name, ssize_t len, tmpl_rules_t const *t_rules))
Initialise a tmpl without copying the input name string.
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition state_test.c:8
Value pair map.
Definition map.h:77
tmpl_t * lhs
Typically describes the attribute to add, modify or compare.
Definition map.h:78
tmpl_t * rhs
Typically describes a literal value or a src attribute to copy or compare.
Definition map.h:79
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define talloc_get_type_abort_const
Definition talloc.h:244
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:590
#define fr_time_add(_a, _b)
Add a time/time delta together.
Definition time.h:196
#define fr_time_gt(_a, _b)
Definition time.h:237
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
"server local" time.
Definition time.h:69
void tmpl_dcursor_clear(tmpl_dcursor_ctx_t *cc)
Clear any temporary state allocations.
#define tmpl_dcursor_init(_err, _ctx, _cc, _cursor, _request, _vpt)
Maintains state between cursor calls.
@ T_BARE_WORD
Definition token.h:120
@ T_OP_SET
Definition token.h:84
@ T_DOUBLE_QUOTED_STRING
Definition token.h:121
static char const * fail_file
#define XLAT_ARGS(_list,...)
Populate local variables with value boxes from the input list.
Definition xlat.h:383
unsigned int required
Argument must be present, and non-empty.
Definition xlat.h:146
#define XLAT_ARG_PARSER_TERMINATOR
Definition xlat.h:170
xlat_action_t
Definition xlat.h:37
@ XLAT_ACTION_FAIL
An xlat function failed.
Definition xlat.h:44
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition xlat.h:43
Definition for a single argument consumend by an xlat function.
Definition xlat.h:145
void fr_value_box_bstrndup_shallow(fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Assign a string to to a fr_value_box_t.
Definition value.c:4926
int fr_value_box_memdup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, uint8_t const *src, size_t len, bool tainted)
Copy a buffer to a fr_value_box_t.
Definition value.c:5083
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:645
static fr_slen_t data
Definition value.h:1333
int nonnull(2, 5))
static size_t char ** out
Definition value.h:1025
An xlat calling ctx.
Definition xlat_ctx.h:49
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
Definition xlat_func.c:363
xlat_t * xlat_func_register(TALLOC_CTX *ctx, char const *name, xlat_func_t func, fr_type_t return_type)
Register an xlat function.
Definition xlat_func.c:216
void xlat_func_unregister(char const *name)
Unregister an xlat function.
Definition xlat_func.c:516