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