From: Arran Cudbard-Bell Date: Wed, 16 May 2018 03:47:23 +0000 (+0600) Subject: Move xlat functions out of rlm_expr and back into xlat_func X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ad221ed6602288ea5f9d9c8dc1984b33cd3939aa;p=thirdparty%2Ffreeradius-server.git Move xlat functions out of rlm_expr and back into xlat_func We probably need an rlm_escape to deal with the escape expansions --- diff --git a/src/main/xlat_func.c b/src/main/xlat_func.c index 73d41f2b290..a2bf2aa2062 100644 --- a/src/main/xlat_func.c +++ b/src/main/xlat_func.c @@ -30,6 +30,11 @@ RCSID("$Id$") #include #include #include +#include +#include +#ifdef HAVE_OPENSSL_EVP_H +# include +#endif #include #include "xlat_priv.h" @@ -50,6 +55,20 @@ static char const * const xlat_foreach_names[] = {"Foreach-Variable-0", 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. @@ -288,280 +307,1419 @@ static ssize_t xlat_debug_attr(UNUSED TALLOC_CTX *ctx, UNUSED char **out, UNUSED } 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, "")); + + 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 }" + */ +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, "")); + p += slen; + + if (*p++ != ' ') { + arg_error: + talloc_free(vpt); + REDEBUG("explode needs exactly two arguments: &ref "); + 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: 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 @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; } /* @@ -1223,6 +2381,31 @@ int xlat_init(void) 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); diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c index 86a3004286e..0c0bb6ce6a8 100644 --- a/src/modules/rlm_expr/rlm_expr.c +++ b/src/modules/rlm_expr/rlm_expr.c @@ -26,16 +26,10 @@ RCSID("$Id$") USES_APPLE_DEPRECATED_API #include -#include -#include -#include + #include #include -#ifdef HAVE_OPENSSL_EVP_H -# include -#endif - #include #include "rlm_expr.h" @@ -53,18 +47,6 @@ static const CONF_PARSER module_config[] = { 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 @@ -549,266 +531,6 @@ static ssize_t expr_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen, 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:foo.jpg}" == "=60img=62foo.jpg=60/img=62" @endverbatim @@ -915,932 +637,28 @@ static ssize_t unescape_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen, 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 }" - */ -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 "); - 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: 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 @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