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: a162c8c6aa72ad8b173d5f304084668ef0903508 $
22 * @file rlm_dpsk.c
23 * @brief Dynamic PSK for WiFi
24 *
25 * @copyright 2025 Network RADIUS SAS (legal@networkradius.com)
26 */
27RCSID("$Id: a162c8c6aa72ad8b173d5f304084668ef0903508 $")
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
35#include <openssl/ssl.h>
36#include <openssl/evp.h>
37#include <openssl/hmac.h>
38
39
40/*
41 Header: 02030075
42
43 descriptor 02
44 information 010a
45 length 0010
46 replay counter 000000000000001
47 snonce c3bb319516614aacfb44e933bf1671131fb1856e5b2721952d414ce3f5aa312b
48 IV 0000000000000000000000000000000
49 rsc 0000000000000000
50 reserved 0000000000000000
51 mic 35cddcedad0dfb6a12a2eca55c17c323
52 data length 0016
53 data 30140100000fac040100000fac040100000fac028c00
54
55 30
56 14 length of data
57 01 ...
58*/
59
60typedef struct eapol_key_frame_t {
61 uint8_t descriptor; // message number 2
63 uint16_t length; // always 0010, for 16 octers
64 uint8_t replay_counter[8]; // usually "1"
65 uint8_t nonce[32]; // random token
66 uint8_t iv[16]; // zeroes
67 uint8_t rsc[8]; // zeros
68 uint8_t reserved[8]; // zeroes
69 uint8_t mic[16]; // calculated data
70 uint16_t data_len; // various other things we don't need.
71} CC_HINT(__packed__) eapol_key_frame_t;
72
73typedef struct eapol_attr_t {
74 uint8_t header[4]; // 02030075
76} CC_HINT(__packed__) eapol_attr_t;
77
78
79typedef struct rlm_dpsk_s rlm_dpsk_t;
80
81typedef struct {
83 uint8_t mac[6];
84 uint8_t pmk[32];
85
87 size_t ssid_len;
88
89 char *identity;
91
92 char *psk;
93 size_t psk_len;
95
99
106
115
117
120 { .out = &dict_freeradius, .proto = "freeradius" },
122};
123
125
128 { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
129
131};
132
133static const conf_parser_t module_config[] = {
134 { FR_CONF_OFFSET("cache_size", rlm_dpsk_t, cache_size) },
135 { FR_CONF_OFFSET("cache_lifetime", rlm_dpsk_t, cache_lifetime) },
136
138};
139
144
163
164#ifdef WITH_TLS
165static const call_env_method_t dpsk_autz_method_env = {
167 .inst_type = "dpsk_autz_call_env_t",
168 .env = (call_env_parser_t[]) {
170 dpsk_autz_call_env_t, anonce_tmpl), .pair.dflt = "FreeRADIUS-EV5.802_1X-Anonce",
171 .pair.dflt_quote = T_BARE_WORD },
173 dpsk_autz_call_env_t, key_msg_tmpl), .pair.dflt = "FreeRADIUS-EV5.802_1X-EAPoL-Key-Msg",
174 .pair.dflt_quote = T_BARE_WORD },
176 }
177};
178
179static const call_env_method_t dpsk_auth_method_env = {
181 .inst_type = "dpsk_auth_call_env_t",
182 .env = (call_env_parser_t[]) {
185 dpsk_auth_call_env_t, ssid, ssid_tmpl), .pair.dflt = "Called-Station-SSID",
186 .pair.dflt_quote = T_BARE_WORD },
189 dpsk_auth_call_env_t, anonce, anonce_tmpl), .pair.dflt = "FreeRADIUS-EV5.802_1X-Anonce",
190 .pair.dflt_quote = T_BARE_WORD },
193 dpsk_auth_call_env_t, key_msg, key_msg_tmpl), .pair.dflt = "FreeRADIUS-EV5.802_1X-EAPoL-Key-Msg",
194 .pair.dflt_quote = T_BARE_WORD },
196 dpsk_auth_call_env_t, username), .pair.dflt = "User-Name", .pair.dflt_quote = T_BARE_WORD },
198 dpsk_auth_call_env_t, calledstation), .pair.dflt = "Called-Station-MAC", .pair.dflt_quote = T_BARE_WORD },
200 dpsk_auth_call_env_t, masterkey, masterkey_tmpl), .pair.dflt = "control.Pairwise-Master-Key", .pair.dflt_quote = T_BARE_WORD },
202 dpsk_auth_call_env_t, psk, psk_tmpl), .pair.dflt = "control.Pre-Shared-Key", .pair.dflt_quote = T_BARE_WORD },
204 dpsk_auth_call_env_t, psk_identity), .pair.dflt = "control.PSK-Identity", .pair.dflt_quote = T_BARE_WORD },
206 dpsk_auth_call_env_t, psk_dest_tmpl), .pair.dflt = "reply.Pre-Shared-Key", .pair.dflt_quote = T_BARE_WORD },
208 dpsk_auth_call_env_t, psk_identity_dest_tmpl), .pair.dflt = "reply.PSK-Identity", .pair.dflt_quote = T_BARE_WORD },
211 }
212};
213
214/*
215 * mod_authorize() - authorize user if we can authenticate
216 * it later. Add Auth-Type attribute if present in module
217 * configuration (usually Auth-Type must be "DPSK")
218 */
219static unlang_action_t CC_HINT(nonnull) mod_authorize(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
220{
222 dpsk_autz_call_env_t *env = talloc_get_type_abort(mctx->env_data, dpsk_autz_call_env_t);
224 fr_dcursor_t cursor;
225 fr_pair_t *vp;
226
227 vp = tmpl_dcursor_init(NULL, NULL, &cc, &cursor, request, env->anonce_tmpl);
230
231 vp = tmpl_dcursor_init(NULL, NULL, &cc, &cursor, request, env->key_msg_tmpl);
234
235 if (!inst->auth_type) {
236 WARN("No 'authenticate %s {...}' section or 'Auth-Type = %s' set. Cannot setup DPSK authentication.",
237 mctx->mi->name, mctx->mi->name);
239 }
240
242
244}
245
246static rlm_dpsk_cache_t *dpsk_cache_find(request_t *request, rlm_dpsk_t const *inst, uint8_t *buffer, size_t buflen,
247 fr_value_box_t *ssid, uint8_t const *mac)
248{
249 rlm_dpsk_cache_t *entry, my_entry;
250
251 memcpy(my_entry.mac, mac, sizeof(my_entry.mac));
252 memcpy(&my_entry.ssid, &ssid->vb_octets, sizeof(my_entry.ssid)); /* const issues */
253 my_entry.ssid_len = ssid->vb_length;
254
255 entry = fr_rb_find(&inst->mutable->cache, &my_entry);
256 if (entry) {
257 if fr_time_gt(entry->expires, fr_time()) {
258 RDEBUG3("Cache entry found");
259 memcpy(buffer, entry->pmk, buflen);
260 return entry;
261 }
262
263 RDEBUG3("Cache entry has expired");
264 fr_rb_delete(&inst->mutable->cache, entry);
265 }
266
267 return NULL;
268}
269
270
271static int generate_pmk(request_t *request, uint8_t *buffer, size_t buflen, fr_value_box_t *ssid, char const *psk, size_t psk_len)
272{
273 fr_assert(buflen == 32);
274
275 if (PKCS5_PBKDF2_HMAC_SHA1((const char *) psk, psk_len, (const unsigned char *) ssid->vb_strvalue,
276 ssid->vb_length, 4096, buflen, buffer) == 0) {
277 RERROR("Failed calling OpenSSL to calculate the PMK");
278 return -1;
279 }
280
281 return 0;
282}
283
284/*
285 * Verify the DPSK information.
286 */
287static unlang_action_t CC_HINT(nonnull) mod_authenticate(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
288{
289 rlm_dpsk_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_dpsk_t);
290 dpsk_auth_call_env_t *env = talloc_get_type_abort(mctx->env_data, dpsk_auth_call_env_t);
291 rlm_dpsk_cache_t *entry = NULL;
292 int lineno = 0;
293 int stage = 0;
295 size_t psk_len = 0;
296 unsigned int digest_len, mic_len;
297 eapol_attr_t const *eapol;
298 eapol_attr_t *zeroed;
299 FILE *fp = NULL;
300 char const *filename = (env->filename.type == FR_TYPE_STRING) ? env->filename.vb_strvalue : NULL;
301 char const *psk_identity = NULL, *psk = NULL;
302 uint8_t *p;
303 uint8_t const *snonce, *ap_mac;
304 uint8_t const *min_mac, *max_mac;
305 uint8_t const *min_nonce, *max_nonce;
306 uint8_t pmk[32];
307 uint8_t s_mac[6], message[sizeof("Pairwise key expansion") + 6 + 6 + 32 + 32 + 1], frame[128];
308 uint8_t digest[EVP_MAX_MD_SIZE], mic[EVP_MAX_MD_SIZE];
309 char token_identity[256];
310 char token_psk[256];
311
312 /*
313 * Search for the information in a bunch of attributes.
314 */
315 if (env->anonce.vb_length != 32) {
316 RWARN("%s has incorrect length (%zu, not 32)", env->anonce_tmpl->name, env->anonce.vb_length);
318 }
319
320 if (env->key_msg.vb_length < sizeof(*eapol)) {
321 RWARN("%s has incorrect length (%zu < %zu)", env->key_msg_tmpl->name, env->key_msg.vb_length, sizeof(*eapol));
323 }
324
325 if (env->key_msg.vb_length > sizeof(frame)) {
326 RWARN("%s has incorrect length (%zu > %zu)", env->key_msg_tmpl->name, env->key_msg.vb_length, sizeof(frame));
328 }
329
330 /*
331 * At this point, the request has the relevant DPSK
332 * attributes. The module now should return FAIL for
333 * missing / invalid attributes, or REJECT for
334 * authentication failure.
335 *
336 * If the entry is found in a VP or a cache, the module
337 * returns OK. This means that the caller should not
338 * save &control:Pre-Shared-Key somewhere.
339 *
340 * If the module found a matching entry in the file, it
341 * returns UPDATED to indicate that the caller should
342 * update the database with the PSK which was found.
343 */
344
345#ifdef __COVERITY__
346 /*
347 * Coverity doesn't see that fr_base16_decode will populate s_mac
348 */
349 memset(s_mac, 0, 6);
350#endif
351 /*
352 * Get supplicant MAC address from the User-Name
353 */
354 if (fr_base16_decode(NULL, &FR_DBUFF_TMP(s_mac, sizeof(s_mac)),
355 &FR_SBUFF_IN(env->username.vb_strvalue, env->username.vb_length), false) != 6) {
356 RERROR("User-Name is not a recognizable hex MAC address");
358 }
359
360 if (env->calledstation.vb_length != 6) {
361 RERROR("Called-Station-MAC is not a recognizable MAC address");
363 }
364
365 ap_mac = env->calledstation.vb_octets;
366
367 /*
368 * Sort the MACs
369 */
370 if (memcmp(s_mac, ap_mac, 6) <= 0) {
371 min_mac = s_mac;
372 max_mac = ap_mac;
373 } else {
374 min_mac = ap_mac;
375 max_mac = s_mac;
376 }
377
378 eapol = (eapol_attr_t const *) env->key_msg.vb_octets;
379
380 /*
381 * Get supplicant nonce and AP nonce.
382 *
383 * Then sort the nonces.
384 */
385 snonce = env->key_msg.vb_octets + 17;
386 if (memcmp(snonce, env->anonce.vb_octets, 32) <= 0) {
387 min_nonce = snonce;
388 max_nonce = env->anonce.vb_octets;
389 } else {
390 min_nonce = env->anonce.vb_octets;
391 max_nonce = snonce;
392 }
393
394 /*
395 * Create the base message which we will hash.
396 */
397 memcpy(message, "Pairwise key expansion", sizeof("Pairwise key expansion")); /* including trailing NUL */
398 p = &message[sizeof("Pairwise key expansion")];
399
400 memcpy(p, min_mac, 6);
401 memcpy(p + 6, max_mac, 6);
402 p += 12;
403
404 memcpy(p, min_nonce, 32);
405 memcpy(p + 32, max_nonce, 32);
406 p += 64;
407 *p = '\0';
408 fr_assert(sizeof(message) == (p + 1 - message));
409
410 /*
411 * If we're caching, then check the cache first, before
412 * trying the file. This check allows us to avoid the
413 * PMK calculation in many situations, as that can be
414 * expensive.
415 */
416 if (inst->cache_size) {
417 pthread_mutex_lock(&inst->mutable->mutex);
418 entry = dpsk_cache_find(request, inst, pmk, sizeof(pmk), &env->ssid, s_mac);
419 if (entry) {
420 psk_identity = entry->identity;
421 psk = entry->psk;
422 psk_len = entry->psk_len;
423 pthread_mutex_unlock(&inst->mutable->mutex);
424 goto make_digest;
425 }
426 pthread_mutex_unlock(&inst->mutable->mutex);
427 }
428
429 /*
430 * No cache, or no cache entry. Look for an external PMK
431 * taken from a database.
432 */
433stage1:
434 stage = 1;
435
436 if (env->masterkey.type == FR_TYPE_OCTETS) {
437 if (env->masterkey.vb_length != sizeof(pmk)) {
438 RWARN("%s has incorrect length (%zu != %zu) - ignoring it", env->masterkey_tmpl->name,
439 env->masterkey.vb_length, sizeof(pmk));
440 } else {
441 RDEBUG2("Using %s", env->masterkey_tmpl->name);
442 memcpy(pmk, env->masterkey.vb_octets, sizeof(pmk));
443 goto make_digest;
444 }
445 }
446
447 /*
448 * No external PMK. Try an external PSK.
449 */
450 if (env->psk.type == FR_TYPE_STRING) {
451 RDEBUG3("Trying %s", env->psk_tmpl->name);
452 if (generate_pmk(request, pmk, sizeof(pmk), &env->ssid, env->psk.vb_strvalue, env->psk.vb_length) < 0) {
453 fr_assert(!fp);
455 }
456
457 if (env->psk_identity.type == FR_TYPE_STRING) {
458 psk_identity = env->psk_identity.vb_strvalue;
459 } else {
460 psk_identity = env->username.vb_strvalue;
461 }
462
463 psk = env->psk.vb_strvalue;
464 psk_len = env->psk.vb_length;
465
466 goto make_digest;
467 }
468
469 /*
470 * No external PSK was found. If there's no file, then
471 * we can't do anything else.
472 */
473stage2:
474 stage = 2;
475
476 if (!filename) {
477 RERROR("No %s was found, and no 'filename' was configured", env->psk_tmpl->name);
479 }
480
481 /*
482 * If there's an PSK from an external database, then we
483 * never read the filename.
484 */
485 if (filename) {
486 char token_mac[256];
487 char buffer[1024];
488 fr_sbuff_t sbuff;
490 size_t len;
491 fr_sbuff_term_t const terms = FR_SBUFF_TERMS(L("\n"),L("\r"),L(","));
492 fr_sbuff_term_t const quoted_terms = FR_SBUFF_TERMS(L("\""));
493 bool quoted = false;
494
495 RDEBUG3("Looking for PSK in file %s", filename);
496
497 fp = fopen(filename, "r");
498 if (!fp) {
499 REDEBUG("Failed opening %s - %s", filename, fr_syserror(errno));
501 }
502
503 fr_sbuff_init_file(&sbuff, &fctx, buffer, sizeof(buffer), fp, SIZE_MAX);
504
505stage2a:
506 lineno++;
507 fr_sbuff_adv_past_whitespace(&sbuff, SIZE_MAX, NULL);
508 quoted = fr_sbuff_next_if_char(&sbuff, '"');
509 len = fr_sbuff_out_bstrncpy_until(&FR_SBUFF_OUT(token_identity, sizeof(token_identity)), &sbuff,
510 sizeof(token_identity), quoted ? &quoted_terms : &terms, NULL);
511 if (len == 0) {
512 RDEBUG("Failed to find matching PSK or MAC in %s", filename);
513 fail_file:
514 fclose(fp);
516 }
517 if (quoted) {
518 fr_sbuff_next_if_char(&sbuff, '"');
519 fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL);
520 }
521
522 if (!fr_sbuff_next_if_char(&sbuff, ',')) {
523 RDEBUG("%s[%d] Failed to find ',' after identity", filename, lineno);
524 goto fail_file;
525 }
526
527 fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL);
528 quoted = fr_sbuff_next_if_char(&sbuff, '"');
529 len = fr_sbuff_out_bstrncpy_until(&FR_SBUFF_OUT(token_psk, sizeof(token_psk)), &sbuff,
530 sizeof(token_identity), quoted ? &quoted_terms : &terms, NULL);
531 if (len == 0) {
532 RDEBUG("%s[%d] Failed parsing PSK", filename, lineno);
533 goto fail_file;
534 }
535 if (quoted) {
536 fr_sbuff_next_if_char(&sbuff, '"');
537 fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL);
538 }
539
540 /*
541 * The MAC is optional. If there is a MAC, we
542 * loop over the file until we find a matching
543 * one.
544 */
545 if (fr_sbuff_next_if_char(&sbuff, ',')) {
546
547 fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL);
548 quoted = fr_sbuff_next_if_char(&sbuff, '"');
549 len = fr_sbuff_out_bstrncpy_until(&FR_SBUFF_OUT(token_mac, sizeof(token_mac)), &sbuff,
550 sizeof(token_identity), quoted ? &quoted_terms : &terms, NULL);
551 if (len == 0) {
552 RERROR("%s[%d] Failed parsing MAC", filename, lineno);
553 goto fail_file;
554 }
555
556 /*
557 * See if the MAC matches. If not, skip
558 * this entry. That's a basic negative cache.
559 */
560 if ((len != 12) ||
561 (fr_base16_decode(NULL, &FR_DBUFF_TMP((uint8_t *)token_mac, 6),
562 &FR_SBUFF_IN(token_mac, 12), false) != 6)) {
563 RERROR("%s[%d] Failed parsing MAC", filename, lineno);
564 goto fail_file;
565 }
566 if (quoted) fr_sbuff_next_if_char(&sbuff, '"');
567
568 /*
569 * The MAC doesn't match, don't even bother trying to generate the PMK.
570 */
571 if (memcmp(s_mac, token_mac, 6) != 0) {
572 goto stage2a;
573 }
574
575 RDEBUG3("Found matching MAC");
576 stage = 3;
577 }
578
579 /*
580 * Generate the PMK using the SSID, this MAC, and the PSK we just read.
581 */
582 psk = token_psk;
583 psk_len = strlen(token_psk);
584 psk_identity = token_identity;
585
586 RDEBUG3("%s[%d] Trying PSK %s", filename, lineno, token_psk);
587 if (generate_pmk(request, pmk, sizeof(pmk), &env->ssid, psk, psk_len) < 0) {
588 goto fail_file;
589 }
590 }
591
592 /*
593 * HMAC = HMAC_SHA1(pmk, message);
594 *
595 * We need the first 16 octets of this.
596 */
597make_digest:
598 digest_len = sizeof(digest);
599#ifdef __COVERITY__
600 /*
601 * Coverity doesn't see that HMAC will populate digest
602 */
603 memset(digest, 0, digest_len);
604#endif
605 HMAC(EVP_sha1(), pmk, sizeof(pmk), message, sizeof(message), digest, &digest_len);
606
607 RHEXDUMP3(message, sizeof(message), "message:");
608 RHEXDUMP3(pmk, sizeof(pmk), "pmk :");
609 RHEXDUMP3(digest, 16, "kck :");
610
611 /*
612 * Create the frame with the middle field zero, and hash it with the KCK digest we calculated from the key expansion.
613 */
614 memcpy(frame, env->key_msg.vb_octets, env->key_msg.vb_length);
615 zeroed = (eapol_attr_t *) &frame[0];
616 memset(&zeroed->frame.mic[0], 0, 16);
617
618 RHEXDUMP3(frame, env->key_msg.vb_length, "zeroed:");
619
620 mic_len = sizeof(mic);
621#ifdef __COVERITY__
622 /*
623 * Coverity doesn't see that HMAC will populate mic
624 */
625 memset(mic, 0, mic_len);
626#endif
627 HMAC(EVP_sha1(), digest, 16, frame, env->key_msg.vb_length, mic, &mic_len);
628
629 /*
630 * The MICs don't match.
631 */
632 if (memcmp(&eapol->frame.mic[0], mic, 16) != 0) {
633 RDEBUG3("Stage %d", stage);
634 RHEXDUMP3(mic, 16, "calculated mic:");
635 RHEXDUMP3(eapol->frame.mic, 16, "packet mic :");
636
637 psk_identity = NULL;
638 psk = NULL;
639 psk_len = 0;
640
641 /*
642 * Found a cached entry, but it didn't match. Go
643 * check external PMK / PSK.
644 */
645 if (stage == 0) {
646 fr_assert(entry != NULL);
647 fr_rb_delete(&inst->mutable->cache, entry); /* locks and unlinks the entry */
648 entry = NULL;
649 goto stage1;
650 }
651
652 /*
653 * Found an external PMK or PSK, but it didn't
654 * match. Go check the file.
655 */
656 if (stage == 1) {
657 if (env->psk.type == FR_TYPE_STRING) RWARN("%s did not match", env->psk_tmpl->name);
658
659 if (filename) {
660 RDEBUG("Checking file %s for PSK and MAC", filename);
661 goto stage2;
662 }
663
664 RWARN("No 'filename' was configured.");
666 }
667
668 /*
669 * The file is open, so we keep reading it until
670 * we find a matching entry.
671 */
672 fr_assert(fp);
673
674 if (stage == 2) goto stage2a;
675
676 fclose(fp);
677
678 /*
679 * We found a PSK associated with this MAC in the
680 * file. But it didn't match, so we're done.
681 */
682 fr_assert(stage == 3);
683
684 RWARN("Found matching MAC in %s, but the PSK does not match", filename);
686 }
687
688 /*
689 * We found a matching PSK. If we read it from the file,
690 * then close the file, and ensure that we return
691 * UPDATED. This tells the caller to write the entry
692 * into the database, so that we don't need to scan the
693 * file again.
694 */
695 if (fp) {
696 rcode = RLM_MODULE_UPDATED;
697 fr_assert(psk == token_psk);
698 fr_assert(psk_identity == token_identity);
699 fclose(fp);
700 }
701
702 /*
703 * Extend the lifetime of the cache entry, or add the
704 * cache entry if necessary. We only add / update the
705 * cache entry if the PSK was not found in a VP.
706 *
707 * If the caller gave us only a PMK, then don't cache anything.
708 */
709 if (inst->cache_size && psk && psk_identity) {
710 rlm_dpsk_cache_t my_entry;
711
712 /*
713 * We've found an entry. Just update it.
714 */
715 if (entry) goto update_entry;
716
717 /*
718 * No cached entry, or the PSK in the cached
719 * entry didn't match. We need to create one.
720 */
721 memcpy(my_entry.mac, s_mac, sizeof(my_entry.mac));
722 memcpy(&my_entry.ssid, env->ssid.vb_octets, sizeof(my_entry.ssid)); /* const ptr issues */
723 my_entry.ssid_len = env->ssid.vb_length;
724
725 entry = fr_rb_find(&inst->mutable->cache, &my_entry);
726 if (!entry) {
727 /*
728 * Maybe there are oo many entries in the
729 * cache. If so, delete the oldest one.
730 */
731 if (fr_rb_num_elements(&inst->mutable->cache) > inst->cache_size) {
732 pthread_mutex_lock(&inst->mutable->mutex);
733 entry = fr_dlist_head(&inst->mutable->head);
734 pthread_mutex_unlock(&inst->mutable->mutex);
735
736 fr_rb_delete(&inst->mutable->cache, entry); /* locks and unlinks the entry */
737 }
738
739 MEM(entry = talloc_zero(&inst->mutable->cache, rlm_dpsk_cache_t));
740
741 memcpy(entry->mac, s_mac, sizeof(entry->mac));
742 memcpy(entry->pmk, pmk, sizeof(entry->pmk));
743
744 entry->inst = inst;
745
746 /*
747 * Save the SSID, PSK, and PSK identity in the cache entry.
748 */
749 MEM(entry->ssid = talloc_memdup(entry, env->ssid.vb_octets, env->ssid.vb_length));
750 entry->ssid_len = env->ssid.vb_length;
751
752 MEM(entry->psk = talloc_strdup(entry, psk));
753 entry->psk_len = psk_len;
754
755 entry->identity_len = strlen(psk_identity);
756 MEM(entry->identity = talloc_strdup(entry, psk_identity));
757
758 /*
759 * Cache it.
760 */
761 if (!fr_rb_insert(&inst->mutable->cache, entry)) {
762 TALLOC_FREE(entry);
763 goto update_attributes;
764 }
765 RDEBUG3("Cache entry saved");
766 }
767
768 update_entry:
769 entry->expires = fr_time_add(fr_time(), inst->cache_lifetime);
770 pthread_mutex_lock(&inst->mutable->mutex);
771 if (fr_dlist_entry_in_list(&entry->dlist)) fr_dlist_remove(&inst->mutable->head, entry);
772 fr_dlist_insert_tail(&inst->mutable->head, entry);
773 pthread_mutex_unlock(&inst->mutable->mutex);
774
775 /*
776 * Add the PSK to the reply items, if it was cached.
777 */
778 if (entry->psk) {
779 tmpl_t psk_rhs;
780 map_t psk_map = {
781 .lhs = env->psk_dest_tmpl,
782 .op = T_OP_SET,
783 .rhs = &psk_rhs
784 };
785
787 fr_value_box_bstrndup_shallow(&psk_map.rhs->data.literal,
788 NULL, entry->psk, entry->psk_len, true);
789 if (map_to_request(request, &psk_map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
790 }
791 }
792
793update_attributes:
794 /*
795 * We found a cache entry, or an external PSK. Don't
796 * create new attributes.
797 */
798 if (rcode == RLM_MODULE_OK) RETURN_UNLANG_OK;
799
800 fr_assert(psk != NULL);
801 fr_assert(psk_identity != NULL);
802
803 {
804 tmpl_t rhs;
805 map_t map = {
806 .lhs = env->psk_dest_tmpl,
807 .op = T_OP_SET,
808 .rhs = &rhs
809 };
810
811 RDEBUG2("Creating %s and %s", env->psk_dest_tmpl->name, env->psk_identity_dest_tmpl->name);
813 fr_value_box_bstrndup_shallow(&map.rhs->data.literal, NULL, psk, psk_len, true);
814 if (map_to_request(request, &map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
815
816 map.lhs = env->psk_identity_dest_tmpl;
817 fr_value_box_bstrndup_shallow(&map.rhs->data.literal, NULL, psk_identity, strlen(psk_identity), true);
818 if (map_to_request(request, &map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
819 }
820
822}
823
824static xlat_arg_parser_t const dpsk_pmk_xlat_arg[] = {
825 { .required = true, .concat = true, .type = FR_TYPE_STRING },
826 { .required = true, .concat = true, .type = FR_TYPE_STRING },
828};
829
830/** xlat to generate the PMK from SSID and Pre-Shared-Key
831 *
832 * Example:
833 @verbatim
834 %dpsk.pmk(Calling-Station-SSID, Pre-Shared-Key)
835 @endverbatim
836 *
837 * @ingroup xlat_functions
838 */
839static xlat_action_t dpsk_pmk_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, UNUSED xlat_ctx_t const *xctx,
840 request_t *request, fr_value_box_list_t *in)
841{
842 fr_value_box_t *ssid, *psk, *vb;
843 uint8_t buffer[32];
844
845 XLAT_ARGS(in, &ssid, &psk);
846
847 if (PKCS5_PBKDF2_HMAC_SHA1(psk->vb_strvalue, psk->vb_length,
848 (const unsigned char *) ssid->vb_strvalue, ssid->vb_length,
849 4096, sizeof(buffer), buffer) == 0) {
850 RERROR("Failed calling OpenSSL to calculate the PMK");
851 return XLAT_ACTION_FAIL;
852 }
853
854 MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_OCTETS, NULL));
855 fr_value_box_memdup(vb, vb, NULL, buffer, sizeof(buffer), true);
856
858
859 return XLAT_ACTION_DONE;
860}
861
862static int mod_load(void)
863{
864 xlat_t *xlat;
865 if (unlikely((xlat = xlat_func_register(NULL, "dpsk.pmk", dpsk_pmk_xlat, FR_TYPE_OCTETS)) == NULL)) return -1;
866 xlat_func_args_set(xlat, dpsk_pmk_xlat_arg);
867
868 return 0;
869}
870
871static void mod_unload(void)
872{
873 xlat_func_unregister("dpsk.pmk");
874}
875
876static int8_t cache_entry_cmp(void const *one, void const *two)
877{
878 rlm_dpsk_cache_t const *a = (rlm_dpsk_cache_t const *) one;
879 rlm_dpsk_cache_t const *b = (rlm_dpsk_cache_t const *) two;
880 int rcode;
881
882 rcode = memcmp(a->mac, b->mac, sizeof(a->mac));
883 if (rcode != 0) return rcode;
884
885 if (a->ssid_len < b->ssid_len) return -1;
886 if (a->ssid_len > b->ssid_len) return +1;
887
888 return CMP(memcmp(a->ssid, b->ssid, a->ssid_len), 0);
889}
890
891static void cache_entry_free(void *data)
892{
894
895 pthread_mutex_lock(&entry->inst->mutable->mutex);
897 pthread_mutex_unlock(&entry->inst->mutable->mutex);
898
899 talloc_free(entry);
900}
901
902static int mod_detach(const module_detach_ctx_t *mctx)
903{
904 rlm_dpsk_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_dpsk_t);
905
906 if (!inst->cache_size) return 0;
907
908 pthread_mutex_destroy(&inst->mutable->mutex);
909
910 talloc_free(inst->mutable);
911
912 return 0;
913}
914#endif
915
916static int mod_instantiate(module_inst_ctx_t const *mctx)
917{
918#ifdef WITH_TLS
919 rlm_dpsk_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_dpsk_t);
920
921 inst->auth_type = fr_dict_enum_by_name(attr_auth_type, mctx->mi->name, -1);
922 if (!inst->auth_type) {
923 WARN("Failed to find 'authenticate %s {...}' section. DPSK will likely not work",
924 mctx->mi->name);
925 }
926
927 /*
928 * We can still use a cache if we're getting PSKs from a
929 * database. The PMK calculation can take time, so
930 * caching the PMK still saves us time.
931 */
932 if (!inst->cache_size) return 0;
933
934 FR_INTEGER_BOUND_CHECK("cache_size", inst->cache_size, <=, ((uint32_t) 1) << 16);
935
936 FR_TIME_DELTA_BOUND_CHECK("cache_lifetime", inst->cache_lifetime, <=, fr_time_delta_from_sec(7 * 86400));
937 FR_TIME_DELTA_BOUND_CHECK("cache_lifetime", inst->cache_lifetime, >=, fr_time_delta_from_sec(3600));
938
939 inst->mutable = talloc_zero(NULL, rlm_dpsk_mutable_t);
940
942
943 fr_dlist_init(&inst->mutable->head, rlm_dpsk_cache_t, dlist);
944
945 if (pthread_mutex_init(&inst->mutable->mutex, NULL) < 0) {
946 cf_log_err(mctx->mi->conf, "Failed creating mutex");
947 return -1;
948 }
949
950 return 0;
951#else
952 cf_log_err(mctx->mi->conf, "rlm_dpsk requires OpenSSL");
953 return 0;
954#endif
955}
956
957/*
958 * The module name should be the only globally exported symbol.
959 * That is, everything else should be 'static'.
960 *
961 * If the module needs to temporarily modify it's instantiation
962 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
963 * The server will then take care of ensuring that the module
964 * is single-threaded.
965 */
968 .common = {
969 .magic = MODULE_MAGIC_INIT,
970 .name = "dpsk",
971 .inst_size = sizeof(rlm_dpsk_t),
972 .instantiate = mod_instantiate,
974#ifdef WITH_TLS
975 .detach = mod_detach,
976 .onload = mod_load,
977 .unload = mod_unload,
978#endif
979 },
980#ifdef WITH_TLS
981 .method_group = {
982 .bindings = (module_method_binding_t[]){
983 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_authorize, .method_env = &dpsk_autz_method_env },
984 { .section = SECTION_NAME("authenticate", CF_IDENT_ANY), .method = mod_authenticate, .method_env = &dpsk_auth_method_env },
985
987 }
988 }
989#endif
990};
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:576
#define fr_base16_decode(_err, _out, _in, _no_trailing)
Definition base16.h:92
#define RCSID(id)
Definition build.h:506
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:228
#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:113
#define unlikely(_x)
Definition build.h:402
#define UNUSED
Definition build.h:336
#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:657
#define FR_INTEGER_BOUND_CHECK(_name, _var, _op, _bound)
Definition cf_parse.h:517
#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:280
#define FR_TIME_DELTA_BOUND_CHECK(_name, _var, _op, _bound)
Definition cf_parse.h:528
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:594
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:285
#define CF_IDENT_ANY
Definition cf_util.h:75
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:522
static int fr_dcursor_append(fr_dcursor_t *cursor, void *v)
Insert a single item at the end of the list.
Definition dcursor.h:406
#define MEM(x)
Definition debug.h:46
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:292
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:305
#define DICT_AUTOLOAD_TERMINATOR
Definition dict.h:311
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:3701
static fr_slen_t in
Definition dict.h:882
Specifies an attribute which must be present for the module to function.
Definition dict.h:291
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:304
Value of an enumerated attribute.
Definition dict.h:253
#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:242
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:468
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition dlist.h:620
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:145
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:128
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:360
Head of a doubly linked list.
Definition dlist.h:51
Entry in a doubly linked list.
Definition dlist.h:41
talloc_free(hp)
#define RDEBUG3(fmt,...)
Definition log.h:355
#define RWARN(fmt,...)
Definition log.h:309
#define RERROR(fmt,...)
Definition log.h:310
#define RHEXDUMP3(_data, _len, _fmt,...)
Definition log.h:717
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:1604
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:1884
#define fr_time()
Definition event.c:60
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:418
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
static const conf_parser_t config[]
Definition base.c:163
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:37
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
#define RDEBUG(fmt,...)
#define WARN(fmt,...)
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:178
The main red black tree structure.
Definition rb.h:71
#define RETURN_UNLANG_UPDATED
Definition rcode.h:70
#define RETURN_UNLANG_FAIL
Definition rcode.h:63
#define RETURN_UNLANG_REJECT
Definition rcode.h:62
#define RETURN_UNLANG_OK
Definition rcode.h:64
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:44
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:49
@ RLM_MODULE_UPDATED
OK (pairs modified).
Definition rcode.h:55
#define RETURN_UNLANG_NOOP
Definition rcode.h:69
static int mod_detach(module_detach_ctx_t const *mctx)
Definition rlm_always.c:136
static void cache_entry_free(rlm_cache_entry_t *c)
Locate a cache entry in memcached.
static int8_t cache_entry_cmp(void const *one, void const *two)
Compare two entries by key.
static unlang_action_t mod_authorize(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition rlm_chap.c:173
static unlang_action_t mod_authenticate(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition rlm_chap.c:225
uint8_t pmk[32]
Definition rlm_dpsk.c:84
tmpl_t * psk_identity_dest_tmpl
Definition rlm_dpsk.c:160
uint8_t rsc[8]
Definition rlm_dpsk.c:67
tmpl_t * psk_dest_tmpl
Definition rlm_dpsk.c:159
fr_rb_tree_t cache
Definition rlm_dpsk.c:101
uint16_t information
Definition rlm_dpsk.c:62
fr_value_box_t key_msg
Definition rlm_dpsk.c:150
fr_rb_node_t node
Definition rlm_dpsk.c:82
size_t ssid_len
Definition rlm_dpsk.c:87
struct rlm_dpsk_s rlm_dpsk_t
Definition rlm_dpsk.c:79
module_rlm_t rlm_dpsk
Definition rlm_dpsk.c:967
uint8_t reserved[8]
Definition rlm_dpsk.c:68
fr_value_box_t username
Definition rlm_dpsk.c:152
static fr_dict_t const * dict_freeradius
Definition rlm_dpsk.c:116
tmpl_t * key_msg_tmpl
Definition rlm_dpsk.c:142
uint8_t mac[6]
Definition rlm_dpsk.c:83
fr_value_box_t masterkey
Definition rlm_dpsk.c:154
fr_dlist_head_t head
Definition rlm_dpsk.c:104
static fr_dict_attr_t const * attr_auth_type
Definition rlm_dpsk.c:124
fr_dict_autoload_t rlm_dpsk_dict[]
Definition rlm_dpsk.c:119
fr_value_box_t psk_identity
Definition rlm_dpsk.c:158
fr_value_box_t ssid
Definition rlm_dpsk.c:146
rlm_dpsk_t const * inst
Definition rlm_dpsk.c:97
fr_dict_attr_autoload_t rlm_dpsk_dict_attr[]
Definition rlm_dpsk.c:127
pthread_mutex_t mutex
Definition rlm_dpsk.c:103
uint16_t length
Definition rlm_dpsk.c:63
uint8_t * ssid
Definition rlm_dpsk.c:86
uint8_t mic[16]
Definition rlm_dpsk.c:69
uint8_t iv[16]
Definition rlm_dpsk.c:66
uint8_t nonce[32]
Definition rlm_dpsk.c:65
tmpl_t * masterkey_tmpl
Definition rlm_dpsk.c:155
char * identity
Definition rlm_dpsk.c:89
uint8_t descriptor
Definition rlm_dpsk.c:61
uint32_t cache_size
Definition rlm_dpsk.c:110
fr_dlist_t dlist
Definition rlm_dpsk.c:96
fr_value_box_t filename
Definition rlm_dpsk.c:161
eapol_key_frame_t frame
Definition rlm_dpsk.c:75
tmpl_t * key_msg_tmpl
Definition rlm_dpsk.c:151
uint8_t header[4]
Definition rlm_dpsk.c:74
static const conf_parser_t module_config[]
Definition rlm_dpsk.c:133
rlm_dpsk_mutable_t * mutable
Definition rlm_dpsk.c:113
size_t identity_len
Definition rlm_dpsk.c:90
uint16_t data_len
Definition rlm_dpsk.c:70
fr_dict_enum_value_t const * auth_type
Definition rlm_dpsk.c:108
fr_time_delta_t cache_lifetime
Definition rlm_dpsk.c:111
fr_value_box_t calledstation
Definition rlm_dpsk.c:153
static int mod_instantiate(module_inst_ctx_t const *mctx)
Definition rlm_dpsk.c:916
fr_value_box_t anonce
Definition rlm_dpsk.c:148
fr_value_box_t psk
Definition rlm_dpsk.c:156
uint8_t replay_counter[8]
Definition rlm_dpsk.c:64
fr_time_t expires
Definition rlm_dpsk.c:94
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:2128
#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:190
#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:148
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:39
char const * name
Instance name e.g. user_database.
Definition module.h:357
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:351
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:293
#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
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:110
#define talloc_strdup(_ctx, _str)
Definition talloc.h:142
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:118
@ T_OP_SET
Definition token.h:82
@ T_DOUBLE_QUOTED_STRING
Definition token.h:119
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 consumed 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:4910
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:5064
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:644
static fr_slen_t data
Definition value.h:1340
int nonnull(2, 5))
static size_t char ** out
Definition value.h:1030
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