summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_expr
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_expr')
-rw-r--r--src/modules/rlm_expr/all.mk2
-rw-r--r--src/modules/rlm_expr/paircmp.c231
-rw-r--r--src/modules/rlm_expr/rlm_expr.c1922
-rw-r--r--src/modules/rlm_expr/rlm_expr.h25
4 files changed, 2180 insertions, 0 deletions
diff --git a/src/modules/rlm_expr/all.mk b/src/modules/rlm_expr/all.mk
new file mode 100644
index 0000000..3b67160
--- /dev/null
+++ b/src/modules/rlm_expr/all.mk
@@ -0,0 +1,2 @@
+TARGET := rlm_expr.a
+SOURCES := rlm_expr.c paircmp.c
diff --git a/src/modules/rlm_expr/paircmp.c b/src/modules/rlm_expr/paircmp.c
new file mode 100644
index 0000000..cc69cc1
--- /dev/null
+++ b/src/modules/rlm_expr/paircmp.c
@@ -0,0 +1,231 @@
+/*
+ * paircmp.c Valuepair functions for various attributes
+ *
+ * Version: $Id$
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2000,2006 The FreeRADIUS server project
+ * Copyright 2000 Alan DeKok <aland@ox.org>
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
+#include "rlm_expr.h"
+
+/*
+ * Compare prefix/suffix.
+ *
+ * If they compare:
+ * - if PW_STRIP_USER_NAME is present in check_pairs,
+ * strip the username of prefix/suffix.
+ * - if PW_STRIP_USER_NAME is not present in check_pairs,
+ * add a PW_STRIPPED_USER_NAME to the request.
+ */
+static int presufcmp(UNUSED void *instance,
+ REQUEST *request,
+ UNUSED VALUE_PAIR *req,
+ VALUE_PAIR *check,
+ VALUE_PAIR *check_pairs,
+ UNUSED VALUE_PAIR **reply_pairs)
+{
+ VALUE_PAIR *vp;
+ char const *name;
+ char rest[MAX_STRING_LEN];
+ int len, namelen;
+ int ret = -1;
+
+ if (!request->username) {
+ return -1;
+ }
+
+ VERIFY_VP(request->username);
+ VERIFY_VP(check);
+ rad_assert(request->username->da->type == PW_TYPE_STRING);
+
+ name = request->username->vp_strvalue;
+
+#if 0 /* DEBUG */
+ printf("Comparing %s and %s, check->attr is %d\n", name, check->vp_strvalue, check->attribute);
+#endif
+
+ len = strlen(check->vp_strvalue);
+ if (check->da->vendor == 0) switch (check->da->attr) {
+ case PW_PREFIX:
+ ret = strncmp(name, check->vp_strvalue, len);
+ if (ret == 0)
+ strlcpy(rest, name + len, sizeof(rest));
+ break;
+ case PW_SUFFIX:
+ namelen = strlen(name);
+ if (namelen < len)
+ break;
+ ret = strcmp(name + namelen - len,
+ check->vp_strvalue);
+ if (ret == 0) {
+ strlcpy(rest, name, namelen - len + 1);
+ }
+ break;
+ }
+ if (ret != 0) {
+ return ret;
+ }
+
+ /*
+ * If Strip-User-Name == No, then don't do any more.
+ */
+ vp = fr_pair_find_by_num(check_pairs, PW_STRIP_USER_NAME, 0, TAG_ANY);
+ if (vp && !vp->vp_integer) return ret;
+
+ /*
+ * See where to put the stripped user name.
+ */
+ vp = fr_pair_find_by_num(check_pairs, PW_STRIPPED_USER_NAME, 0, TAG_ANY);
+ if (!vp) {
+ vp = radius_pair_create(request->packet, &request->packet->vps, PW_STRIPPED_USER_NAME, 0);
+ if (!vp) return ret;
+ request->username = vp;
+ }
+
+ fr_pair_value_strcpy(vp, rest);
+
+ return ret;
+}
+
+
+/*
+ * Compare the request packet type.
+ */
+static int packetcmp(UNUSED void *instance,
+ REQUEST *request,
+ UNUSED VALUE_PAIR *req,
+ VALUE_PAIR *check,
+ UNUSED VALUE_PAIR *check_pairs,
+ UNUSED VALUE_PAIR **reply_pairs)
+{
+ if (request->packet->code == check->vp_integer) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Compare the response packet type.
+ */
+static int responsecmp(UNUSED void *instance,
+ REQUEST *request,
+ UNUSED VALUE_PAIR *req,
+ VALUE_PAIR *check,
+ UNUSED VALUE_PAIR *check_pairs,
+ UNUSED VALUE_PAIR **reply_pairs)
+{
+ if (request->reply->code == check->vp_integer) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Generic comparisons, via xlat.
+ */
+static int genericcmp(UNUSED void *instance,
+ REQUEST *request,
+ VALUE_PAIR *req,
+ VALUE_PAIR *check,
+ UNUSED VALUE_PAIR *check_pairs,
+ UNUSED VALUE_PAIR **reply_pairs)
+{
+ if ((check->op != T_OP_REG_EQ) &&
+ (check->op != T_OP_REG_NE)) {
+ int rcode;
+ char name[1024];
+ char value[1024];
+ VALUE_PAIR *vp;
+
+ snprintf(name, sizeof(name), "%%{%s}", check->da->name);
+
+ if (radius_xlat(value, sizeof(value), request, name, NULL, NULL) < 0) {
+ return 0;
+ }
+ vp = fr_pair_make(req, NULL, check->da->name, value, check->op);
+
+ /*
+ * Paircmp returns 0 for failed comparison,
+ * 1 for succeeded.
+ */
+ rcode = fr_pair_cmp(check, vp);
+
+ /*
+ * We're being called from radius_callback_compare,
+ * which wants 0 for success, and 1 for fail (sigh)
+ *
+ * We should really fix the API so that it is
+ * consistent. i.e. the comparison callbacks should
+ * return ONLY the resut of comparing A to B.
+ * The radius_callback_cmp function should then
+ * take care of using the operator to see if the
+ * condition (A OP B) is true or not.
+ *
+ * This would also allow "<", etc. to work in the
+ * callback functions...
+ *
+ * See rlm_ldap, ...groupcmp() for something that
+ * returns 0 for matched, and 1 for didn't match.
+ */
+ rcode = !rcode;
+ fr_pair_list_free(&vp);
+
+ return rcode;
+ }
+
+ /*
+ * Will do the xlat for us
+ */
+ return radius_compare_vps(request, check, req);
+}
+
+static int generic_attrs[] = {
+ PW_CLIENT_IP_ADDRESS,
+ PW_PACKET_SRC_IP_ADDRESS,
+ PW_PACKET_DST_IP_ADDRESS,
+ PW_PACKET_SRC_PORT,
+ PW_PACKET_DST_PORT,
+ PW_REQUEST_PROCESSING_STAGE,
+ PW_PACKET_SRC_IPV6_ADDRESS,
+ PW_PACKET_DST_IPV6_ADDRESS,
+ PW_VIRTUAL_SERVER,
+ 0
+};
+
+/*
+ * Register server-builtin special attributes.
+ */
+void pair_builtincompare_add(void *instance)
+{
+ int i;
+
+ paircompare_register(dict_attrbyvalue(PW_PREFIX, 0), dict_attrbyvalue(PW_USER_NAME, 0), false, presufcmp, instance);
+ paircompare_register(dict_attrbyvalue(PW_SUFFIX, 0), dict_attrbyvalue(PW_USER_NAME, 0), false, presufcmp, instance);
+ paircompare_register(dict_attrbyvalue(PW_PACKET_TYPE, 0), NULL, true, packetcmp, instance);
+ paircompare_register(dict_attrbyvalue(PW_RESPONSE_PACKET_TYPE, 0), NULL, true, responsecmp, instance);
+
+ for (i = 0; generic_attrs[i] != 0; i++) {
+ paircompare_register(dict_attrbyvalue(generic_attrs[i], 0), NULL, true, genericcmp, instance);
+ }
+}
diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c
new file mode 100644
index 0000000..f835800
--- /dev/null
+++ b/src/modules/rlm_expr/rlm_expr.c
@@ -0,0 +1,1922 @@
+/*
+ * This program is is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file rlm_expr.c
+ * @brief Register many xlat expansions including the expr expansion.
+ *
+ * @copyright 2001,2006 The FreeRADIUS server project
+ * @copyright 2002 Alan DeKok <aland@ox.org>
+ */
+RCSID("$Id$")
+USES_APPLE_DEPRECATED_API
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/md5.h>
+#include <freeradius-devel/sha1.h>
+#include <freeradius-devel/base64.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/rad_assert.h>
+
+#ifdef HAVE_OPENSSL_EVP_H
+# include <openssl/evp.h>
+#endif
+
+#ifdef HAVE_CRYPT_H
+# include <crypt.h>
+#endif
+
+#include <ctype.h>
+
+#include "rlm_expr.h"
+
+/*
+ * Define a structure for our module configuration.
+ */
+typedef struct rlm_expr_t {
+ char const *xlat_name;
+ char const *allowed_chars;
+} rlm_expr_t;
+
+static const CONF_PARSER module_config[] = {
+ { "safe_characters", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_expr_t, allowed_chars), "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" },
+ CONF_PARSER_TERMINATOR
+};
+
+/*
+ * Lookup tables for randstr char classes
+ */
+static char randstr_punc[] = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
+static char randstr_salt[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz/.";
+
+/*
+ * Characters humans rarely confuse. Reduces char set considerably
+ * should only be used for things such as one time passwords.
+ */
+static char randstr_otp[] = "469ACGHJKLMNPQRUVWXYabdfhijkprstuvwxyz";
+
+static char const hextab[] = "0123456789abcdef";
+
+/** Calculate powers
+ *
+ * @author Orson Peters
+ * @note Borrowed from the gist here: https://gist.github.com/nightcracker/3551590.
+ *
+ * @param base a 32bit signed integer.
+ * @param exp amount to raise base by.
+ * @return base ^ pow, or 0 on underflow/overflow.
+ */
+static int64_t fr_pow(int64_t base, int64_t exp)
+{
+ static const uint8_t highest_bit_set[] = {
+ 0, 1, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6 // anything past 63 is a guaranteed overflow with base > 1
+ };
+
+ int64_t result = 1;
+
+ if (exp > 63) {
+ if (base == 1) {
+ return 1;
+ }
+
+ if (base == -1) {
+ return 1 - 2 * (exp & 1);
+ }
+ return 0; /* overflow */
+ }
+
+ switch (highest_bit_set[exp]) {
+ case 6:
+ if (exp & 1) result *= base;
+ exp >>= 1;
+ base *= base;
+ /* FALL-THROUGH */
+ case 5:
+ if (exp & 1) result *= base;
+ exp >>= 1;
+ base *= base;
+ /* FALL-THROUGH */
+ case 4:
+ if (exp & 1) result *= base;
+ exp >>= 1;
+ base *= base;
+ /* FALL-THROUGH */
+ case 3:
+ if (exp & 1) result *= base;
+ exp >>= 1;
+ base *= base;
+ /* FALL-THROUGH */
+ case 2:
+ if (exp & 1) result *= base;
+ exp >>= 1;
+ base *= base;
+ /* FALL-THROUGH */
+ case 1:
+ if (exp & 1) result *= base;
+ /* FALL-THROUGH */
+ default:
+ return result;
+ }
+}
+
+/*
+ * Start of expression calculator.
+ */
+typedef enum expr_token_t {
+ TOKEN_NONE = 0,
+ TOKEN_INTEGER,
+
+ TOKEN_AND,
+ TOKEN_OR,
+
+ TOKEN_LSHIFT,
+ TOKEN_RSHIFT,
+
+ TOKEN_ADD,
+ TOKEN_SUBTRACT,
+
+ TOKEN_DIVIDE,
+ TOKEN_REMAINDER,
+ TOKEN_MULTIPLY,
+
+ TOKEN_POWER,
+ TOKEN_LAST
+} expr_token_t;
+
+static int precedence[TOKEN_LAST + 1] = {
+ 0, 0, 1, 1, /* and or */
+ 2, 2, 3, 3, /* shift add */
+ 4, 4, 4, 5, /* mul, pow */
+ 0
+};
+
+typedef struct expr_map_t {
+ char op;
+ expr_token_t token;
+} expr_map_t;
+
+static expr_map_t map[] =
+{
+ {'+', TOKEN_ADD },
+ {'-', TOKEN_SUBTRACT },
+ {'/', TOKEN_DIVIDE },
+ {'*', TOKEN_MULTIPLY },
+ {'%', TOKEN_REMAINDER },
+ {'&', TOKEN_AND },
+ {'|', TOKEN_OR },
+ {'^', TOKEN_POWER },
+ {0, TOKEN_LAST}
+};
+
+static bool get_expression(REQUEST *request, char const **string, int64_t *answer, expr_token_t prev);
+
+static bool get_number(REQUEST *request, char const **string, int64_t *answer)
+{
+ int64_t x;
+ bool invert = false;
+ bool negative = false;
+ char const *p = *string;
+
+ /*
+ * Look for a number.
+ */
+ while (isspace((int) *p)) p++;
+
+ /*
+ * ~1 == 0xff...ffe
+ */
+ if (*p == '~') {
+ invert = true;
+ p++;
+ }
+
+ /*
+ * No algrebraic operator found, the next thing
+ * MUST be a number.
+ *
+ * If it isn't, then we die.
+ */
+ if ((*p == '0') && (p[1] == 'x')) {
+ char *end;
+
+ x = strtoul(p, &end, 16);
+ p = end;
+ goto done;
+ }
+
+ if (*p == '-') {
+ negative = true;
+ p++;
+ }
+
+ /*
+ * Look for an attribute.
+ */
+ if (*p == '&') {
+ ssize_t slen;
+ VALUE_PAIR *vp;
+ vp_tmpl_t vpt;
+
+ p += 1;
+
+ slen = tmpl_from_attr_substr(&vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
+ if (slen <= 0) {
+ REDEBUG("Failed parsing attribute name '%s': %s", p, fr_strerror());
+ return false;
+ }
+
+ p += slen;
+
+ if (tmpl_find_vp(&vp, request, &vpt) < 0) {
+ RWDEBUG("Can't find &%.*s. Using 0 as operand value", (int)vpt.len, vpt.name);
+ x = 0;
+ goto done;
+ }
+
+ if (vp->da->type != PW_TYPE_INTEGER64) {
+ value_data_t value;
+
+ if (value_data_cast(vp, &value, PW_TYPE_INTEGER64, NULL, vp->da->type, vp->da, &vp->data, vp->vp_length) < 0) {
+ REDEBUG("Failed converting &%.*s to an integer value: %s", (int) vpt.len,
+ vpt.name, fr_strerror());
+ return false;
+ }
+ if (value.integer64 > INT64_MAX) {
+ overflow:
+ REDEBUG("Value of &%.*s would overflow a signed 64bit integer "
+ "(our internal arithmetic type)", (int)vpt.len, vpt.name);
+ return false;
+ }
+ x = (int64_t)value.integer64;
+
+ RINDENT();
+ RDEBUG3("&%.*s --> %" PRIu64, (int)vpt.len, vpt.name, x);
+ REXDENT();
+ } else {
+ if (vp->vp_integer64 > INT64_MAX) goto overflow;
+ x = (int64_t)vp->vp_integer64;
+ }
+
+ goto done;
+ }
+
+ /*
+ * Do brackets recursively
+ */
+ if (*p == '(') {
+ p++;
+ if (!get_expression(request, &p, &x, TOKEN_NONE)) return false;
+
+ if (*p != ')') {
+ RDEBUG("No trailing ')'");
+ return false;
+ }
+ p++;
+ goto done;
+ }
+
+ while (isspace((int) *p)) p++;
+
+ if ((*p < '0') || (*p > '9')) {
+ RDEBUG2("Not a number at \"%s\"", p);
+ return false;
+ }
+
+ /*
+ * This is doing it the hard way, but it also allows
+ * us to increment 'p'.
+ */
+ x = 0;
+ while ((*p >= '0') && (*p <= '9')) {
+ x *= 10;
+ x += (*p - '0');
+ p++;
+ }
+
+done:
+ if (invert) x = ~x;
+
+ if (negative) x = -x;
+
+ *string = p;
+ *answer = x;
+ return true;
+}
+
+static bool calc_result(REQUEST *request, int64_t lhs, expr_token_t op, int64_t rhs, int64_t *answer)
+{
+ switch (op) {
+ default:
+ case TOKEN_ADD:
+ *answer = lhs + rhs;
+ break;
+
+ case TOKEN_SUBTRACT:
+ *answer = lhs - rhs;
+ break;
+
+ case TOKEN_DIVIDE:
+ if (rhs == 0) {
+ RDEBUG("Division by zero!");
+ return false;
+ } else {
+ *answer = lhs / rhs;
+ }
+ break;
+
+ case TOKEN_REMAINDER:
+ if (rhs == 0) {
+ RDEBUG("Division by zero!");
+ return false;
+ }
+
+ *answer = lhs % rhs;
+ break;
+
+ case TOKEN_MULTIPLY:
+ *answer = lhs * rhs;
+ break;
+
+ case TOKEN_LSHIFT:
+ if (rhs > 63) {
+ RDEBUG("Shift must be less than 63 (was %lld)", (long long int) rhs);
+ return false;
+ }
+
+ *answer = lhs << rhs;
+ break;
+
+ case TOKEN_RSHIFT:
+ if (rhs > 63) {
+ RDEBUG("Shift must be less than 63 (was %lld)", (long long int) rhs);
+ return false;
+ }
+
+ *answer = lhs >> rhs;
+ break;
+
+ case TOKEN_AND:
+ *answer = lhs & rhs;
+ break;
+
+ case TOKEN_OR:
+ *answer = lhs | rhs;
+ break;
+
+ case TOKEN_POWER:
+ if (rhs > 63) {
+ REDEBUG("Exponent must be between 0-63 (was %lld)", (long long int) rhs);
+ return false;
+ }
+
+ if (lhs > 65535) {
+ REDEBUG("Base must be between 0-65535 (was %lld)", (long long int) lhs);
+ return false;
+ }
+
+ *answer = fr_pow(lhs, rhs);
+ break;
+ }
+
+ return true;
+}
+
+static bool get_operator(REQUEST *request, char const **string, expr_token_t *op)
+{
+ int i;
+ char const *p = *string;
+
+ /*
+ * All tokens are one character.
+ */
+ for (i = 0; map[i].token != TOKEN_LAST; i++) {
+ if (*p == map[i].op) {
+ *op = map[i].token;
+ *string = p + 1;
+ return true;
+ }
+ }
+
+ if ((p[0] == '<') && (p[1] == '<')) {
+ *op = TOKEN_LSHIFT;
+ *string = p + 2;
+ return true;
+ }
+
+ if ((p[0] == '>') && (p[1] == '>')) {
+ *op = TOKEN_RSHIFT;
+ *string = p + 2;
+ return true;
+ }
+
+ RDEBUG("Expected operator at \"%s\"", p);
+ return false;
+}
+
+
+static bool get_expression(REQUEST *request, char const **string, int64_t *answer, expr_token_t prev)
+{
+ int64_t lhs, rhs;
+ char const *p, *op_p;
+ expr_token_t this;
+
+ p = *string;
+
+ if (!get_number(request, &p, &lhs)) return false;
+
+redo:
+ while (isspace((int) *p)) p++;
+
+ /*
+ * A number by itself is OK.
+ */
+ if (!*p || (*p == ')')) {
+ *answer = lhs;
+ *string = p;
+ return true;
+ }
+
+ /*
+ * Peek at the operator.
+ */
+ op_p = p;
+ if (!get_operator(request, &p, &this)) return false;
+
+ /*
+ * a + b + c ... = (a + b) + c ...
+ * a * b + c ... = (a * b) + c ...
+ *
+ * Feed the current number to the caller, who will take
+ * care of continuing.
+ */
+ if (precedence[this] <= precedence[prev]) {
+ *answer = lhs;
+ *string = op_p;
+ return true;
+ }
+
+ /*
+ * a + b * c ... = a + (b * c) ...
+ */
+ if (!get_expression(request, &p, &rhs, this)) return false;
+
+ if (!calc_result(request, lhs, this, rhs, answer)) return false;
+
+ /*
+ * There may be more to calculate. The answer we
+ * calculated here is now the LHS of the lower priority
+ * operation which follows the current expression. e.g.
+ *
+ * a * b + c ... = (a * b) + c ...
+ * = d + c ...
+ */
+ lhs = *answer;
+ goto redo;
+}
+
+/*
+ * Do xlat of strings!
+ */
+static ssize_t expr_xlat(UNUSED void *instance, REQUEST *request, char const *fmt,
+ char *out, size_t outlen)
+{
+ int64_t result;
+ char const *p;
+
+ p = fmt;
+
+ if (!get_expression(request, &p, &result, TOKEN_NONE)) {
+ return -1;
+ }
+
+ if (*p) {
+ RDEBUG("Invalid text after expression: %s", p);
+ return -1;
+ }
+
+ snprintf(out, outlen, "%lld", (long long int) result);
+ return strlen(out);
+}
+
+/** Generate a random integer value
+ *
+ */
+static ssize_t rand_xlat(UNUSED void *instance, UNUSED REQUEST *request, char const *fmt,
+ char *out, size_t outlen)
+{
+ int64_t result;
+
+ result = atoi(fmt);
+
+ /*
+ * Too small or too big.
+ */
+ if (result <= 0) {
+ *out = '\0';
+ return -1;
+ }
+ if (result >= (1 << 30)) result = (1 << 30);
+
+ result *= fr_rand(); /* 0..2^32-1 */
+ result >>= 32;
+
+ snprintf(out, outlen, "%ld", (long int) result);
+ return strlen(out);
+}
+
+/** Generate a string of random chars
+ *
+ * Build strings of random chars, useful for generating tokens and passcodes
+ * Format similar to String::Random.
+ */
+static ssize_t randstr_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ char const *p;
+ unsigned int result;
+ unsigned int number;
+ size_t freespace = outlen;
+
+ if (outlen <= 1) return 0;
+
+ *out = '\0';
+
+ p = fmt;
+ while (*p && (--freespace > 0)) {
+ number = 0;
+
+ /*
+ * Modifiers are polite.
+ *
+ * But we limit it to 100, because we don't want
+ * utter stupidity.
+ */
+ while (isdigit((int) *p)) {
+ if (number >= 100) {
+ p++;
+ continue;
+ }
+
+ number *= 10;
+ number += *p - '0';
+ p++;
+ }
+
+ redo:
+ result = fr_rand();
+
+ switch (*p) {
+ /*
+ * Lowercase letters
+ */
+ case 'c':
+ *out++ = 'a' + (result % 26);
+ break;
+
+ /*
+ * Uppercase letters
+ */
+ case 'C':
+ *out++ = 'A' + (result % 26);
+ break;
+
+ /*
+ * Numbers
+ */
+ case 'n':
+ *out++ = '0' + (result % 10);
+ break;
+
+ /*
+ * Alpha numeric
+ */
+ case 'a':
+ *out++ = randstr_salt[result % (sizeof(randstr_salt) - 3)];
+ break;
+
+ /*
+ * Punctuation
+ */
+ case '!':
+ *out++ = randstr_punc[result % (sizeof(randstr_punc) - 1)];
+ break;
+
+ /*
+ * Alpa numeric + punctuation
+ */
+ case '.':
+ *out++ = '!' + (result % 95);
+ break;
+
+ /*
+ * Alpha numeric + salt chars './'
+ */
+ case 's':
+ *out++ = randstr_salt[result % (sizeof(randstr_salt) - 1)];
+ break;
+
+ /*
+ * Chars suitable for One Time Password tokens.
+ * Alpha numeric with easily confused char pairs removed.
+ */
+ case 'o':
+ *out++ = randstr_otp[result % (sizeof(randstr_otp) - 1)];
+ break;
+
+ /*
+ * Binary data as hexits (we don't really support
+ * non printable chars).
+ */
+ case 'h':
+ if (freespace < 2) {
+ break;
+ }
+
+ snprintf(out, 3, "%02x", result % 256);
+
+ /* Already decremented */
+ freespace -= 1;
+ out += 2;
+ break;
+
+ /*
+ * Binary data with uppercase hexits
+ */
+ case 'H':
+ if (freespace < 2) {
+ break;
+ }
+
+ snprintf(out, 3, "%02X", result % 256);
+
+ /* Already decremented */
+ freespace -= 1;
+ out += 2;
+ break;
+
+ default:
+ ERROR("rlm_expr: invalid character class '%c'", *p);
+
+ return -1;
+ }
+
+ if (number > 0) {
+ number--;
+ goto redo;
+ }
+
+ p++;
+ }
+
+ *out++ = '\0';
+
+ return outlen - freespace;
+}
+
+/** URLencode special characters
+ *
+ * Example: "%{urlquote:http://example.org/}" == "http%3A%47%47example.org%47"
+ */
+static ssize_t urlquote_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ char const *p;
+ size_t freespace = outlen;
+
+ if (outlen <= 1) return 0;
+
+ p = fmt;
+ while (*p && (--freespace > 0)) {
+ if (isalnum(*p)) {
+ *out++ = *p++;
+ continue;
+ }
+
+ switch (*p) {
+ case '-':
+ case '_':
+ case '.':
+ case '~':
+ *out++ = *p++;
+ break;
+
+ default:
+ if (freespace < 3)
+ break;
+
+ /* MUST be upper case hex to be compliant */
+ snprintf(out, 4, "%%%02X", (uint8_t) *p++); /* %XX */
+
+ /* Already decremented */
+ freespace -= 2;
+ out += 3;
+ }
+ }
+
+ *out = '\0';
+
+ return outlen - freespace;
+}
+
+/** URLdecode special characters
+ *
+ * Example: "%{urlunquote:http%%3A%%47%%47example.org%%47}" == "http://example.org/"
+ *
+ * Remember to escape % with %% in strings, else xlat will try to parse it.
+ */
+static ssize_t urlunquote_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ char const *p;
+ char *c1, *c2;
+ size_t freespace = outlen;
+
+ if (outlen <= 1) return 0;
+
+ p = fmt;
+ while (*p && (--freespace > 0)) {
+ if (*p != '%') {
+ *out++ = *p++;
+ continue;
+ }
+ /* Is a % char */
+
+ /* Don't need \0 check, as it won't be in the hextab */
+ if (!(c1 = memchr(hextab, tolower(*++p), 16)) ||
+ !(c2 = memchr(hextab, tolower(*++p), 16))) {
+ REMARKER(fmt, p - fmt, "None hex char in % sequence");
+ return -1;
+ }
+ p++;
+ *out++ = ((c1 - hextab) << 4) + (c2 - hextab);
+ }
+
+ *out = '\0';
+
+ return outlen - freespace;
+}
+
+/** Equivalent to the old safe_characters functionality in rlm_sql but with utf8 support
+ *
+ * @verbatim Example: "%{escape:<img>foo.jpg</img>}" == "=60img=62foo.jpg=60/img=62" @endverbatim
+ */
+static ssize_t escape_xlat(void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ rlm_expr_t *inst = instance;
+ char const *p = fmt;
+ size_t freespace = outlen;
+
+ while (p[0]) {
+ int chr_len = 1;
+ int ret = 1; /* -Werror=uninitialized */
+
+ if (fr_utf8_strchr(&chr_len, inst->allowed_chars, p) == NULL) {
+ /*
+ * '=' 1 + ([hex]{2}) * chr_len)
+ */
+ if (freespace <= (size_t)(1 + (chr_len * 3))) break;
+
+ switch (chr_len) {
+ case 4:
+ ret = snprintf(out, freespace, "=%02X=%02X=%02X=%02X",
+ (uint8_t)p[0], (uint8_t)p[1], (uint8_t)p[2], (uint8_t)p[3]);
+ break;
+
+ case 3:
+ ret = snprintf(out, freespace, "=%02X=%02X=%02X",
+ (uint8_t)p[0], (uint8_t)p[1], (uint8_t)p[2]);
+ break;
+
+ case 2:
+ ret = snprintf(out, freespace, "=%02X=%02X", (uint8_t)p[0], (uint8_t)p[1]);
+ break;
+
+ case 1:
+ ret = snprintf(out, freespace, "=%02X", (uint8_t)p[0]);
+ break;
+ }
+
+ p += chr_len;
+ out += ret;
+ freespace -= ret;
+ continue;
+ }
+
+ /*
+ * Only one byte left.
+ */
+ if (freespace <= 1) break;
+
+ /*
+ * Allowed character (copy whole mb chars at once)
+ */
+ memcpy(out, p, chr_len);
+ out += chr_len;
+ p += chr_len;
+ freespace -= chr_len;
+ }
+ *out = '\0';
+
+ return outlen - freespace;
+}
+
+/** Equivalent to the old safe_characters functionality in rlm_sql
+ *
+ * @verbatim Example: "%{unescape:=60img=62foo.jpg=60/img=62}" == "<img>foo.jpg</img>" @endverbatim
+ */
+static ssize_t unescape_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ char const *p;
+ char *c1, *c2, c3;
+ size_t freespace = outlen;
+
+ if (outlen <= 1) return 0;
+
+ p = fmt;
+ while (*p && (--freespace > 0)) {
+ if (*p != '=') {
+ next:
+
+ *out++ = *p++;
+ continue;
+ }
+
+ /* Is a = char */
+
+ if (!(c1 = memchr(hextab, tolower(*(p + 1)), 16)) ||
+ !(c2 = memchr(hextab, tolower(*(p + 2)), 16))) goto next;
+ c3 = ((c1 - hextab) << 4) + (c2 - hextab);
+
+ *out++ = c3;
+ p += 3;
+ }
+
+ *out = '\0';
+
+ return outlen - freespace;
+}
+
+/** Convert a string to lowercase
+ *
+ * Example: "%{tolower:Bar}" == "bar"
+ *
+ * Probably only works for ASCII
+ */
+static ssize_t tolower_xlat(UNUSED void *instance, UNUSED REQUEST *request, char const *fmt, char *out, size_t outlen)
+{
+ char *q;
+ char const *p;
+
+ if (outlen <= 1) return 0;
+
+ for (p = fmt, q = out; *p != '\0'; p++, outlen--) {
+ if (outlen <= 1) break;
+
+ *(q++) = tolower((int) *p);
+ }
+
+ *q = '\0';
+
+ return strlen(out);
+}
+
+/** Convert a string to uppercase
+ *
+ * Example: "%{toupper:Foo}" == "FOO"
+ *
+ * Probably only works for ASCII
+ */
+static ssize_t toupper_xlat(UNUSED void *instance, UNUSED REQUEST *request, char const *fmt, char *out, size_t outlen)
+{
+ char *q;
+ char const *p;
+
+ if (outlen <= 1) return 0;
+
+ for (p = fmt, q = out; *p != '\0'; p++, outlen--) {
+ if (outlen <= 1) break;
+
+ *(q++) = toupper((int) *p);
+ }
+
+ *q = '\0';
+
+ return strlen(out);
+}
+
+/** Calculate the MD5 hash of a string or attribute.
+ *
+ * Example: "%{md5:foo}" == "acbd18db4cc2f85cedef654fccc4a4d8"
+ */
+static ssize_t md5_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ uint8_t digest[16];
+ ssize_t i, len, inlen;
+ uint8_t const *p;
+ FR_MD5_CTX ctx;
+
+ /*
+ * We need room for at least one octet of output.
+ */
+ if (outlen < 3) {
+ *out = '\0';
+ return 0;
+ }
+
+ inlen = xlat_fmt_to_ref(&p, request, fmt);
+ if (inlen < 0) {
+ return -1;
+ }
+
+ fr_md5_init(&ctx);
+ fr_md5_update(&ctx, p, inlen);
+ fr_md5_final(digest, &ctx);
+
+ /*
+ * Each digest octet takes two hex digits, plus one for
+ * the terminating NUL.
+ */
+ len = (outlen / 2) - 1;
+ if (len > 16) len = 16;
+
+ for (i = 0; i < len; i++) {
+ snprintf(out + i * 2, 3, "%02x", digest[i]);
+ }
+
+ return strlen(out);
+}
+
+/** Calculate the MD4 hash of a string or attribute.
+ *
+ * Example: "%{md4:foo}" == "0ac6700c491d70fb8650940b1ca1e4b2"
+ */
+static ssize_t md4_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ uint8_t digest[16];
+ ssize_t i, len, inlen;
+ uint8_t const *p;
+ FR_MD4_CTX ctx;
+
+ /*
+ * We need room for at least one octet of output.
+ */
+ if (outlen < 3) {
+ *out = '\0';
+ return 0;
+ }
+
+ inlen = xlat_fmt_to_ref(&p, request, fmt);
+ if (inlen < 0) {
+ return -1;
+ }
+
+ fr_md4_init(&ctx);
+ fr_md4_update(&ctx, p, inlen);
+ fr_md4_final(digest, &ctx);
+
+ /*
+ * Each digest octet takes two hex digits, plus one for
+ * the terminating NUL.
+ */
+ len = (outlen / 2) - 1;
+ if (len > 16) len = 16;
+
+ for (i = 0; i < len; i++) {
+ snprintf(out + i * 2, 3, "%02x", digest[i]);
+ }
+
+ return strlen(out);
+}
+
+/** Calculate the SHA1 hash of a string or attribute.
+ *
+ * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
+ */
+static ssize_t sha1_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ uint8_t digest[20];
+ ssize_t i, len, inlen;
+ uint8_t const *p;
+ fr_sha1_ctx ctx;
+
+ /*
+ * We need room for at least one octet of output.
+ */
+ if (outlen < 3) {
+ *out = '\0';
+ return 0;
+ }
+
+ inlen = xlat_fmt_to_ref(&p, request, fmt);
+ if (inlen < 0) {
+ return -1;
+ }
+
+ fr_sha1_init(&ctx);
+ fr_sha1_update(&ctx, p, inlen);
+ fr_sha1_final(digest, &ctx);
+
+ /*
+ * Each digest octet takes two hex digits, plus one for
+ * the terminating NUL. SHA1 is 160 bits (20 bytes)
+ */
+ len = (outlen / 2) - 1;
+ if (len > 20) len = 20;
+
+ for (i = 0; i < len; i++) {
+ snprintf(out + i * 2, 3, "%02x", digest[i]);
+ }
+
+ return strlen(out);
+}
+
+/** Calculate any digest supported by OpenSSL EVP_MD
+ *
+ * Example: "%{sha256:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
+ */
+#ifdef HAVE_OPENSSL_EVP_H
+static ssize_t evp_md_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen, EVP_MD const *md)
+{
+ uint8_t digest[EVP_MAX_MD_SIZE];
+ unsigned int digestlen, i, len;
+ ssize_t inlen;
+ uint8_t const *p;
+
+ EVP_MD_CTX *ctx;
+
+ /*
+ * We need room for at least one octet of output.
+ */
+ if (outlen < 3) {
+ *out = '\0';
+ return 0;
+ }
+
+ inlen = xlat_fmt_to_ref(&p, request, fmt);
+ if (inlen < 0) {
+ return -1;
+ }
+
+ ctx = EVP_MD_CTX_create();
+ EVP_DigestInit_ex(ctx, md, NULL);
+ EVP_DigestUpdate(ctx, p, inlen);
+ EVP_DigestFinal_ex(ctx, digest, &digestlen);
+ EVP_MD_CTX_destroy(ctx);
+
+ /*
+ * Each digest octet takes two hex digits, plus one for
+ * the terminating NUL.
+ */
+ len = (outlen / 2) - 1;
+ if (len > digestlen) len = digestlen;
+
+ for (i = 0; i < len; i++) {
+ snprintf(out + i * 2, 3, "%02x", digest[i]);
+ }
+ return strlen(out);
+}
+
+# define EVP_MD_XLAT(_md) \
+static ssize_t _md##_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)\
+{\
+ return evp_md_xlat(instance, request, fmt, out, outlen, EVP_##_md());\
+}
+
+EVP_MD_XLAT(sha256)
+EVP_MD_XLAT(sha512)
+#endif
+
+/** Generate the HMAC-MD5 of a string or attribute
+ *
+ * Example: "%{hmacmd5:foo bar}" == "Zm9v"
+ */
+static ssize_t hmac_md5_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ uint8_t const *data, *key;
+ char const *p;
+ ssize_t data_len, key_len;
+ uint8_t digest[MD5_DIGEST_LENGTH];
+ char data_ref[256];
+
+ if (outlen <= (sizeof(digest) * 2)) {
+ REDEBUG("Insufficient space to write digest, needed %zu bytes, have %zu bytes",
+ (sizeof(digest) * 2) + 1, outlen);
+ return -1;
+ }
+
+ p = strchr(fmt, ' ');
+ if (!p) {
+ REDEBUG("HMAC requires exactly two arguments (&data &key)");
+ return -1;
+ }
+
+ if ((size_t)(p - fmt) >= sizeof(data_ref)) {
+ REDEBUG("Insufficient space to store HMAC input data, needed %zu bytes, have %zu bytes",
+ (p - fmt) + 1, sizeof(data_ref));
+
+ return -1;
+ }
+ strlcpy(data_ref, fmt, (p - fmt) + 1);
+
+ data_len = xlat_fmt_to_ref(&data, request, data_ref);
+ if (data_len < 0) return -1;
+
+ while (isspace(*p) && p++);
+
+ key_len = xlat_fmt_to_ref(&key, request, p);
+ if (key_len < 0) return -1;
+
+ fr_hmac_md5(digest, data, data_len, key, key_len);
+
+ return fr_bin2hex(out, digest, sizeof(digest));
+}
+
+/** Generate the HMAC-SHA1 of a string or attribute
+ *
+ * Example: "%{hmacsha1:foo bar}" == "Zm9v"
+ */
+static ssize_t hmac_sha1_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ uint8_t const *data, *key;
+ char const *p;
+ ssize_t data_len, key_len;
+ uint8_t digest[SHA1_DIGEST_LENGTH];
+ char data_ref[256];
+
+ if (outlen <= (sizeof(digest) * 2)) {
+ REDEBUG("Insufficient space to write digest, needed %zu bytes, have %zu bytes",
+ (sizeof(digest) * 2) + 1, outlen);
+ return -1;
+ }
+
+ p = strchr(fmt, ' ');
+ if (!p) {
+ REDEBUG("HMAC requires exactly two arguments (&data &key)");
+ return -1;
+ }
+
+ if ((size_t)(p - fmt) >= sizeof(data_ref)) {
+ REDEBUG("Insufficient space to store HMAC input data, needed %zu bytes, have %zu bytes",
+ (p - fmt) + 1, sizeof(data_ref));
+
+ return -1;
+ }
+ strlcpy(data_ref, fmt, (p - fmt) + 1);
+
+ data_len = xlat_fmt_to_ref(&data, request, data_ref);
+ if (data_len < 0) return -1;
+
+ while (isspace(*p) && p++);
+
+ key_len = xlat_fmt_to_ref(&key, request, p);
+ if (key_len < 0) return -1;
+
+ fr_hmac_sha1(digest, data, data_len, key, key_len);
+
+ return fr_bin2hex(out, digest, sizeof(digest));
+}
+
+/** Crypt a string or attribute.
+ *
+ * Example: "%{crypt:ab:foo}" == "abQ9KY.KfrYrc"
+ * Example: "%{crypt:$1$abcdefgh:foo}" == "$1$abcdefgh$XxzGe9Muun7wTYbZO4sdr0"
+ * Example: "%{crypt:$1$%{randstr:aaaaaaaa}:&User-Password}" -> "$1$Z9hrfzst$hRkQwmSUApr/r10kb/d3W0"
+ */
+#ifdef HAVE_CRYPT_R
+static ssize_t crypt_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ ssize_t inlen;
+ size_t len;
+ uint8_t const *salt;
+ uint8_t const *pass;
+ char *p;
+ struct crypt_data cdata;
+
+ cdata.initialized = 0;
+
+ /*
+ * DES passwords will be at least 13 chars long.
+ */
+ if (outlen < 14) {
+ *out = '\0';
+ return 0;
+ }
+
+ p = strchr(fmt, ':');
+ if (!p) {
+ REDEBUG("No salt specified in crypt xlat");
+ return -1;
+ }
+
+ *p = '\0';
+ p++;
+
+ /*
+ * Get salt
+ */
+ inlen = xlat_fmt_to_ref(&salt, request, fmt);
+ if (inlen < 0) {
+ return -1;
+ }
+
+ /*
+ * Get cleartext password
+ */
+ inlen = xlat_fmt_to_ref(&pass, request, p);
+ if (inlen < 0) {
+ return -1;
+ }
+
+ p = crypt_r((const char *) pass, (const char *) salt, &cdata);
+
+ if (!p) {
+ switch (errno) {
+ case EINVAL:
+ REDEBUG("Crypt salt has the wrong format: '%s'", salt);
+ break;
+ default:
+ REDEBUG("Crypt error");
+ }
+ return -1;
+ }
+
+ len = strlen(p);
+ if (outlen < len) {
+ *out = '\0';
+ return 0;
+ }
+
+ strncpy(out, p, outlen);
+
+ return len;
+}
+#else
+static ssize_t crypt_xlat(UNUSED void *instance, REQUEST *request,
+ UNUSED char const *fmt, UNUSED char *out,
+ UNUSED size_t outlen)
+{
+ RERROR("Crypt not available at compile time (no 'crypt_r' support)");
+ return 0;
+}
+#endif
+
+/** Encode attributes as a series of string attribute/value pairs
+ *
+ * This is intended to serialize one or more attributes as a comma
+ * delimited string.
+ *
+ * Example: "%{pairs:request:}" == "User-Name = 'foo', User-Password = 'bar'"
+ */
+static ssize_t pairs_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ vp_tmpl_t vpt;
+ vp_cursor_t cursor;
+ size_t len, freespace = outlen;
+ char *p = out;
+
+ VALUE_PAIR *vp;
+
+ if (tmpl_from_attr_str(&vpt, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false) <= 0) {
+ REDEBUG("%s", fr_strerror());
+ return -1;
+ }
+
+ for (vp = tmpl_cursor_init(NULL, &cursor, request, &vpt);
+ vp;
+ vp = tmpl_cursor_next(&cursor, &vpt)) {
+ FR_TOKEN op = vp->op;
+
+ vp->op = T_OP_EQ;
+ len = vp_prints(p, freespace, vp);
+ vp->op = op;
+
+ if (is_truncated(len, freespace)) {
+ no_space:
+ REDEBUG("Insufficient space to store pair string, needed %zu bytes have %zu bytes",
+ (p - out) + len, outlen);
+ *out = '\0';
+ return -1;
+ }
+ p += len;
+ freespace -= len;
+
+ if (freespace < 2) {
+ len = 2;
+ goto no_space;
+ }
+
+ *p++ = ',';
+ *p++ = ' ';
+ freespace -= 2;
+ }
+
+ /* Trim the trailing ', ' */
+ if (p != out) p -= 2;
+ *p = '\0';
+
+ return (p - out);
+}
+
+/** Encode string or attribute as base64
+ *
+ * Example: "%{base64:foo}" == "Zm9v"
+ */
+static ssize_t base64_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ ssize_t inlen;
+ uint8_t const *p;
+
+ inlen = xlat_fmt_to_ref(&p, request, fmt);
+ if (inlen < 0) {
+ return -1;
+ }
+
+ /*
+ * We can accurately calculate the length of the output string
+ * if it's larger than outlen, the output would be useless so abort.
+ */
+ if ((inlen < 0) || ((FR_BASE64_ENC_LENGTH(inlen) + 1) > (ssize_t) outlen)) {
+ REDEBUG("xlat failed");
+ *out = '\0';
+ return -1;
+ }
+
+ return fr_base64_encode(out, outlen, p, inlen);
+}
+
+/** Convert base64 to hex
+ *
+ * Example: "%{base64tohex:Zm9v}" == "666f6f"
+ */
+static ssize_t base64_to_hex_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ uint8_t decbuf[1024];
+
+ ssize_t declen;
+ ssize_t len = strlen(fmt);
+
+ *out = '\0';
+
+ declen = fr_base64_decode(decbuf, sizeof(decbuf), fmt, len);
+ if (declen < 0) {
+ REDEBUG("Base64 string invalid");
+ return -1;
+ }
+
+ if ((size_t)((declen * 2) + 1) > outlen) {
+ REDEBUG("Base64 conversion failed, output buffer exhausted, needed %zd bytes, have %zd bytes",
+ (declen * 2) + 1, outlen);
+ return -1;
+ }
+
+ return fr_bin2hex(out, decbuf, declen);
+}
+
+/** Split an attribute into multiple new attributes based on a delimiter
+ *
+ * @todo should support multibyte delimiter for string types.
+ *
+ * Example: "%{explode:&ref <delim>}"
+ */
+static ssize_t explode_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ vp_tmpl_t vpt;
+ vp_cursor_t cursor, to_merge;
+ VALUE_PAIR *vp, *head = NULL;
+ ssize_t slen;
+ int count = 0;
+ char const *p = fmt;
+ char delim;
+
+ /*
+ * Trim whitespace
+ */
+ while (isspace(*p) && p++);
+
+ slen = tmpl_from_attr_substr(&vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
+ if (slen <= 0) {
+ REDEBUG("%s", fr_strerror());
+ return -1;
+ }
+
+ p += slen;
+
+ if (*p++ != ' ') {
+ arg_error:
+ REDEBUG("explode needs exactly two arguments: &ref <delim>");
+ return -1;
+ }
+
+ if (*p == '\0') goto arg_error;
+
+ delim = *p;
+
+ fr_cursor_init(&to_merge, &head);
+
+ for (vp = tmpl_cursor_init(NULL, &cursor, request, &vpt);
+ vp;
+ vp = tmpl_cursor_next(&cursor, &vpt)) {
+ VALUE_PAIR *new;
+ char const *end;
+ char const *q;
+
+ /*
+ * This can theoretically operate on lists too
+ * so we need to check the type of each attribute.
+ */
+ switch (vp->da->type) {
+ case PW_TYPE_OCTETS:
+ case PW_TYPE_STRING:
+ break;
+
+ default:
+ continue;
+ }
+
+ p = vp->data.ptr;
+ end = p + vp->vp_length;
+ while (p < end) {
+ q = memchr(p, delim, end - p);
+ if (!q) {
+ /* Delimiter not present in attribute */
+ if (p == vp->data.ptr) goto next;
+ q = end;
+ }
+
+ /* Skip zero length */
+ if (q == p) {
+ p = q + 1;
+ continue;
+ }
+
+ new = fr_pair_afrom_da(talloc_parent(vp), vp->da);
+ if (!new) {
+ fr_pair_list_free(&head);
+ return -1;
+ }
+ new->tag = vp->tag;
+
+ switch (vp->da->type) {
+ case PW_TYPE_OCTETS:
+ {
+ uint8_t *buff;
+
+ buff = talloc_array(new, uint8_t, q - p);
+ memcpy(buff, p, q - p);
+ fr_pair_value_memsteal(new, buff);
+ }
+ break;
+
+ case PW_TYPE_STRING:
+ {
+ char *buff;
+
+ buff = talloc_array(new, char, (q - p) + 1);
+ memcpy(buff, p, q - p);
+ buff[q - p] = '\0';
+ fr_pair_value_strsteal(new, (char *)buff);
+ }
+ break;
+
+ default:
+ rad_assert(0);
+ }
+
+ fr_cursor_insert(&to_merge, new);
+
+ p = q + 1; /* next */
+
+ count++;
+ }
+
+ /*
+ * Remove the unexploded version
+ */
+ vp = fr_cursor_remove(&cursor);
+ talloc_free(vp);
+
+ next:
+ continue; /* Apparently goto labels aren't allowed at the end of loops? */
+ }
+
+ fr_cursor_merge(&cursor, head);
+
+ return snprintf(out, outlen, "%i", count);
+}
+
+/** Calculate number of seconds until the next n hour(s), day(s), week(s), year(s).
+ *
+ * For example, if it were 16:18 %{nexttime:1h} would expand to 2520.
+ *
+ * The envisaged usage for this function is to limit sessions so that they don't
+ * cross billing periods. The output of the xlat should be combined with %{rand:} to create
+ * some jitter, unless the desired effect is every subscriber on the network
+ * re-authenticating at the same time.
+ */
+static ssize_t next_time_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ long num;
+
+ char const *p;
+ char *q;
+ time_t now;
+ struct tm *local, local_buff;
+
+ now = time(NULL);
+ local = localtime_r(&now, &local_buff);
+
+ p = fmt;
+
+ num = strtoul(p, &q, 10);
+ if (!q || *q == '\0') {
+ REDEBUG("nexttime: <int> must be followed by period specifier (h|d|w|m|y)");
+ return -1;
+ }
+
+ if (p == q) {
+ num = 1;
+ } else {
+ p += q - p;
+ }
+
+ local->tm_sec = 0;
+ local->tm_min = 0;
+
+ switch (*p) {
+ case 'h':
+ local->tm_hour += num;
+ break;
+
+ case 'd':
+ local->tm_hour = 0;
+ local->tm_mday += num;
+ break;
+
+ case 'w':
+ local->tm_hour = 0;
+ local->tm_mday += (7 - local->tm_wday) + (7 * (num-1));
+ break;
+
+ case 'm':
+ local->tm_hour = 0;
+ local->tm_mday = 1;
+ local->tm_mon += num;
+ break;
+
+ case 'y':
+ local->tm_hour = 0;
+ local->tm_mday = 1;
+ local->tm_mon = 0;
+ local->tm_year += num;
+ break;
+
+ default:
+ REDEBUG("nexttime: Invalid period specifier '%c', must be h|d|w|m|y", *p);
+ return -1;
+ }
+
+ return snprintf(out, outlen, "%" PRIu64, (uint64_t)(mktime(local) - now));
+}
+
+/** Calculate number of seconds until the previous n hour(s), day(s), week(s), year(s).
+ *
+ * For example, if it were 16:18 %{lasttime:1h} would expand to -2520.
+ */
+static ssize_t last_time_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ long num;
+
+ char const *p;
+ char *q;
+ time_t now;
+ struct tm *local, local_buff;
+
+ now = time(NULL);
+ local = localtime_r(&now, &local_buff);
+
+ p = fmt;
+
+ num = strtoul(p, &q, 10);
+ if (!q || *q == '\0') {
+ REDEBUG("nexttime: <int> must be followed by period specifier (h|d|w|m|y)");
+ return -1;
+ }
+
+ if (p == q) {
+ num = 1;
+ } else {
+ p += q - p;
+ }
+
+ local->tm_sec = 0;
+ local->tm_min = 0;
+
+ switch (*p) {
+ case 'h':
+ local->tm_hour -= num;
+ break;
+
+ case 'd':
+ local->tm_hour = 0;
+ local->tm_mday -= num;
+ break;
+
+ case 'w':
+ local->tm_hour = 0;
+ local->tm_mday -= (7 - local->tm_wday) + (7 * (num-1));
+ break;
+
+ case 'm':
+ local->tm_hour = 0;
+ local->tm_mday = 1;
+ local->tm_mon -= num;
+ break;
+
+ case 'y':
+ local->tm_hour = 0;
+ local->tm_mday = 1;
+ local->tm_mon = 0;
+ local->tm_year -= num;
+ break;
+
+ default:
+ REDEBUG("lasttime: Invalid period specifier '%c', must be h|d|w|m|y", *p);
+ return -1;
+ }
+
+ return snprintf(out, outlen, "%" PRIu64, (uint64_t)(now - mktime(local)));
+}
+
+
+/*
+ * Parse the 3 arguments to lpad / rpad.
+ */
+static bool parse_pad(REQUEST *request, char const *fmt,
+ vp_tmpl_t **pvpt, size_t *plength,
+ char *fill)
+{
+ ssize_t slen;
+ unsigned long length;
+ char const *p;
+ char *end;
+ vp_tmpl_t *vpt;
+
+ *fill = ' '; /* the default */
+
+ p = fmt;
+ while (isspace((int) *p)) p++;
+
+ if (*p != '&') {
+ RDEBUG("First argument must be an attribute reference");
+ return false;
+ }
+
+ vpt = talloc(request, vp_tmpl_t);
+ if (!vpt) return false;
+
+ slen = tmpl_from_attr_substr(vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
+ if (slen <= 0) {
+ talloc_free(vpt);
+ RDEBUG("Failed expanding string: %s", fr_strerror());
+ return false;
+ }
+
+ p = fmt + slen;
+
+ while (isspace((int) *p)) p++;
+
+ length = strtoul(p, &end, 10);
+ if ((length == ULONG_MAX) || (length > 8192)) {
+ talloc_free(vpt);
+ RDEBUG("Invalid length found at: %s", p);
+ return false;
+ }
+
+ p += (end - p);
+
+ /*
+ * The fill character is optional.
+ *
+ * But we must have a space after the previous number,
+ * and we must have only ONE fill character.
+ */
+ if (*p) {
+ if (!isspace(*p)) {
+ talloc_free(vpt);
+ RDEBUG("Invalid text found at: %s", p);
+ return false;
+ }
+
+ while (isspace((int) *p)) p++;
+
+ if (p[1] != '\0') {
+ talloc_free(vpt);
+ RDEBUG("Invalid text found at: %s", p);
+ return false;
+ }
+
+ *fill = *p;
+ }
+
+ *pvpt = vpt;
+ *plength = length;
+
+ return true;
+}
+
+
+/** left pad a string
+ *
+ * %{lpad:&Attribute-Name length 'x'}
+ */
+static ssize_t lpad_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ char fill;
+ size_t pad;
+ ssize_t len;
+ vp_tmpl_t *vpt;
+
+ *out = '\0';
+ if (!parse_pad(request, fmt, &vpt, &pad, &fill)) {
+ return 0;
+ }
+
+ if (outlen <= pad) {
+ RWARN("Output is too short! Result will be truncated");
+ pad = outlen - 1;
+ }
+
+ /*
+ * Print the attribute (left justified). If it's too
+ * big, we're done.
+ */
+ len = tmpl_expand(NULL, out, pad + 1, request, vpt, NULL, NULL);
+ if (len <= 0) return 0;
+
+ if ((size_t) len >= pad) return pad;
+
+ /*
+ * We have to shift the string to the right, and pad with
+ * "fill" characters.
+ */
+ memmove(out + (pad - len), out, len + 1);
+ memset(out, fill, pad - len);
+
+ return pad;
+}
+
+
+/** right pad a string
+ *
+ * %{rpad:&Attribute-Name length 'x'}
+ */
+static ssize_t rpad_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ char fill;
+ size_t pad;
+ ssize_t len;
+ vp_tmpl_t *vpt;
+
+ *out = '\0';
+
+ if (!parse_pad(request, fmt, &vpt, &pad, &fill)) {
+ return 0;
+ }
+
+ if (outlen <= pad) {
+ RWARN("Output is too short! Result will be truncated");
+ pad = outlen - 1;
+ }
+
+ /*
+ * Print the attribute (left justified). If it's too
+ * big, we're done.
+ */
+ len = tmpl_expand(NULL, out, pad + 1, request, vpt, NULL, NULL);
+ if (len <= 0) return 0;
+
+ if ((size_t) len >= pad) return pad;
+
+ /*
+ * We have to pad with "fill" characters.
+ */
+ memset(out + len, fill, pad - len);
+ out[pad] = '\0';
+
+ return pad;
+}
+
+
+/*
+ * Do any per-module initialization that is separate to each
+ * configured instance of the module. e.g. set up connections
+ * to external databases, read configuration files, set up
+ * dictionary entries, etc.
+ *
+ * If configuration information is given in the config section
+ * that must be referenced in later calls, store a handle to it
+ * in *instance otherwise put a null pointer there.
+ */
+static int mod_bootstrap(CONF_SECTION *conf, void *instance)
+{
+ rlm_expr_t *inst = instance;
+
+ inst->xlat_name = cf_section_name2(conf);
+ if (!inst->xlat_name) {
+ inst->xlat_name = cf_section_name1(conf);
+ }
+
+ xlat_register(inst->xlat_name, expr_xlat, NULL, inst);
+
+ xlat_register("rand", rand_xlat, NULL, inst);
+ xlat_register("randstr", randstr_xlat, NULL, inst);
+ xlat_register("urlquote", urlquote_xlat, NULL, inst);
+ xlat_register("urlunquote", urlunquote_xlat, NULL, inst);
+ xlat_register("escape", escape_xlat, NULL, inst);
+ xlat_register("unescape", unescape_xlat, NULL, inst);
+ xlat_register("tolower", tolower_xlat, NULL, inst);
+ xlat_register("toupper", toupper_xlat, NULL, inst);
+ xlat_register("md4", md4_xlat, NULL, inst);
+ xlat_register("md5", md5_xlat, NULL, inst);
+ xlat_register("sha1", sha1_xlat, NULL, inst);
+#ifdef HAVE_OPENSSL_EVP_H
+ xlat_register("sha256", sha256_xlat, NULL, inst);
+ xlat_register("sha512", sha512_xlat, NULL, inst);
+#endif
+ xlat_register("hmacmd5", hmac_md5_xlat, NULL, inst);
+ xlat_register("hmacsha1", hmac_sha1_xlat, NULL, inst);
+ xlat_register("crypt", crypt_xlat, NULL, inst);
+ xlat_register("pairs", pairs_xlat, NULL, inst);
+
+ xlat_register("base64", base64_xlat, NULL, inst);
+ xlat_register("base64tohex", base64_to_hex_xlat, NULL, inst);
+
+ xlat_register("explode", explode_xlat, NULL, inst);
+
+ xlat_register("nexttime", next_time_xlat, NULL, inst);
+ xlat_register("lasttime", last_time_xlat, NULL, inst);
+ xlat_register("lpad", lpad_xlat, NULL, inst);
+ xlat_register("rpad", rpad_xlat, NULL, inst);
+
+ /*
+ * Initialize various paircompare functions
+ */
+ pair_builtincompare_add(instance);
+ return 0;
+}
+
+/*
+ * The module name should be the only globally exported symbol.
+ * That is, everything else should be 'static'.
+ *
+ * If the module needs to temporarily modify it's instantiation
+ * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
+ * The server will then take care of ensuring that the module
+ * is single-threaded.
+ */
+extern module_t rlm_expr;
+module_t rlm_expr = {
+ .magic = RLM_MODULE_INIT,
+ .name = "expr",
+ .inst_size = sizeof(rlm_expr_t),
+ .config = module_config,
+ .bootstrap = mod_bootstrap,
+};
diff --git a/src/modules/rlm_expr/rlm_expr.h b/src/modules/rlm_expr/rlm_expr.h
new file mode 100644
index 0000000..7c0fa8a
--- /dev/null
+++ b/src/modules/rlm_expr/rlm_expr.h
@@ -0,0 +1,25 @@
+#ifndef _RLM_EXPR_H
+#define _RLM_EXPR_H
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2007 The FreeRADIUS server project
+ * Copyright 2007 Alan DeKok <aland@ox.org>
+ */
+RCSIDH(rlm_expr_h, "$Id$")
+
+void pair_builtincompare_add(void *instance);
+
+#endif