2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Register many xlat expansions including the expr expansion.
21 * @copyright 2001,2006 The FreeRADIUS server project
22 * @copyright 2002 Alan DeKok <aland@ox.org>
24 #include <freeradius-devel/ident.h>
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/md5.h>
29 #include <freeradius-devel/base64.h>
30 #include <freeradius-devel/modules.h>
37 * Define a structure for our module configuration.
39 typedef struct rlm_expr_t {
44 static const CONF_PARSER module_config[] = {
45 {"safe-characters", PW_TYPE_STRING_PTR,
46 offsetof(rlm_expr_t, allowed_chars), NULL,
47 "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
48 {NULL, -1, 0, NULL, NULL}
51 typedef enum expr_token_t {
64 typedef struct expr_map_t {
69 static expr_map_t map[] =
72 {'-', TOKEN_SUBTRACT },
74 {'*', TOKEN_MULTIPLY },
75 {'%', TOKEN_REMAINDER },
82 * Lookup tables for randstr char classes
84 static char randstr_punc[] = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
85 static char randstr_salt[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz/.";
87 static int get_number(REQUEST *request, const char **string, int64_t *answer)
96 * Loop over the input.
101 for (p = *string; *p != '\0'; /* nothing */) {
109 * Discover which token it is.
112 for (i = 0; map[i].token != TOKEN_LAST; i++) {
113 if (*p == map[i].op) {
114 if (this != TOKEN_NONE) {
115 RDEBUG2("Invalid operator at \"%s\"", p);
126 * Found the algebraic operator. Get the next number.
133 * End of a group. Stop.
136 if (this != TOKEN_NONE) {
137 RDEBUG2("Trailing operator before end sub-expression at \"%s\"", p);
145 * Start of a group. Call ourselves recursively.
150 found = get_number(request, &p, &x);
156 * No algrebraic operator found, the next thing
159 * If it isn't, then we die.
161 if ((*p < '0') || (*p > '9')) {
162 RDEBUG2("Not a number at \"%s\"", p);
167 * This is doing it the hard way, but it also allows
168 * us to increment 'p'.
171 while ((*p >= '0') && (*p <= '9')) {
194 result = 0; /* we don't have NaN for integers */
200 case TOKEN_REMAINDER:
202 result = 0; /* we don't have NaN for integers */
222 * We've used this token.
228 * And return the answer to the caller.
236 * Do xlat of strings!
238 static size_t expr_xlat(void *instance, REQUEST *request, const char *fmt,
239 char *out, size_t outlen)
243 rlm_expr_t *inst = instance;
247 inst = inst; /* -Wunused */
250 * Do an xlat on the provided string (nice recursive operation).
252 if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
253 radlog(L_ERR, "rlm_expr: xlat failed.");
259 rcode = get_number(request, &p, &result);
265 * We MUST have eaten the entire input string.
268 RDEBUG2("Failed at %s", p);
272 snprintf(out, outlen, "%ld", (long int) result);
277 * @brief Generate a random integer value
280 static size_t rand_xlat(UNUSED void *instance, REQUEST *request, const char *fmt,
281 char *out, size_t outlen)
287 * Do an xlat on the provided string (nice recursive operation).
289 if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
290 radlog(L_ERR, "rlm_expr: xlat failed.");
295 result = atoi(buffer);
298 * Too small or too big.
300 if (result <= 0) return 0;
301 if (result >= (1 << 30)) result = (1 << 30);
303 result *= fr_rand(); /* 0..2^32-1 */
306 snprintf(out, outlen, "%ld", (long int) result);
311 * @brief Generate a string of random chars
313 * Build strings of random chars, useful for generating tokens and passcodes
314 * Format similar to String::Random.
316 static size_t randstr_xlat(UNUSED void *instance, REQUEST *request,
317 const char *fmt, char *out, size_t outlen)
322 size_t freespace = outlen;
325 if (outlen <= 1) return 0;
328 * Do an xlat on the provided string (nice recursive operation).
330 len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
332 radlog(L_ERR, "rlm_expr: xlat failed.");
338 while ((len-- > 0) && (--freespace > 0)) {
345 *out++ = 'a' + (result % 26);
352 *out++ = 'A' + (result % 26);
359 *out++ = '0' + (result % 10);
366 *out++ = randstr_salt[result % (sizeof(randstr_salt) - 3)];
373 *out++ = randstr_punc[result % (sizeof(randstr_punc) - 1)];
377 * Alpa numeric + punctuation
380 *out++ = '!' + (result % 95);
384 * Alpha numeric + salt chars './'
387 *out++ = randstr_salt[result % (sizeof(randstr_salt) - 1)];
391 * Binary data as hexits (we don't really support
392 * non printable chars).
398 snprintf(out, 3, "%02x", result % 256);
400 /* Already decremented */
407 "rlm_expr: invalid character class '%c'",
419 return outlen - freespace;
423 * @brief URLencode special characters
425 * Example: "%{urlquote:http://example.org/}" == "http%3A%47%47example.org%47"
427 static size_t urlquote_xlat(UNUSED void *instance, REQUEST *request,
428 const char *fmt, char *out, size_t outlen)
432 size_t freespace = outlen;
435 if (outlen <= 1) return 0;
437 len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
439 radlog(L_ERR, "rlm_expr: xlat failed.");
445 while ((len-- > 0) && (--freespace > 0)) {
462 snprintf(out, 4, "%%%02x", *p++); /* %xx */
464 /* Already decremented */
472 return outlen - freespace;
476 * @brief Equivalent to the old safe-characters functionality in rlm_sql
478 * Example: "%{escape:<img>foo.jpg</img>}" == "=60img=62foo.jpg=60=/img=62"
480 static size_t escape_xlat(UNUSED void *instance, REQUEST *request,
481 const char *fmt, char *out, size_t outlen)
483 rlm_expr_t *inst = instance;
486 size_t freespace = outlen;
489 if (outlen <= 1) return 0;
491 len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
493 radlog(L_ERR, "rlm_expr: xlat failed.");
499 while ((len-- > 0) && (--freespace > 0)) {
501 * Non-printable characters get replaced with their
502 * mime-encoded equivalents.
504 if ((*p > 31) && strchr(inst->allowed_chars, *p)) {
512 snprintf(out, 4, "=%02X", *p++);
514 /* Already decremented */
521 return outlen - freespace;
525 * @brief Convert a string to lowercase
527 * Example "%{lc:Bar}" == "bar"
529 * Probably only works for ASCII
531 static size_t lc_xlat(UNUSED void *instance, REQUEST *request,
532 const char *fmt, char *out, size_t outlen)
537 if (outlen <= 1) return 0;
539 if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
544 for (p = buffer, q = out; *p != '\0'; p++, outlen--) {
545 if (outlen <= 1) break;
547 *(q++) = tolower((int) *p);
556 * @brief Convert a string to uppercase
558 * Example: "%{uc:Foo}" == "FOO"
560 * Probably only works for ASCII
562 static size_t uc_xlat(UNUSED void *instance, REQUEST *request,
563 const char *fmt, char *out, size_t outlen)
568 if (outlen <= 1) return 0;
570 if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
575 for (p = buffer, q = out; *p != '\0'; p++, outlen--) {
576 if (outlen <= 1) break;
578 *(q++) = toupper((int) *p);
587 * @brief Calculate the MD5 hash of a string.
589 * Example: "%{md5:foo}" == "acbd18db4cc2f85cedef654fccc4a4d8"
591 static size_t md5_xlat(UNUSED void *instance, REQUEST *request,
592 const char *fmt, char *out, size_t outlen)
599 if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
605 fr_MD5Update(&ctx, (void *) buffer, strlen(buffer));
606 fr_MD5Final(digest, &ctx);
609 snprintf(out, outlen, "md5_overflow");
613 for (i = 0; i < 16; i++) {
614 snprintf(out + i * 2, 3, "%02x", digest[i]);
621 * @brief Encode string as base64
623 * Example: "%{tobase64:foo}" == "Zm9v"
625 static size_t base64_xlat(UNUSED void *instance, REQUEST *request,
626 const char *fmt, char *out, size_t outlen)
631 len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
634 * We can accurately calculate the length of the output string
635 * if it's larger than outlen, the output would be useless so abort.
637 if (!len || ((FR_BASE64_ENC_LENGTH(len) + 1) > outlen)) {
638 radlog(L_ERR, "rlm_expr: xlat failed.");
643 fr_base64_encode((uint8_t *) buffer, len, out, outlen);
649 * @brief Convert base64 to hex
651 * Example: "%{base64tohex:Zm9v}" == "666f6f"
653 static size_t base64_to_hex_xlat(UNUSED void *instance, REQUEST *request,
654 const char *fmt, char *out, size_t outlen)
661 size_t declen = sizeof(decbuf);
662 size_t freespace = outlen;
665 len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
668 radlog(L_ERR, "rlm_expr: xlat failed.");
673 if (!fr_base64_decode(buffer, len, decbuf, &declen)) {
674 radlog(L_ERR, "rlm_expr: base64 string invalid");
680 while ((declen-- > 0) && (--freespace > 0)) {
684 snprintf(out, 3, "%02x", *p++);
686 /* Already decremented */
691 return outlen - freespace;
696 * Detach a instance free all ..
698 static int expr_detach(void *instance)
700 rlm_expr_t *inst = instance;
702 xlat_unregister(inst->xlat_name, expr_xlat, instance);
703 pair_builtincompare_detach();
708 * Do any per-module initialization that is separate to each
709 * configured instance of the module. e.g. set up connections
710 * to external databases, read configuration files, set up
711 * dictionary entries, etc.
713 * If configuration information is given in the config section
714 * that must be referenced in later calls, store a handle to it
715 * in *instance otherwise put a null pointer there.
717 static int expr_instantiate(CONF_SECTION *conf, void **instance)
722 * Set up a storage area for instance data
724 *instance = inst = talloc_zero(conf, rlm_expr_t);
725 if (!inst) return -1;
727 if (cf_section_parse(conf, inst, module_config) < 0) {
732 inst->xlat_name = cf_section_name2(conf);
733 if (!inst->xlat_name) {
734 inst->xlat_name = cf_section_name1(conf);
736 xlat_register(inst->xlat_name, expr_xlat, inst);
738 xlat_register("rand", rand_xlat, inst);
739 xlat_register("randstr", randstr_xlat, inst);
740 xlat_register("urlquote", urlquote_xlat, inst);
741 xlat_register("escape", escape_xlat, inst);
742 xlat_register("tolower", lc_xlat, inst);
743 xlat_register("toupper", uc_xlat, inst);
744 xlat_register("md5", md5_xlat, inst);
745 xlat_register("tobase64", base64_xlat, inst);
746 xlat_register("base64tohex", base64_to_hex_xlat, inst);
749 * Initialize various paircompare functions
751 pair_builtincompare_init();
756 * The module name should be the only globally exported symbol.
757 * That is, everything else should be 'static'.
759 * If the module needs to temporarily modify it's instantiation
760 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
761 * The server will then take care of ensuring that the module
762 * is single-threaded.
764 module_t rlm_expr = {
767 RLM_TYPE_CHECK_CONFIG_SAFE, /* type */
768 expr_instantiate, /* instantiation */
769 expr_detach, /* detach */
771 NULL, /* authentication */
772 NULL, /* authorization */
773 NULL, /* pre-accounting */
774 NULL /* accounting */