#include <freeradius-devel/parser.h>
#include <freeradius-devel/rad_assert.h>
#include <freeradius-devel/base64.h>
+#include <freeradius-devel/md5.h>
+#include <freeradius-devel/sha1.h>
+#ifdef HAVE_OPENSSL_EVP_H
+# include <openssl/evp.h>
+#endif
#include <ctype.h>
#include "xlat_priv.h"
NULL};
#endif
+/*
+ * 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";
+
static int xlat_foreach_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; /* up to 10 for foreach */
/** Print length of its RHS.
}
talloc_free(value);
- if (!RDEBUG_ENABLED3) continue;
+ if (!RDEBUG_ENABLED3) continue;
+
+ vendor = fr_dict_vendor_by_da(vp->da);
+ if (vendor) RIDEBUG2("Vendor : %i (%s)", vendor->pen, vendor->name);
+ RIDEBUG2("Type : %s", fr_int2str(dict_attr_types, vp->vp_type, "<INVALID>"));
+
+ switch (vp->vp_type) {
+ case FR_TYPE_VARIABLE_SIZE:
+ RIDEBUG2("Length : %zu", vp->vp_length);
+ break;
+
+ default:
+ break;
+ }
+
+ if (!RDEBUG_ENABLED4) continue;
+
+ type = dict_attr_types;
+ while (type->name) {
+ int pad;
+
+ fr_value_box_t *dst = NULL;
+
+ if ((fr_type_t) type->number == vp->vp_type) goto next_type;
+
+ switch (type->number) {
+ case FR_TYPE_INVALID: /* Not real type */
+ case FR_TYPE_MAX: /* Not real type */
+ case FR_TYPE_COMBO_IP_ADDR: /* Covered by IPv4 address IPv6 address */
+ case FR_TYPE_COMBO_IP_PREFIX: /* Covered by IPv4 address IPv6 address */
+ case FR_TYPE_TIMEVAL: /* Not a VALUE_PAIR type */
+ case FR_TYPE_STRUCTURAL:
+ goto next_type;
+
+ default:
+ break;
+ }
+
+ dst = talloc_zero(vp, fr_value_box_t);
+ /* We expect some to fail */
+ if (fr_value_box_cast(dst, dst, type->number, NULL, &vp->data) < 0) {
+ goto next_type;
+ }
+
+ value = fr_value_box_asprint(dst, dst, '\'');
+ if (!value) goto next_type;
+
+ if ((pad = (11 - strlen(type->name))) < 0) {
+ pad = 0;
+ }
+
+ RINDENT();
+ RDEBUG2("as %s%*s: %s", type->name, pad, " ", value);
+ REXDENT();
+
+ next_type:
+ talloc_free(dst);
+ type++;
+ }
+ }
+ REXDENT();
+
+ talloc_free(vpt);
+
+ return 0;
+}
+
+/** Processes fmt as a map string and applies it to the current request
+ *
+ * e.g. "%{map:&User-Name := 'foo'}"
+ *
+ * Allows sets of modifications to be cached and then applied.
+ * Useful for processing generic attributes from LDAP.
+ */
+static ssize_t xlat_map(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ vp_map_t *map = NULL;
+ int ret;
+
+ if (map_afrom_attr_str(request, &map, fmt,
+ REQUEST_CURRENT, PAIR_LIST_REQUEST,
+ REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) {
+ RPEDEBUG("Failed parsing \"%s\" as map", fmt);
+ return -1;
+ }
+
+ RINDENT();
+ ret = map_to_request(request, map, map_to_vp, NULL);
+ REXDENT();
+ talloc_free(map);
+ if (ret < 0) return strlcpy(*out, "0", outlen);
+
+ return strlcpy(*out, "1", outlen);
+}
+
+/** Prints the current module processing the request
+ *
+ */
+static ssize_t xlat_module(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, UNUSED char const *fmt)
+{
+ strlcpy(*out, request->module, outlen);
+
+ return strlen(*out);
+}
+
+#if defined(HAVE_REGEX) && defined(HAVE_PCRE)
+static ssize_t xlat_regex(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ char *p;
+ size_t len;
+
+ if (regex_request_to_sub_named(request, &p, request, fmt) < 0) return 0;
+
+ len = talloc_array_length(p);
+ if (len > outlen) {
+ RDEBUG("Insufficient buffer space to write subcapture value, needed %zu bytes, have %zu bytes",
+ len, outlen);
+ return -1;
+ }
+ strlcpy(*out, p, outlen);
+
+ return len - 1; /* - \0 */
+}
+#endif
+
+#ifdef WITH_UNLANG
+/** Implements the Foreach-Variable-X
+ *
+ * @see modcall()
+ */
+static ssize_t xlat_foreach(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen,
+ void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, UNUSED char const *fmt)
+{
+ VALUE_PAIR **pvp;
+
+ /*
+ * See modcall, "FOREACH" for how this works.
+ */
+ pvp = (VALUE_PAIR **) request_data_reference(request, (void *)radius_get_vp, *(int const *) mod_inst);
+ if (!pvp || !*pvp) return 0;
+
+ *out = fr_pair_value_asprint(ctx, *pvp, '\0');
+ return talloc_array_length(*out) - 1;
+}
+#endif
+
+/** Print data as string, if possible.
+ *
+ * If attribute "Foo" is defined as "octets" it will normally
+ * be printed as 0x0a0a0a. The xlat "%{string:Foo}" will instead
+ * expand to "\n\n\n"
+ */
+static ssize_t xlat_string(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ ssize_t ret;
+ VALUE_PAIR *vp;
+ uint8_t buffer[64];
+
+ while (isspace((int) *fmt)) fmt++;
+
+ if (outlen < 3) {
+ nothing:
+ return 0;
+ }
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) goto nothing;
+
+ /*
+ * These are printed specially.
+ */
+ switch (vp->vp_type) {
+ case FR_TYPE_OCTETS:
+ return fr_snprint(*out, outlen, (char const *) vp->vp_octets, vp->vp_length, '"');
+
+ /*
+ * Note that "%{string:...}" is NOT binary safe!
+ * It is explicitly used to get rid of embedded zeros.
+ */
+ case FR_TYPE_STRING:
+ return strlcpy(*out, vp->vp_strvalue, outlen);
+
+ default:
+ break;
+ }
+
+ ret = fr_value_box_to_network(NULL, buffer, sizeof(buffer), &vp->data);
+ if (ret < 0) return ret;
+
+ return fr_snprint(*out, outlen, (char const *) buffer, ret, '\0');
+}
+
+/** xlat expand string attribute value
+ *
+ */
+static ssize_t xlat_xlat(TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ ssize_t slen;
+ VALUE_PAIR *vp;
+
+ while (isspace((int) *fmt)) fmt++;
+
+ if (outlen < 3) {
+ nothing:
+ return 0;
+ }
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) goto nothing;
+
+ RDEBUG2("EXPAND %s", fmt);
+ RINDENT();
+
+ /*
+ * If it's a string, expand it again
+ */
+ if (vp->vp_type == FR_TYPE_STRING) {
+ slen = xlat_eval(*out, outlen, request, vp->vp_strvalue, NULL, NULL);
+ if (slen <= 0) return slen;
+ /*
+ * If it's not a string, treat it as a literal
+ */
+ } else {
+ *out = fr_pair_value_asprint(ctx, vp, '\0');
+ if (!*out) return -1;
+ slen = talloc_array_length(*out) - 1;
+ }
+
+ REXDENT();
+ RDEBUG2("--> %s", *out);
+
+ return slen;
+}
+
+/** Dynamically change the debugging level for the current request
+ *
+ * Example %{debug:3}
+ */
+static ssize_t xlat_debug(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ int level = 0;
+
+ /*
+ * Expand to previous (or current) level
+ */
+ snprintf(*out, outlen, "%d", request->log.lvl);
+
+ /*
+ * Assume we just want to get the current value and NOT set it to 0
+ */
+ if (!*fmt)
+ goto done;
+
+ level = atoi(fmt);
+ if (level == 0) {
+ request->log.lvl = RAD_REQUEST_LVL_NONE;
+ } else {
+ if (level > 4) level = 4;
+ request->log.lvl = level;
+ }
+
+done:
+ return strlen(*out);
+}
+
+/** Generate a random integer value
+ *
+ */
+static ssize_t rand_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ UNUSED REQUEST *request, char const *fmt)
+{
+ int64_t result;
+
+ result = atoi(fmt);
+
+ /*
+ * Too small or too big.
+ */
+ if (result <= 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 TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ char const *p;
+ char *out_p = *out;
+ unsigned int result;
+ unsigned int number;
+ size_t freespace = outlen;
+
+ if (outlen <= 1) return 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_p++ = 'a' + (result % 26);
+ break;
+
+ /*
+ * Uppercase letters
+ */
+ case 'C':
+ *out_p++ = 'A' + (result % 26);
+ break;
+
+ /*
+ * Numbers
+ */
+ case 'n':
+ *out_p++ = '0' + (result % 10);
+ break;
+
+ /*
+ * Alpha numeric
+ */
+ case 'a':
+ *out_p++ = randstr_salt[result % (sizeof(randstr_salt) - 3)];
+ break;
+
+ /*
+ * Punctuation
+ */
+ case '!':
+ *out_p++ = randstr_punc[result % (sizeof(randstr_punc) - 1)];
+ break;
+
+ /*
+ * Alpa numeric + punctuation
+ */
+ case '.':
+ *out_p++ = '!' + (result % 95);
+ break;
+
+ /*
+ * Alpha numeric + salt chars './'
+ */
+ case 's':
+ *out_p++ = 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_p++ = 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_p, 3, "%02x", result % 256);
+
+ /* Already decremented */
+ freespace -= 1;
+ out_p += 2;
+ break;
+
+ /*
+ * Binary data with uppercase hexits
+ */
+ case 'H':
+ if (freespace < 2) {
+ break;
+ }
+
+ snprintf(out_p, 3, "%02X", result % 256);
+
+ /* Already decremented */
+ freespace -= 1;
+ out_p += 2;
+ break;
+
+ default:
+ REDEBUG("Invalid character class '%c'", *p);
+
+ return -1;
+ }
+
+ if (number > 0) {
+ number--;
+ goto redo;
+ }
+
+ p++;
+ }
+
+ *out_p++ = '\0';
+
+ return outlen - freespace;
+}
+
+/** URLencode special characters
+ *
+ * Example: "%{urlquote:http://example.org/}" == "http%3A%47%47example.org%47"
+ */
+static ssize_t urlquote_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ UNUSED REQUEST *request, char const *fmt)
+{
+ char const *p;
+ char *out_p = *out;
+ size_t freespace = outlen;
+
+ if (outlen <= 1) return 0;
+
+ p = fmt;
+ while (*p && (--freespace > 0)) {
+ if (isalnum(*p)) {
+ *out_p++ = *p++;
+ continue;
+ }
+
+ switch (*p) {
+ case '-':
+ case '_':
+ case '.':
+ case '~':
+ *out_p++ = *p++;
+ break;
+
+ default:
+ if (freespace < 3)
+ break;
+
+ /* MUST be upper case hex to be compliant */
+ snprintf(out_p, 4, "%%%02X", (uint8_t) *p++); /* %XX */
+
+ /* Already decremented */
+ freespace -= 2;
+ out_p += 3;
+ }
+ }
+
+ *out_p = '\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 TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ char const *p;
+ char *out_p = *out;
+ char *c1, *c2;
+ size_t freespace = outlen;
+
+ if (outlen <= 1) return 0;
+
+ p = fmt;
+ while (*p && (--freespace > 0)) {
+ if (*p != '%') {
+ *out_p++ = *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, "Non-hex char in % sequence");
+ return -1;
+ }
+ p++;
+ *out_p++ = ((c1 - hextab) << 4) + (c2 - hextab);
+ }
+
+ *out_p = '\0';
+
+ return outlen - freespace;
+}
+
+
+/** Convert a string to lowercase
+ *
+ * Example: "%{tolower:Bar}" == "bar"
+ *
+ * Probably only works for ASCII
+ */
+static ssize_t tolower_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ UNUSED REQUEST *request, char const *fmt)
+{
+ 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 TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ UNUSED REQUEST *request, char const *fmt)
+{
+ 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);
+}
+
+/** Decodes data or &Attr-Name to data
+ *
+ * This needs to die, and hopefully will die, when xlat functions accept
+ * xlat node structures.
+ *
+ * @param ctx Talloc ctx for temporary allocations.
+ * @param out fr_value_box_t containing a shallow copy of the attribute,
+ * or the fmt string.
+ * @param request current request.
+ * @param fmt string.
+ * @returns
+ * - The length of the data.
+ * - -1 on failure.
+ */
+static int fr_value_box_from_fmt(TALLOC_CTX *ctx, fr_value_box_t *out, REQUEST *request, char const *fmt)
+{
+ VALUE_PAIR *vp;
+
+ while (isspace((int) *fmt)) fmt++;
+
+ /*
+ * Not an attribute reference? Just use the input format.
+ */
+ if (*fmt != '&') {
+ memset(out, 0, sizeof(*out));
+ out->vb_strvalue = fmt;
+ out->datum.length = talloc_array_length(fmt) - 1;
+ out->type = FR_TYPE_STRING;
+ return 0;
+ }
+
+ /*
+ * If it's an attribute reference, get the underlying
+ * attribute, and then store the data in network byte
+ * order.
+ */
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) return -1;
+
+ fr_value_box_copy(ctx, out, &vp->data);
+
+ return 0;
+}
+
+static int fr_value_box_to_bin(TALLOC_CTX *ctx, REQUEST *request, uint8_t **out, size_t *outlen, fr_value_box_t const *in)
+{
+ fr_value_box_t bin;
+
+ switch (in->type) {
+ case FR_TYPE_STRING:
+ case FR_TYPE_OCTETS:
+ memcpy(out, &in->datum.ptr, sizeof(in));
+ *outlen = in->datum.length;
+ return 0;
+
+ default:
+ if (fr_value_box_cast(ctx, &bin, FR_TYPE_OCTETS, NULL, in) < 0) {
+ RPERROR("Failed casting xlat input to 'octets'");
+ return -1;
+ }
+ memcpy(out, &bin.datum.ptr, sizeof(in));
+ *outlen = bin.datum.length;
+ return 0;
+ }
+}
+
+#define VALUE_FROM_FMT(_tmp_ctx, _p, _len, _request, _fmt) \
+ fr_value_box_t _value; \
+ if (!_tmp_ctx) MEM(_tmp_ctx = talloc_new(_request)); \
+ if (fr_value_box_from_fmt(_tmp_ctx, &_value, _request, _fmt) < 0) { \
+ talloc_free(_tmp_ctx); \
+ return -1; \
+ } \
+ if (fr_value_box_to_bin(_tmp_ctx, _request, &_p, &_len, &_value) < 0) { \
+ talloc_free(_tmp_ctx); \
+ return -1; \
+ }
+
+
+/** Calculate the MD5 hash of a string or attribute.
+ *
+ * Example: "%{md5:foo}" == "acbd18db4cc2f85cedef654fccc4a4d8"
+ */
+static ssize_t md5_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ uint8_t digest[16];
+ size_t i, len, inlen;
+ uint8_t *p;
+ FR_MD5_CTX md5_ctx;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
+
+ fr_md5_init(&md5_ctx);
+ fr_md5_update(&md5_ctx, p, inlen);
+ fr_md5_final(digest, &md5_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]);
+
+ talloc_free(tmp_ctx);
+
+ return strlen(*out);
+}
+
+/** Calculate the SHA1 hash of a string or attribute.
+ *
+ * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
+ */
+static ssize_t sha1_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ uint8_t digest[20];
+ size_t i, len, inlen;
+ uint8_t *p;
+ fr_sha1_ctx sha1_ctx;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
+
+ fr_sha1_init(&sha1_ctx);
+ fr_sha1_update(&sha1_ctx, p, inlen);
+ fr_sha1_final(digest, &sha1_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]);
+
+ talloc_free(tmp_ctx);
+
+ 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 TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt, EVP_MD const *md)
+{
+ uint8_t digest[EVP_MAX_MD_SIZE];
+ unsigned int digestlen, i, len;
+ size_t inlen;
+ uint8_t *p;
+ EVP_MD_CTX *md_ctx;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
+
+ md_ctx = EVP_MD_CTX_create();
+ EVP_DigestInit_ex(md_ctx, md, NULL);
+ EVP_DigestUpdate(md_ctx, p, inlen);
+ EVP_DigestFinal_ex(md_ctx, digest, &digestlen);
+ EVP_MD_CTX_destroy(md_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]);
+
+ talloc_free(tmp_ctx);
+
+ return strlen(*out);
+}
+
+# define EVP_MD_XLAT(_md) \
+static ssize_t _md##_xlat(TALLOC_CTX *ctx, char **out, size_t outlen,\
+ void const *mod_inst, void const *xlat_inst,\
+ REQUEST *request, char const *fmt)\
+{\
+ return evp_md_xlat(ctx, out, outlen, mod_inst, xlat_inst, request, fmt, EVP_##_md());\
+}
+
+EVP_MD_XLAT(sha256)
+EVP_MD_XLAT(sha512)
+
+# ifdef HAVE_EVP_SHA3_512
+EVP_MD_XLAT(sha3_256)
+EVP_MD_XLAT(sha3_512)
+# endif
+#endif
+
+/** Generate the HMAC-MD5 of a string or attribute
+ *
+ * Example: "%{hmacmd5:foo bar}" == "Zm9v"
+ */
+static ssize_t hmac_md5_xlat(TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+
+ char const *p, *q;
+ uint8_t digest[MD5_DIGEST_LENGTH];
+
+ char *data_fmt;
+
+ uint8_t *data_p, *key_p;
+ size_t data_len, key_len;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ 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 = fmt;
+ while (isspace(*p)) p++;
+
+ /*
+ * Find the delimiting char
+ */
+ q = strchr(p, ' ');
+ if (!q) {
+ REDEBUG("HMAC requires exactly two arguments (&data &key)");
+ return -1;
+ }
+
+ tmp_ctx = talloc_new(ctx);
+ data_fmt = talloc_bstrndup(tmp_ctx, p, q - p);
+ p = q + 1;
+
+ {
+ VALUE_FROM_FMT(tmp_ctx, data_p, data_len, request, data_fmt);
+ }
+ {
+ VALUE_FROM_FMT(tmp_ctx, key_p, key_len, request, p);
+ }
+ fr_hmac_md5(digest, data_p, data_len, key_p, key_len);
+ talloc_free(tmp_ctx);
+
+ 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(TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ char const *p, *q;
+ uint8_t digest[SHA1_DIGEST_LENGTH];
+
+ char *data_fmt;
+
+ uint8_t *data_p, *key_p;
+ size_t data_len, key_len;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ 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 = fmt;
+ while (isspace(*p)) p++;
+
+ /*
+ * Find the delimiting char
+ */
+ q = strchr(p, ' ');
+ if (!q) {
+ REDEBUG("HMAC requires exactly two arguments (&data &key)");
+ return -1;
+ }
+
+ tmp_ctx = talloc_new(ctx);
+ data_fmt = talloc_bstrndup(tmp_ctx, p, q - p);
+ p = q + 1;
+
+ {
+ VALUE_FROM_FMT(tmp_ctx, data_p, data_len, request, data_fmt);
+ }
+ {
+ VALUE_FROM_FMT(tmp_ctx, key_p, key_len, request, p);
+ }
+
+ fr_hmac_sha1(digest, data_p, data_len, key_p, key_len);
+
+ talloc_free(tmp_ctx);
+
+ return fr_bin2hex(*out, digest, sizeof(digest));
+}
+
+/** 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(TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ vp_tmpl_t *vpt = NULL;
+ fr_cursor_t cursor;
+ size_t len, freespace = outlen;
+ char *p = *out;
+
+ VALUE_PAIR *vp;
+
+ if (tmpl_afrom_attr_str(ctx, &vpt, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false) <= 0) {
+ RPEDEBUG("Invalid input");
+ return -1;
+ }
+
+ for (vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ FR_TOKEN op = vp->op;
+
+ vp->op = T_OP_EQ;
+ len = fr_pair_snprint(p, freespace, vp);
+ vp->op = op;
+
+ if (is_truncated(len, freespace)) {
+ no_space:
+ talloc_free(vpt);
+ REDEBUG("Insufficient space to store pair string, needed %zu bytes have %zu bytes",
+ (p - *out) + len, outlen);
+ 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';
+ talloc_free(vpt);
+
+ return (p - *out);
+}
+
+/** Encode string or attribute as base64
+ *
+ * Example: "%{base64:foo}" == "Zm9v"
+ */
+static ssize_t base64_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ size_t inlen;
+ uint8_t *p;
+ TALLOC_CTX *tmp_ctx = NULL;
+ ssize_t ret;
+
+ VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
+
+ /*
+ * We can accurately calculate the length of the output string
+ * if it's larger than outlen, the output would be useless so abort.
+ */
+ if ((FR_BASE64_ENC_LENGTH(inlen) + 1) > outlen) {
+ REDEBUG("xlat failed");
+
+ talloc_free(tmp_ctx);
+
+ return -1;
+ }
+
+ ret = fr_base64_encode(*out, outlen, p, inlen);
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+/** Convert base64 to hex
+ *
+ * Example: "%{base64tohex:Zm9v}" == "666f6f"
+ */
+static ssize_t base64_to_hex_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ uint8_t decbuf[1024];
+
+ ssize_t declen;
+ ssize_t len = strlen(fmt);
+
+ 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(TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
+{
+ vp_tmpl_t *vpt = NULL;
+ VALUE_PAIR *vp;
+ fr_cursor_t cursor, to_merge;
+ VALUE_PAIR *head = NULL;
+ ssize_t slen;
+ int count = 0;
+ char const *p = fmt;
+ char delim;
+
+ /*
+ * Trim whitespace
+ */
+ while (isspace(*p) && p++);
+
+ slen = tmpl_afrom_attr_substr(ctx, &vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
+ if (slen <= 0) {
+ RPEDEBUG("Invalid input");
+ return -1;
+ }
- vendor = fr_dict_vendor_by_da(vp->da);
- if (vendor) RIDEBUG2("Vendor : %i (%s)", vendor->pen, vendor->name);
- RIDEBUG2("Type : %s", fr_int2str(dict_attr_types, vp->vp_type, "<INVALID>"));
+ p += slen;
+
+ if (*p++ != ' ') {
+ arg_error:
+ talloc_free(vpt);
+ REDEBUG("explode needs exactly two arguments: &ref <delim>");
+ return -1;
+ }
+
+ if (*p == '\0') goto arg_error;
+
+ delim = *p;
+ fr_cursor_init(&to_merge, &head);
+
+ vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
+ while (vp) {
+ VALUE_PAIR *nvp;
+ 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->vp_type) {
- case FR_TYPE_VARIABLE_SIZE:
- RIDEBUG2("Length : %zu", vp->vp_length);
+ case FR_TYPE_OCTETS:
+ case FR_TYPE_STRING:
break;
default:
- break;
+ goto next;
}
- if (!RDEBUG_ENABLED4) continue;
-
- type = dict_attr_types;
- while (type->name) {
- int pad;
+ p = vp->vp_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->vp_ptr) goto next;
+ q = end;
+ }
- fr_value_box_t *dst = NULL;
+ /* Skip zero length */
+ if (q == p) {
+ p = q + 1;
+ continue;
+ }
- if ((fr_type_t) type->number == vp->vp_type) goto next_type;
+ nvp = fr_pair_afrom_da(talloc_parent(vp), vp->da);
+ if (!nvp) {
+ fr_pair_list_free(&head);
+ return -1;
+ }
+ nvp->tag = vp->tag;
- switch (type->number) {
- case FR_TYPE_INVALID: /* Not real type */
- case FR_TYPE_MAX: /* Not real type */
- case FR_TYPE_COMBO_IP_ADDR: /* Covered by IPv4 address IPv6 address */
- case FR_TYPE_COMBO_IP_PREFIX: /* Covered by IPv4 address IPv6 address */
- case FR_TYPE_TIMEVAL: /* Not a VALUE_PAIR type */
- case FR_TYPE_STRUCTURAL:
- goto next_type;
+ switch (vp->vp_type) {
+ case FR_TYPE_OCTETS:
+ {
+ uint8_t *buff;
- default:
- break;
+ buff = talloc_array(nvp, uint8_t, q - p);
+ memcpy(buff, p, q - p);
+ fr_pair_value_memsteal(nvp, buff);
}
+ break;
- dst = talloc_zero(vp, fr_value_box_t);
- /* We expect some to fail */
- if (fr_value_box_cast(dst, dst, type->number, NULL, &vp->data) < 0) {
- goto next_type;
- }
+ case FR_TYPE_STRING:
+ {
+ char *buff;
- value = fr_value_box_asprint(dst, dst, '\'');
- if (!value) goto next_type;
+ buff = talloc_array(nvp, char, (q - p) + 1);
+ memcpy(buff, p, q - p);
+ buff[q - p] = '\0';
+ fr_pair_value_strsteal(nvp, (char *)buff);
+ }
+ break;
- if ((pad = (11 - strlen(type->name))) < 0) {
- pad = 0;
+ default:
+ rad_assert(0);
}
- RINDENT();
- RDEBUG2("as %s%*s: %s", type->name, pad, " ", value);
- REXDENT();
+ fr_cursor_append(&to_merge, nvp);
- next_type:
- talloc_free(dst);
- type++;
+ p = q + 1; /* next */
+
+ count++;
}
+
+ /*
+ * Remove the unexploded version
+ */
+ vp = fr_cursor_remove(&cursor);
+ talloc_free(vp);
+ /*
+ * Remove sets cursor->current to
+ * the next iter value.
+ */
+ vp = fr_cursor_current(&cursor);
+ continue;
+
+ next:
+ vp = fr_cursor_next(&cursor);
}
- REXDENT();
+ fr_cursor_head(&to_merge);
+ fr_cursor_merge(&cursor, &to_merge);
talloc_free(vpt);
- return 0;
+ return snprintf(*out, outlen, "%i", count);
}
-/** Processes fmt as a map string and applies it to the current request
+/** Calculate number of seconds until the next n hour(s), day(s), week(s), year(s).
*
- * e.g. "%{map:&User-Name := 'foo'}"
+ * For example, if it were 16:18 %{nexttime:1h} would expand to 2520.
*
- * Allows sets of modifications to be cached and then applied.
- * Useful for processing generic attributes from LDAP.
+ * 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 xlat_map(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
+static ssize_t next_time_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
{
- vp_map_t *map = NULL;
- int ret;
+ long num;
- if (map_afrom_attr_str(request, &map, fmt,
- REQUEST_CURRENT, PAIR_LIST_REQUEST,
- REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) {
- RPEDEBUG("Failed parsing \"%s\" as map", fmt);
+ 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;
}
- RINDENT();
- ret = map_to_request(request, map, map_to_vp, NULL);
- REXDENT();
- talloc_free(map);
- if (ret < 0) return strlcpy(*out, "0", outlen);
+ if (p == q) {
+ num = 1;
+ } else {
+ p += q - p;
+ }
- return strlcpy(*out, "1", outlen);
-}
+ local->tm_sec = 0;
+ local->tm_min = 0;
-/** Prints the current module processing the request
- *
- */
-static ssize_t xlat_module(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, UNUSED char const *fmt)
-{
- strlcpy(*out, request->module, outlen);
+ switch (*p) {
+ case 'h':
+ local->tm_hour += num;
+ break;
- return strlen(*out);
-}
+ case 'd':
+ local->tm_hour = 0;
+ local->tm_mday += num;
+ break;
-#if defined(HAVE_REGEX) && defined(HAVE_PCRE)
-static ssize_t xlat_regex(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- char *p;
- size_t len;
+ case 'w':
+ local->tm_hour = 0;
+ local->tm_mday += (7 - local->tm_wday) + (7 * (num-1));
+ break;
- if (regex_request_to_sub_named(request, &p, request, fmt) < 0) return 0;
+ case 'm':
+ local->tm_hour = 0;
+ local->tm_mday = 1;
+ local->tm_mon += num;
+ break;
- len = talloc_array_length(p);
- if (len > outlen) {
- RDEBUG("Insufficient buffer space to write subcapture value, needed %zu bytes, have %zu bytes",
- len, outlen);
+ 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;
}
- strlcpy(*out, p, outlen);
- return len - 1; /* - \0 */
+ return snprintf(*out, outlen, "%" PRIu64, (uint64_t)(mktime(local) - now));
}
-#endif
-#ifdef WITH_UNLANG
-/** Implements the Foreach-Variable-X
+
+/** Parse the 3 arguments to lpad / rpad.
*
- * @see modcall()
+ * Parses a fmt string with the components @verbatim <tmpl> <pad_len> <pad_char>@endverbatim
+ *
+ * @param[out] vpt_p Template to retrieve value to pad.
+ * @param[out] pad_len_p Length the string needs to be padded to.
+ * @param[out] pad_char_p Char to use for padding.
+ * @param[in] request The current request.
+ * @param[in] fmt string to parse.
+ *
+ * @return
+ * - <= 0 the negative offset the parse error ocurred at.
+ * - >0 how many bytes of fmt were parsed.
*/
-static ssize_t xlat_foreach(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen,
- void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, UNUSED char const *fmt)
+static ssize_t parse_pad(vp_tmpl_t **vpt_p, size_t *pad_len_p, char *pad_char_p, REQUEST *request, char const *fmt)
{
- VALUE_PAIR **pvp;
-
- /*
- * See modcall, "FOREACH" for how this works.
- */
- pvp = (VALUE_PAIR **) request_data_reference(request, (void *)radius_get_vp, *(int const *) mod_inst);
- if (!pvp || !*pvp) return 0;
+ ssize_t slen;
+ unsigned long pad_len;
+ char const *p;
+ char *end;
+ vp_tmpl_t *vpt;
- *out = fr_pair_value_asprint(ctx, *pvp, '\0');
- return talloc_array_length(*out) - 1;
-}
-#endif
+ *pad_char_p = ' '; /* the default */
-/** Print data as string, if possible.
- *
- * If attribute "Foo" is defined as "octets" it will normally
- * be printed as 0x0a0a0a. The xlat "%{string:Foo}" will instead
- * expand to "\n\n\n"
- */
-static ssize_t xlat_string(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- ssize_t ret;
- VALUE_PAIR *vp;
- uint8_t buffer[64];
+ *vpt_p = NULL;
- while (isspace((int) *fmt)) fmt++;
+ p = fmt;
+ while (isspace((int) *p)) p++;
- if (outlen < 3) {
- nothing:
+ if (*p != '&') {
+ RDEBUG("First argument must be an attribute reference");
return 0;
}
- if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) goto nothing;
+ slen = tmpl_afrom_attr_substr(request, &vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
+ if (slen <= 0) {
+ RPEDEBUG("Failed parsing input string");
+ return slen;
+ }
+
+ p = fmt + slen;
+
+ while (isspace((int) *p)) p++;
+
+ pad_len = strtoul(p, &end, 10);
+ if ((pad_len == ULONG_MAX) || (pad_len > 8192)) {
+ talloc_free(vpt);
+ RDEBUG("Invalid pad_len found at: %s", p);
+ return fmt - p;
+ }
+
+ p += (end - p);
/*
- * These are printed specially.
+ * The pad_char_p character is optional.
+ *
+ * But we must have a space after the previous number,
+ * and we must have only ONE pad_char_p character.
*/
- switch (vp->vp_type) {
- case FR_TYPE_OCTETS:
- return fr_snprint(*out, outlen, (char const *) vp->vp_octets, vp->vp_length, '"');
+ if (*p) {
+ if (!isspace(*p)) {
+ talloc_free(vpt);
+ RDEBUG("Invalid text found at: %s", p);
+ return fmt - p;
+ }
- /*
- * Note that "%{string:...}" is NOT binary safe!
- * It is explicitly used to get rid of embedded zeros.
- */
- case FR_TYPE_STRING:
- return strlcpy(*out, vp->vp_strvalue, outlen);
+ while (isspace((int) *p)) p++;
- default:
- break;
+ if (p[1] != '\0') {
+ talloc_free(vpt);
+ RDEBUG("Invalid text found at: %s", p);
+ return fmt - p;
+ }
+
+ *pad_char_p = *p++;
}
- ret = fr_value_box_to_network(NULL, buffer, sizeof(buffer), &vp->data);
- if (ret < 0) return ret;
+ *vpt_p = vpt;
+ *pad_len_p = pad_len;
- return fr_snprint(*out, outlen, (char const *) buffer, ret, '\0');
+ return p - fmt;
}
-/** xlat expand string attribute value
+
+/** left pad a string
*
+ * %{lpad:&Attribute-Name length 'x'}
*/
-static ssize_t xlat_xlat(TALLOC_CTX *ctx, char **out, size_t outlen,
+static ssize_t lpad_xlat(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen,
UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
REQUEST *request, char const *fmt)
{
- ssize_t slen;
- VALUE_PAIR *vp;
+ char fill;
+ size_t pad;
+ ssize_t len;
+ vp_tmpl_t *vpt;
+ char *to_pad = NULL;
- while (isspace((int) *fmt)) fmt++;
+ if (parse_pad(&vpt, &pad, &fill, request, fmt) <= 0) return 0;
- if (outlen < 3) {
- nothing:
- return 0;
- }
+ if (!fr_cond_assert(vpt)) return 0;
- if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) goto nothing;
+ /*
+ * Print the attribute (left justified). If it's too
+ * big, we're done.
+ */
+ len = tmpl_aexpand(ctx, &to_pad, request, vpt, NULL, NULL);
+ if (len <= 0) return -1;
- RDEBUG2("EXPAND %s", fmt);
- RINDENT();
+ /*
+ * Already big enough, no padding required...
+ */
+ if ((size_t) len >= pad) {
+ *out = to_pad;
+ return pad;
+ }
/*
- * If it's a string, expand it again
+ * Realloc is actually pretty cheap in most cases...
*/
- if (vp->vp_type == FR_TYPE_STRING) {
- slen = xlat_eval(*out, outlen, request, vp->vp_strvalue, NULL, NULL);
- if (slen <= 0) return slen;
+ MEM(to_pad = talloc_realloc(ctx, to_pad, char, pad + 1));
+
/*
- * If it's not a string, treat it as a literal
+ * We have to shift the string to the right, and pad with
+ * "fill" characters.
*/
- } else {
- *out = fr_pair_value_asprint(ctx, vp, '\0');
- if (!*out) return -1;
- slen = talloc_array_length(*out) - 1;
- }
+ memmove(to_pad + (pad - len), to_pad, len + 1);
+ memset(to_pad, fill, pad - len);
- REXDENT();
- RDEBUG2("--> %s", *out);
+ *out = to_pad;
- return slen;
+ return pad;
}
-/** Dynamically change the debugging level for the current request
+/** right pad a string
*
- * Example %{debug:3}
+ * %{rpad:&Attribute-Name length 'x'}
*/
-static ssize_t xlat_debug(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
+static ssize_t rpad_xlat(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen,
+ UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
+ REQUEST *request, char const *fmt)
{
- int level = 0;
+ char fill;
+ size_t pad;
+ ssize_t len;
+ vp_tmpl_t *vpt;
+ char *to_pad = NULL;
+
+ rad_assert(!*out);
+
+ if (parse_pad(&vpt, &pad, &fill, request, fmt) <= 0) return 0;
+
+ if (!fr_cond_assert(vpt)) return 0;
/*
- * Expand to previous (or current) level
+ * Print the attribute (left justified). If it's too
+ * big, we're done.
*/
- snprintf(*out, outlen, "%d", request->log.lvl);
+ len = tmpl_aexpand(ctx, &to_pad, request, vpt, NULL, NULL);
+ if (len <= 0) return 0;
+
+ if ((size_t) len >= pad) {
+ *out = to_pad;
+ return pad;
+ }
+
+ MEM(to_pad = talloc_realloc(ctx, to_pad, char, pad + 1));
/*
- * Assume we just want to get the current value and NOT set it to 0
+ * We have to pad with "fill" characters.
*/
- if (!*fmt)
- goto done;
+ memset(to_pad + len, fill, pad - len);
+ to_pad[pad] = '\0';
- level = atoi(fmt);
- if (level == 0) {
- request->log.lvl = RAD_REQUEST_LVL_NONE;
- } else {
- if (level > 4) level = 4;
- request->log.lvl = level;
- }
+ *out = to_pad;
-done:
- return strlen(*out);
+ return pad;
}
/*
XLAT_REGISTER(regex);
#endif
+ xlat_register(NULL, "rand", rand_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "randstr", randstr_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "urlquote", urlquote_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "urlunquote", urlunquote_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "tolower", tolower_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "toupper", toupper_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "md5", md5_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "sha1", sha1_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+#ifdef HAVE_OPENSSL_EVP_H
+ xlat_register(NULL, "sha256", sha256_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "sha512", sha512_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+#endif
+ xlat_register(NULL, "hmacmd5", hmac_md5_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "hmacsha1", hmac_sha1_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "pairs", pairs_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+
+ xlat_register(NULL, "base64", base64_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "base64tohex", base64_to_hex_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+
+ xlat_register(NULL, "explode", explode_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+
+ xlat_register(NULL, "nexttime", next_time_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "lpad", lpad_xlat, NULL, NULL, 0, 0, true);
+ xlat_register(NULL, "rpad", rpad_xlat, NULL, NULL, 0, 0, true);
+
xlat_register(&xlat_foreach_inst[0], "debug", xlat_debug, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
c = xlat_func_find("debug");
rad_assert(c != NULL);
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
-
#include <ctype.h>
#include "rlm_expr.h"
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
return strlen(*out);
}
-/** Generate a random integer value
- *
- */
-static ssize_t rand_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- UNUSED REQUEST *request, char const *fmt)
-{
- int64_t result;
-
- result = atoi(fmt);
-
- /*
- * Too small or too big.
- */
- if (result <= 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 TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- char const *p;
- char *out_p = *out;
- unsigned int result;
- unsigned int number;
- size_t freespace = outlen;
-
- if (outlen <= 1) return 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_p++ = 'a' + (result % 26);
- break;
-
- /*
- * Uppercase letters
- */
- case 'C':
- *out_p++ = 'A' + (result % 26);
- break;
-
- /*
- * Numbers
- */
- case 'n':
- *out_p++ = '0' + (result % 10);
- break;
-
- /*
- * Alpha numeric
- */
- case 'a':
- *out_p++ = randstr_salt[result % (sizeof(randstr_salt) - 3)];
- break;
-
- /*
- * Punctuation
- */
- case '!':
- *out_p++ = randstr_punc[result % (sizeof(randstr_punc) - 1)];
- break;
-
- /*
- * Alpa numeric + punctuation
- */
- case '.':
- *out_p++ = '!' + (result % 95);
- break;
-
- /*
- * Alpha numeric + salt chars './'
- */
- case 's':
- *out_p++ = 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_p++ = 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_p, 3, "%02x", result % 256);
-
- /* Already decremented */
- freespace -= 1;
- out_p += 2;
- break;
-
- /*
- * Binary data with uppercase hexits
- */
- case 'H':
- if (freespace < 2) {
- break;
- }
-
- snprintf(out_p, 3, "%02X", result % 256);
-
- /* Already decremented */
- freespace -= 1;
- out_p += 2;
- break;
-
- default:
- REDEBUG("Invalid character class '%c'", *p);
-
- return -1;
- }
-
- if (number > 0) {
- number--;
- goto redo;
- }
-
- p++;
- }
-
- *out_p++ = '\0';
-
- return outlen - freespace;
-}
-
-/** URLencode special characters
- *
- * Example: "%{urlquote:http://example.org/}" == "http%3A%47%47example.org%47"
- */
-static ssize_t urlquote_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- UNUSED REQUEST *request, char const *fmt)
-{
- char const *p;
- char *out_p = *out;
- size_t freespace = outlen;
-
- if (outlen <= 1) return 0;
-
- p = fmt;
- while (*p && (--freespace > 0)) {
- if (isalnum(*p)) {
- *out_p++ = *p++;
- continue;
- }
-
- switch (*p) {
- case '-':
- case '_':
- case '.':
- case '~':
- *out_p++ = *p++;
- break;
-
- default:
- if (freespace < 3)
- break;
-
- /* MUST be upper case hex to be compliant */
- snprintf(out_p, 4, "%%%02X", (uint8_t) *p++); /* %XX */
-
- /* Already decremented */
- freespace -= 2;
- out_p += 3;
- }
- }
-
- *out_p = '\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 TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- char const *p;
- char *out_p = *out;
- char *c1, *c2;
- size_t freespace = outlen;
-
- if (outlen <= 1) return 0;
-
- p = fmt;
- while (*p && (--freespace > 0)) {
- if (*p != '%') {
- *out_p++ = *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, "Non-hex char in % sequence");
- return -1;
- }
- p++;
- *out_p++ = ((c1 - hextab) << 4) + (c2 - hextab);
- }
-
- *out_p = '\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
return outlen - freespace;
}
-/** Convert a string to lowercase
- *
- * Example: "%{tolower:Bar}" == "bar"
- *
- * Probably only works for ASCII
- */
-static ssize_t tolower_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- UNUSED REQUEST *request, char const *fmt)
-{
- 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 TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- UNUSED REQUEST *request, char const *fmt)
-{
- 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);
-}
-
-/** Decodes data or &Attr-Name to data
- *
- * This needs to die, and hopefully will die, when xlat functions accept
- * xlat node structures.
+/*
+ * 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.
*
- * @param ctx Talloc ctx for temporary allocations.
- * @param out fr_value_box_t containing a shallow copy of the attribute,
- * or the fmt string.
- * @param request current request.
- * @param fmt string.
- * @returns
- * - The length of the data.
- * - -1 on failure.
+ * 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 fr_value_box_from_fmt(TALLOC_CTX *ctx, fr_value_box_t *out, REQUEST *request, char const *fmt)
-{
- VALUE_PAIR *vp;
-
- while (isspace((int) *fmt)) fmt++;
-
- /*
- * Not an attribute reference? Just use the input format.
- */
- if (*fmt != '&') {
- memset(out, 0, sizeof(*out));
- out->vb_strvalue = fmt;
- out->datum.length = talloc_array_length(fmt) - 1;
- out->type = FR_TYPE_STRING;
- return 0;
- }
-
- /*
- * If it's an attribute reference, get the underlying
- * attribute, and then store the data in network byte
- * order.
- */
- if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) return -1;
-
- fr_value_box_copy(ctx, out, &vp->data);
-
- return 0;
-}
-
-static int fr_value_box_to_bin(TALLOC_CTX *ctx, REQUEST *request, uint8_t **out, size_t *outlen, fr_value_box_t const *in)
+static int mod_bootstrap(void *instance, CONF_SECTION *conf)
{
- fr_value_box_t bin;
-
- switch (in->type) {
- case FR_TYPE_STRING:
- case FR_TYPE_OCTETS:
- memcpy(out, &in->datum.ptr, sizeof(in));
- *outlen = in->datum.length;
- return 0;
-
- default:
- if (fr_value_box_cast(ctx, &bin, FR_TYPE_OCTETS, NULL, in) < 0) {
- RPERROR("Failed casting xlat input to 'octets'");
- return -1;
- }
- memcpy(out, &bin.datum.ptr, sizeof(in));
- *outlen = bin.datum.length;
- return 0;
- }
-}
+ rlm_expr_t *inst = instance;
-#define VALUE_FROM_FMT(_tmp_ctx, _p, _len, _request, _fmt) \
- fr_value_box_t _value; \
- if (!_tmp_ctx) MEM(_tmp_ctx = talloc_new(_request)); \
- if (fr_value_box_from_fmt(_tmp_ctx, &_value, _request, _fmt) < 0) { \
- talloc_free(_tmp_ctx); \
- return -1; \
- } \
- if (fr_value_box_to_bin(_tmp_ctx, _request, &_p, &_len, &_value) < 0) { \
- talloc_free(_tmp_ctx); \
- return -1; \
- }
-
-
-/** Calculate the MD5 hash of a string or attribute.
- *
- * Example: "%{md5:foo}" == "acbd18db4cc2f85cedef654fccc4a4d8"
- */
-static ssize_t md5_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- uint8_t digest[16];
- size_t i, len, inlen;
- uint8_t *p;
- FR_MD5_CTX md5_ctx;
- TALLOC_CTX *tmp_ctx = NULL;
-
- VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
-
- fr_md5_init(&md5_ctx);
- fr_md5_update(&md5_ctx, p, inlen);
- fr_md5_final(digest, &md5_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]);
-
- talloc_free(tmp_ctx);
-
- return strlen(*out);
-}
-
-/** Calculate the SHA1 hash of a string or attribute.
- *
- * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
- */
-static ssize_t sha1_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- uint8_t digest[20];
- size_t i, len, inlen;
- uint8_t *p;
- fr_sha1_ctx sha1_ctx;
- TALLOC_CTX *tmp_ctx = NULL;
-
- VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
-
- fr_sha1_init(&sha1_ctx);
- fr_sha1_update(&sha1_ctx, p, inlen);
- fr_sha1_final(digest, &sha1_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]);
-
- talloc_free(tmp_ctx);
-
- 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 TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt, EVP_MD const *md)
-{
- uint8_t digest[EVP_MAX_MD_SIZE];
- unsigned int digestlen, i, len;
- size_t inlen;
- uint8_t *p;
- EVP_MD_CTX *md_ctx;
- TALLOC_CTX *tmp_ctx = NULL;
-
- VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
-
- md_ctx = EVP_MD_CTX_create();
- EVP_DigestInit_ex(md_ctx, md, NULL);
- EVP_DigestUpdate(md_ctx, p, inlen);
- EVP_DigestFinal_ex(md_ctx, digest, &digestlen);
- EVP_MD_CTX_destroy(md_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]);
-
- talloc_free(tmp_ctx);
-
- return strlen(*out);
-}
-
-# define EVP_MD_XLAT(_md) \
-static ssize_t _md##_xlat(TALLOC_CTX *ctx, char **out, size_t outlen,\
- void const *mod_inst, void const *xlat_inst,\
- REQUEST *request, char const *fmt)\
-{\
- return evp_md_xlat(ctx, out, outlen, mod_inst, xlat_inst, request, fmt, EVP_##_md());\
-}
-
-EVP_MD_XLAT(sha256)
-EVP_MD_XLAT(sha512)
-
-# ifdef HAVE_EVP_SHA3_512
-EVP_MD_XLAT(sha3_256)
-EVP_MD_XLAT(sha3_512)
-# endif
-#endif
-
-/** Generate the HMAC-MD5 of a string or attribute
- *
- * Example: "%{hmacmd5:foo bar}" == "Zm9v"
- */
-static ssize_t hmac_md5_xlat(TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
-
- char const *p, *q;
- uint8_t digest[MD5_DIGEST_LENGTH];
-
- char *data_fmt;
-
- uint8_t *data_p, *key_p;
- size_t data_len, key_len;
- TALLOC_CTX *tmp_ctx = NULL;
-
- 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 = fmt;
- while (isspace(*p)) p++;
-
- /*
- * Find the delimiting char
- */
- q = strchr(p, ' ');
- if (!q) {
- REDEBUG("HMAC requires exactly two arguments (&data &key)");
- return -1;
- }
-
- tmp_ctx = talloc_new(ctx);
- data_fmt = talloc_bstrndup(tmp_ctx, p, q - p);
- p = q + 1;
-
- {
- VALUE_FROM_FMT(tmp_ctx, data_p, data_len, request, data_fmt);
- }
- {
- VALUE_FROM_FMT(tmp_ctx, key_p, key_len, request, p);
- }
- fr_hmac_md5(digest, data_p, data_len, key_p, key_len);
- talloc_free(tmp_ctx);
-
- 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(TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- char const *p, *q;
- uint8_t digest[SHA1_DIGEST_LENGTH];
-
- char *data_fmt;
-
- uint8_t *data_p, *key_p;
- size_t data_len, key_len;
- TALLOC_CTX *tmp_ctx = NULL;
-
- 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 = fmt;
- while (isspace(*p)) p++;
-
- /*
- * Find the delimiting char
- */
- q = strchr(p, ' ');
- if (!q) {
- REDEBUG("HMAC requires exactly two arguments (&data &key)");
- return -1;
- }
-
- tmp_ctx = talloc_new(ctx);
- data_fmt = talloc_bstrndup(tmp_ctx, p, q - p);
- p = q + 1;
-
- {
- VALUE_FROM_FMT(tmp_ctx, data_p, data_len, request, data_fmt);
- }
- {
- VALUE_FROM_FMT(tmp_ctx, key_p, key_len, request, p);
- }
-
- fr_hmac_sha1(digest, data_p, data_len, key_p, key_len);
-
- talloc_free(tmp_ctx);
-
- return fr_bin2hex(*out, digest, sizeof(digest));
-}
-
-/** 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(TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- vp_tmpl_t *vpt = NULL;
- fr_cursor_t cursor;
- size_t len, freespace = outlen;
- char *p = *out;
-
- VALUE_PAIR *vp;
-
- if (tmpl_afrom_attr_str(ctx, &vpt, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false) <= 0) {
- RPEDEBUG("Invalid input");
- return -1;
- }
-
- for (vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
- vp;
- vp = fr_cursor_next(&cursor)) {
- FR_TOKEN op = vp->op;
-
- vp->op = T_OP_EQ;
- len = fr_pair_snprint(p, freespace, vp);
- vp->op = op;
-
- if (is_truncated(len, freespace)) {
- no_space:
- talloc_free(vpt);
- REDEBUG("Insufficient space to store pair string, needed %zu bytes have %zu bytes",
- (p - *out) + len, outlen);
- 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';
- talloc_free(vpt);
-
- return (p - *out);
-}
-
-/** Encode string or attribute as base64
- *
- * Example: "%{base64:foo}" == "Zm9v"
- */
-static ssize_t base64_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- size_t inlen;
- uint8_t *p;
- TALLOC_CTX *tmp_ctx = NULL;
- ssize_t ret;
-
- VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
-
- /*
- * We can accurately calculate the length of the output string
- * if it's larger than outlen, the output would be useless so abort.
- */
- if ((FR_BASE64_ENC_LENGTH(inlen) + 1) > outlen) {
- REDEBUG("xlat failed");
-
- talloc_free(tmp_ctx);
-
- return -1;
- }
-
- ret = fr_base64_encode(*out, outlen, p, inlen);
- talloc_free(tmp_ctx);
-
- return ret;
-}
-
-/** Convert base64 to hex
- *
- * Example: "%{base64tohex:Zm9v}" == "666f6f"
- */
-static ssize_t base64_to_hex_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- uint8_t decbuf[1024];
-
- ssize_t declen;
- ssize_t len = strlen(fmt);
-
- 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(TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- vp_tmpl_t *vpt = NULL;
- VALUE_PAIR *vp;
- fr_cursor_t cursor, to_merge;
- VALUE_PAIR *head = NULL;
- ssize_t slen;
- int count = 0;
- char const *p = fmt;
- char delim;
-
- /*
- * Trim whitespace
- */
- while (isspace(*p) && p++);
-
- slen = tmpl_afrom_attr_substr(ctx, &vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
- if (slen <= 0) {
- RPEDEBUG("Invalid input");
- return -1;
- }
-
- p += slen;
-
- if (*p++ != ' ') {
- arg_error:
- talloc_free(vpt);
- REDEBUG("explode needs exactly two arguments: &ref <delim>");
- return -1;
- }
-
- if (*p == '\0') goto arg_error;
-
- delim = *p;
-
- fr_cursor_init(&to_merge, &head);
-
- vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
- while (vp) {
- VALUE_PAIR *nvp;
- 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->vp_type) {
- case FR_TYPE_OCTETS:
- case FR_TYPE_STRING:
- break;
-
- default:
- goto next;
- }
-
- p = vp->vp_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->vp_ptr) goto next;
- q = end;
- }
-
- /* Skip zero length */
- if (q == p) {
- p = q + 1;
- continue;
- }
-
- nvp = fr_pair_afrom_da(talloc_parent(vp), vp->da);
- if (!nvp) {
- fr_pair_list_free(&head);
- return -1;
- }
- nvp->tag = vp->tag;
-
- switch (vp->vp_type) {
- case FR_TYPE_OCTETS:
- {
- uint8_t *buff;
-
- buff = talloc_array(nvp, uint8_t, q - p);
- memcpy(buff, p, q - p);
- fr_pair_value_memsteal(nvp, buff);
- }
- break;
-
- case FR_TYPE_STRING:
- {
- char *buff;
-
- buff = talloc_array(nvp, char, (q - p) + 1);
- memcpy(buff, p, q - p);
- buff[q - p] = '\0';
- fr_pair_value_strsteal(nvp, (char *)buff);
- }
- break;
-
- default:
- rad_assert(0);
- }
-
- fr_cursor_append(&to_merge, nvp);
-
- p = q + 1; /* next */
-
- count++;
- }
-
- /*
- * Remove the unexploded version
- */
- vp = fr_cursor_remove(&cursor);
- talloc_free(vp);
- /*
- * Remove sets cursor->current to
- * the next iter value.
- */
- vp = fr_cursor_current(&cursor);
- continue;
-
- next:
- vp = fr_cursor_next(&cursor);
- }
-
- fr_cursor_head(&to_merge);
- fr_cursor_merge(&cursor, &to_merge);
- talloc_free(vpt);
-
- 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 TALLOC_CTX *ctx, char **out, size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- 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));
-}
-
-
-/** Parse the 3 arguments to lpad / rpad.
- *
- * Parses a fmt string with the components @verbatim <tmpl> <pad_len> <pad_char>@endverbatim
- *
- * @param[out] vpt_p Template to retrieve value to pad.
- * @param[out] pad_len_p Length the string needs to be padded to.
- * @param[out] pad_char_p Char to use for padding.
- * @param[in] request The current request.
- * @param[in] fmt string to parse.
- *
- * @return
- * - <= 0 the negative offset the parse error ocurred at.
- * - >0 how many bytes of fmt were parsed.
- */
-static ssize_t parse_pad(vp_tmpl_t **vpt_p, size_t *pad_len_p, char *pad_char_p, REQUEST *request, char const *fmt)
-{
- ssize_t slen;
- unsigned long pad_len;
- char const *p;
- char *end;
- vp_tmpl_t *vpt;
-
- *pad_char_p = ' '; /* the default */
-
- *vpt_p = NULL;
-
- p = fmt;
- while (isspace((int) *p)) p++;
-
- if (*p != '&') {
- RDEBUG("First argument must be an attribute reference");
- return 0;
- }
-
- slen = tmpl_afrom_attr_substr(request, &vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
- if (slen <= 0) {
- RPEDEBUG("Failed parsing input string");
- return slen;
- }
-
- p = fmt + slen;
-
- while (isspace((int) *p)) p++;
-
- pad_len = strtoul(p, &end, 10);
- if ((pad_len == ULONG_MAX) || (pad_len > 8192)) {
- talloc_free(vpt);
- RDEBUG("Invalid pad_len found at: %s", p);
- return fmt - p;
- }
-
- p += (end - p);
-
- /*
- * The pad_char_p character is optional.
- *
- * But we must have a space after the previous number,
- * and we must have only ONE pad_char_p character.
- */
- if (*p) {
- if (!isspace(*p)) {
- talloc_free(vpt);
- RDEBUG("Invalid text found at: %s", p);
- return fmt - p;
- }
-
- while (isspace((int) *p)) p++;
-
- if (p[1] != '\0') {
- talloc_free(vpt);
- RDEBUG("Invalid text found at: %s", p);
- return fmt - p;
- }
-
- *pad_char_p = *p++;
- }
-
- *vpt_p = vpt;
- *pad_len_p = pad_len;
-
- return p - fmt;
-}
-
-
-/** left pad a string
- *
- * %{lpad:&Attribute-Name length 'x'}
- */
-static ssize_t lpad_xlat(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- char fill;
- size_t pad;
- ssize_t len;
- vp_tmpl_t *vpt;
- char *to_pad = NULL;
-
- if (parse_pad(&vpt, &pad, &fill, request, fmt) <= 0) return 0;
-
- if (!fr_cond_assert(vpt)) return 0;
-
- /*
- * Print the attribute (left justified). If it's too
- * big, we're done.
- */
- len = tmpl_aexpand(ctx, &to_pad, request, vpt, NULL, NULL);
- if (len <= 0) return -1;
-
- /*
- * Already big enough, no padding required...
- */
- if ((size_t) len >= pad) {
- *out = to_pad;
- return pad;
- }
-
- /*
- * Realloc is actually pretty cheap in most cases...
- */
- MEM(to_pad = talloc_realloc(ctx, to_pad, char, pad + 1));
-
- /*
- * We have to shift the string to the right, and pad with
- * "fill" characters.
- */
- memmove(to_pad + (pad - len), to_pad, len + 1);
- memset(to_pad, fill, pad - len);
-
- *out = to_pad;
-
- return pad;
-}
-
-/** right pad a string
- *
- * %{rpad:&Attribute-Name length 'x'}
- */
-static ssize_t rpad_xlat(TALLOC_CTX *ctx, char **out, UNUSED size_t outlen,
- UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
- REQUEST *request, char const *fmt)
-{
- char fill;
- size_t pad;
- ssize_t len;
- vp_tmpl_t *vpt;
- char *to_pad = NULL;
-
- rad_assert(!*out);
-
- if (parse_pad(&vpt, &pad, &fill, request, fmt) <= 0) return 0;
-
- if (!fr_cond_assert(vpt)) return 0;
-
- /*
- * Print the attribute (left justified). If it's too
- * big, we're done.
- */
- len = tmpl_aexpand(ctx, &to_pad, request, vpt, NULL, NULL);
- if (len <= 0) return 0;
-
- if ((size_t) len >= pad) {
- *out = to_pad;
- return pad;
- }
-
- MEM(to_pad = talloc_realloc(ctx, to_pad, char, pad + 1));
-
- /*
- * We have to pad with "fill" characters.
- */
- memset(to_pad + len, fill, pad - len);
- to_pad[pad] = '\0';
-
- *out = to_pad;
-
- 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(void *instance, CONF_SECTION *conf)
-{
- rlm_expr_t *inst = instance;
-
- inst->xlat_name = cf_section_name2(conf);
- if (!inst->xlat_name) {
- inst->xlat_name = cf_section_name1(conf);
+ inst->xlat_name = cf_section_name2(conf);
+ if (!inst->xlat_name) {
+ inst->xlat_name = cf_section_name1(conf);
}
xlat_register(inst, inst->xlat_name, expr_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
-
- xlat_register(inst, "rand", rand_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "randstr", randstr_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "urlquote", urlquote_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "urlunquote", urlunquote_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "escape", escape_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "unescape", unescape_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "tolower", tolower_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "toupper", toupper_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "md5", md5_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "sha1", sha1_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
-#ifdef HAVE_OPENSSL_EVP_H
- xlat_register(inst, "sha256", sha256_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "sha512", sha512_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
-#endif
- xlat_register(inst, "hmacmd5", hmac_md5_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "hmacsha1", hmac_sha1_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "pairs", pairs_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
-
- xlat_register(inst, "base64", base64_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "base64tohex", base64_to_hex_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
-
- xlat_register(inst, "explode", explode_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
-
- xlat_register(inst, "nexttime", next_time_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
- xlat_register(inst, "lpad", lpad_xlat, NULL, NULL, 0, 0, true);
- xlat_register(inst, "rpad", rpad_xlat, NULL, NULL, 0, 0, true);
+ xlat_register(NULL, "escape", escape_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ xlat_register(NULL, "unescape", unescape_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
/*
* Initialize various paircompare functions