static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head, char const **error) { ssize_t slen; char *p, *q, *brace; char const *attrname; xlat_exp_t *node; rad_assert(fmt[0] == '%'); rad_assert(fmt[1] == '{'); /* * %{%{...}:-bar} */ if ((fmt[2] == '%') && (fmt[3] == '{')) { return xlat_tokenize_alternation(ctx, fmt, head, error); } XLAT_DEBUG("EXPANSION: %s", fmt); node = talloc_zero(ctx, xlat_exp_t); attrname = node->fmt = fmt + 2; node->len = 0; #ifdef HAVE_REGEX_H /* * Handle regex's specially. */ if (isdigit((int) fmt[2]) && (fmt[3] == '}')) { if (fmt[2] == '9') { talloc_free(node); *error = "Invalid regex reference"; return -2; } XLAT_DEBUG("REGEX: %s", fmt); fmt[3] = '\0'; node->num = fmt[2] - '0'; /* ASCII */ node->type = XLAT_REGEX; *head = node; return 4; } #endif /* HAVE_REGEX_H */ /* * %{Attr-Name} * %{Attr-Name[#]} * %{Tunnel-Password:1} * %{Tunnel-Password:1[#]} * %{request:Attr-Name} * %{request:Tunnel-Password:1} * %{request:Tunnel-Password:1[#]} * %{mod:foo} */ q = brace = NULL; for (p = fmt + 2; *p != '\0'; p++) { if (*p == ':') break; if (isspace((int) *p)) break; if (*p == '[') break; if (*p == '}') break; } if (*p != ':') p = NULL; /* * Might be a module name reference. */ if (p) { *p = '\0'; /* * %{mod:foo} */ node->xlat = xlat_find(node->fmt); if (node->xlat) { node->type = XLAT_MODULE; XLAT_DEBUG("MOD: %s --> %s", node->fmt, p); slen = xlat_tokenize_literal(node, p + 1, &node->child, true, error); if (slen <= 0) { talloc_free(node); return slen - (p - fmt); } p += slen + 1; *head = node; rad_assert(node->next == NULL); return p - fmt; } /* * Modules can have '}' in their RHS, so we * didn't check for that until now. * * As of now, node->fmt MUST be a reference to an * attribute, however complicated. So it MUST have a closing brace. */ brace = strchr(p + 1, '}'); if (!brace) goto no_brace; *brace = '\0'; /* * %{User-Name} * %{User-Name[1]} * %{Tunnel-Password:1} * %{request:Tunnel-Password:1} * * <sigh> The syntax is fairly poor. */ XLAT_DEBUG("Looking for list in '%s'", attrname); /* * Not a module. Has to be an attribute * reference. * * As of v3, we've removed %{request: ..>} as * internally registered xlats. */ *p = ':'; node->ref = radius_request_name(&attrname, REQUEST_CURRENT); rad_assert(node->ref != REQUEST_UNKNOWN); node->list = radius_list_name(&attrname, PAIR_LIST_REQUEST); if (node->list == PAIR_LIST_UNKNOWN) { talloc_free(node); *error = "Unknown module"; return -2; } /* * Check for a trailing tag. */ p = strchr(attrname, ':'); if (p) *p = '\0'; } else { brace = strchr(attrname, '}'); if (!brace) { no_brace: talloc_free(node); *error = "No matching closing brace"; return -1; /* second character of format string */ } *brace = '\0'; node->ref = REQUEST_CURRENT; node->list = PAIR_LIST_REQUEST; } *brace = '\0'; XLAT_DEBUG("Looking for attribute name in %s", attrname); /* * Allow for an array reference. They come AFTER the * tag, if the tag exists. Otherwise, they come after * the attribute name. */ if (p) { q = strchr(p + 1, '['); } else { q = strchr(attrname, '['); } if (q) *(q++) = '\0'; if (!*attrname) { talloc_free(node); *error = "Empty expression is invalid"; return -(attrname - fmt); } /* * It's either an attribute name, or a Tunnel-Password:TAG * with the ':' already set to NULL. */ node->da = dict_attrbyname(attrname); if (!node->da) { /* * Foreach. Maybe other stuff, too. */ node->xlat = xlat_find(attrname); if (node->xlat) { node->type = XLAT_VIRTUAL; node->fmt = attrname; XLAT_DEBUG("VIRTUAL: %s", node->fmt); *head = node; rad_assert(node->next == NULL); brace++; return brace - fmt; } talloc_free(node); *error = "Unknown attribute"; return -(attrname - fmt); } /* * Parse the tag. */ if (p) { unsigned long tag; char *end; if (!node->da->flags.has_tag) { talloc_free(node); *error = "Attribute cannot have a tag"; return - (p - fmt); } tag = strtoul(p + 1, &end, 10); p++; if (tag == ULONG_MAX) { talloc_free(node); *error = "Invalid tag value"; return - (p - fmt); } node->tag = tag; p = end; if (*p) { talloc_free(node); *error = "Unexpected text after tag"; return - (p - fmt); } } else { node->tag = TAG_ANY; /* leave p alone */ } /* * Check for array reference */ if (q) { unsigned long num; char *end; p = q; if (*p== '#') { node->num = 65536; p++; } else if (*p == '*') { node->num = 65537; p++; } else if (isdigit((int) *p)) { num = strtoul(p, &end, 10); if ((num == ULONG_MAX) || (num > 65535)) { talloc_free(node); *error = "Invalid number"; return - (p - fmt); } p = end; DEBUG("END %s", p); node->num = num; } else { talloc_free(node); *error = "Invalid array reference"; return - (p - fmt); } if (*p != ']') { talloc_free(node); *error = "Expected ']'"; return - (p - fmt); } p++; if (*p) { talloc_free(node); *error = "Unexpected text after array reference"; return - (p - fmt); } } rad_assert(!p || (p == brace)); node->type = XLAT_ATTRIBUTE; p = brace + 1; *head = node; rad_assert(node->next == NULL); return p - fmt; }
static ssize_t xlat_tokenize_alternation(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head, char const **error) { ssize_t slen; char *p; xlat_exp_t *node; rad_assert(fmt[0] == '%'); rad_assert(fmt[1] == '{'); rad_assert(fmt[2] == '%'); rad_assert(fmt[3] == '{'); XLAT_DEBUG("ALTERNATE: %s", fmt); node = talloc_zero(ctx, xlat_exp_t); node->type = XLAT_ALTERNATE; p = fmt + 2; slen = xlat_tokenize_expansion(node, p, &node->child, error); if (slen <= 0) { talloc_free(node); return slen - (p - fmt); } p += slen; if (p[0] != ':') { talloc_free(node); *error = "Expected ':' after first expansion"; return -(p - fmt); } p++; if (p[0] != '-') { talloc_free(node); *error = "Expected '-' after ':'"; return -(p - fmt); } p++; /* * Allow the RHS to be empty as a special case. */ if (*p == '}') { /* * Hack up an empty string. */ node->alternate = talloc_zero(node, xlat_exp_t); node->alternate->type = XLAT_LITERAL; node->alternate->fmt = talloc_strdup(node->alternate, ""); *(p++) = '\0'; } else { slen = xlat_tokenize_literal(node, p, &node->alternate, true, error); if (slen <= 0) { talloc_free(node); return slen - (p - fmt); } if (!node->alternate) { talloc_free(node); *error = "Empty expansion is invalid"; return -(p - fmt); } p += slen; } *head = node; return p - fmt; }
static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head, int brace, char const **error) { char *p, *q; xlat_exp_t *node; if (!*fmt) return 0; XLAT_DEBUG("LITERAL: %s", fmt); node = talloc_zero(ctx, xlat_exp_t); node->fmt = fmt; node->len = 0; node->type = XLAT_LITERAL; p = fmt; q = fmt; while (*p) { /* * Convert \n to it's literal representation. */ if (p[0] == '\\') switch (p[1]) { case 't': *(q++) = '\t'; p += 2; node->len++; continue; case 'n': *(q++) = '\n'; p += 2; node->len++; continue; case 'x': p += 2; if (!p[0] || !p[1]) { talloc_free(node); *error = "Hex expansion requires two hex digits"; return -(p - fmt); } if (!fr_hex2bin((uint8_t *) q, p, 2)) { talloc_free(node); *error = "Invalid hex characters"; return -(p - fmt); } /* * Don't let people shoot themselves in the foot. * \x00 is forbidden. */ if (!*q) { talloc_free(node); *error = "Cannot add zero byte to printable string"; return -(p - fmt); } p += 2; q++; node->len++; continue; default: *(q++) = *p; p += 2; node->len++; continue; } /* * Process the expansion. */ if ((p[0] == '%') && (p[1] == '{')) { ssize_t slen; XLAT_DEBUG("LITERAL: %s --> %s", node->fmt, p); slen = xlat_tokenize_expansion(node, p, &node->next, error); if (slen <= 0) { talloc_free(node); return slen - (p - fmt); } *p = '\0'; /* end the literal */ p += slen; rad_assert(node->next != NULL); /* * Short-circuit the recursive call. * This saves another function call and * memory allocation. */ if (!*p) break; /* * "foo %{User-Name} bar" * LITERAL "foo " * EXPANSION User-Name * LITERAL " bar" */ slen = xlat_tokenize_literal(node->next, p, &(node->next->next), brace, error); rad_assert(slen != 0); if (slen < 0) { talloc_free(node); return slen - (p - fmt); } p += slen; break; /* stop processing the string */ } /* * Check for valid single-character expansions. */ if (p[0] == '%') { ssize_t slen; xlat_exp_t *next; if (!p[1] || !strchr("%dlmtDGHISTY", p[1])) { talloc_free(node); *error = "Invalid variable expansion"; p++; return - (p - fmt); } XLAT_DEBUG("PERCENT: %s --> %c", node->fmt, p[1]); next = talloc_zero(node, xlat_exp_t); next->fmt = p + 1; next->len = 1; next->type = XLAT_PERCENT; node->next = next; *p = '\0'; p += 2; if (!*p) break; /* * And recurse. */ slen = xlat_tokenize_literal(node->next, p, &(node->next->next), brace, error); rad_assert(slen != 0); if (slen < 0) { talloc_free(node); return slen - (p - fmt); } p += slen; break; /* stop processing the string */ } /* * If required, eat the brace. */ if (brace && (*p == '}')) { *q = '\0'; p++; break; } *(q++) = *(p++); node->len++; } /* * Squash zero-width literals */ if (node->len > 0) { *head = node; } else { (void) talloc_steal(ctx, node->next); *head = node->next; talloc_free(node); } return p - fmt; }
static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * const node, RADIUS_ESCAPE_STRING escape, void *escape_ctx, int lvl) { ssize_t rcode; char *str = NULL, *child; REQUEST *ref; XLAT_DEBUG("%.*sxlat aprint %d", lvl, xlat_spaces, node->type); switch (node->type) { /* * Don't escape this. */ case XLAT_LITERAL: XLAT_DEBUG("xlat_aprint LITERAL"); return talloc_strdup(ctx, node->fmt); /* * Do a one-character expansion. */ case XLAT_PERCENT: { char const *p; char *nl; size_t freespace = 256; struct tm ts; time_t when; XLAT_DEBUG("xlat_aprint PERCENT"); str = talloc_array(ctx, char, freespace); /* @todo do better allocation */ p = node->fmt; when = request->timestamp; if (request->packet) { when = request->packet->timestamp.tv_sec; } switch (*p) { case '%': str[0] = '%'; str[1] = '\0'; break; case 'd': /* request day */ if (!localtime_r(&when, &ts)) goto error; strftime(str, freespace, "%d", &ts); break; case 'l': /* request timestamp */ snprintf(str, freespace, "%lu", (unsigned long) when); break; case 'm': /* request month */ if (!localtime_r(&when, &ts)) goto error; strftime(str, freespace, "%m", &ts); break; case 't': /* request timestamp */ CTIME_R(&when, str, freespace); nl = strchr(str, '\n'); if (nl) *nl = '\0'; break; case 'D': /* request date */ if (!localtime_r(&when, &ts)) goto error; strftime(str, freespace, "%Y%m%d", &ts); break; case 'G': /* request minute */ if (!localtime_r(&when, &ts)) goto error; strftime(str, freespace, "%M", &ts); break; case 'H': /* request hour */ if (!localtime_r(&when, &ts)) goto error; strftime(str, freespace, "%H", &ts); break; case 'I': /* Request ID */ if (request->packet) { snprintf(str, freespace, "%i", request->packet->id); } break; case 'S': /* request timestamp in SQL format*/ if (!localtime_r(&when, &ts)) goto error; strftime(str, freespace, "%Y-%m-%d %H:%M:%S", &ts); break; case 'T': /* request timestamp */ if (!localtime_r(&when, &ts)) goto error; strftime(str, freespace, "%Y-%m-%d-%H.%M.%S.000000", &ts); break; case 'Y': /* request year */ if (!localtime_r(&when, &ts)) { error: REDEBUG("Failed converting packet timestamp to localtime: %s", fr_syserror(errno)); talloc_free(str); return NULL; } strftime(str, freespace, "%Y", &ts); break; default: rad_assert(0 == 1); break; } } break; case XLAT_ATTRIBUTE: XLAT_DEBUG("xlat_aprint ATTRIBUTE"); ref = request; if (radius_request(&ref, node->ref) < 0) { return NULL; } /* * Some attributes are virtual <sigh> */ str = xlat_getvp(ctx, ref, node->list, node->da, node->tag, node->num, true); if (str) { XLAT_DEBUG("expand attr %s --> '%s'", node->da->name, str); } break; case XLAT_VIRTUAL: XLAT_DEBUG("xlat_aprint VIRTUAL"); str = talloc_array(ctx, char, 1024); /* FIXME: have the module call talloc_asprintf */ rcode = node->xlat->func(node->xlat->instance, request, NULL, str, 1024); if (rcode < 0) { talloc_free(str); return NULL; } break; case XLAT_MODULE: XLAT_DEBUG("xlat_aprint MODULE"); if (xlat_process(&child, request, node->child, node->xlat->escape, node->xlat->instance) == 0) { return NULL; } XLAT_DEBUG("%.*sexpand mod %s --> '%s'", lvl, xlat_spaces, node->fmt, child); str = talloc_array(ctx, char, 1024); /* FIXME: have the module call talloc_asprintf */ *str = '\0'; /* Be sure the string is NULL terminated, we now only free on error */ rcode = node->xlat->func(node->xlat->instance, request, child, str, 1024); talloc_free(child); if (rcode < 0) { talloc_free(str); return NULL; } break; #ifdef HAVE_REGEX_H case XLAT_REGEX: XLAT_DEBUG("xlat_aprint REGEX"); child = request_data_reference(request, request, REQUEST_DATA_REGEX | node->num); if (!child) return NULL; str = talloc_strdup(ctx, child); break; #endif case XLAT_ALTERNATE: XLAT_DEBUG("xlat_aprint ALTERNATE"); rad_assert(node->child != NULL); rad_assert(node->alternate != NULL); str = xlat_aprint(ctx, request, node->child, escape, escape_ctx, lvl); if (str) break; str = xlat_aprint(ctx, request, node->alternate, escape, escape_ctx, lvl); break; } /* * Escape the non-literals we found above. */ if (str && escape) { char *escaped; escaped = talloc_array(ctx, char, 1024); /* FIXME: do something intelligent */ escape(request, escaped, 1024, str, escape_ctx); talloc_free(str); str = escaped; }
static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head, char const **error) { ssize_t slen; char *p, *q; char *start; xlat_exp_t *node; #ifdef HAVE_REGEX long num; #endif rad_assert(fmt[0] == '%'); rad_assert(fmt[1] == '{'); /* * %{%{...}:-bar} */ if ((fmt[2] == '%') && (fmt[3] == '{')) return xlat_tokenize_alternation(ctx, fmt, head, error); XLAT_DEBUG("EXPANSION <-- %s", fmt); node = talloc_zero(ctx, xlat_exp_t); node->fmt = start = talloc_typed_strdup(node, fmt + 2); node->len = 0; #ifdef HAVE_REGEX /* * Handle regex's specially. */ p = start; num = strtol(p, &q, 10); if (p != q && (*q == '}')) { XLAT_DEBUG("REGEX <-- %s", fmt); *q = '\0'; if ((num > REQUEST_MAX_REGEX) || (num < 0)) { talloc_free(node); *error = "Invalid regex reference. Must be in range 0-" STRINGIFY(REQUEST_MAX_REGEX); return -2; /* error */ } node->regex_index = num; node->type = XLAT_REGEX; *head = node; node->len = (q - start); MEM(start = talloc_realloc_bstr(start, node->len)); q++; /* Skip closing brace */ return 2 + (q - start); } #endif /* HAVE_REGEX */ /* * %{Attr-Name} * %{Attr-Name[#]} * %{Tunnel-Password:1} * %{Tunnel-Password:1[#]} * %{request:Attr-Name} * %{request:Tunnel-Password:1} * %{request:Tunnel-Password:1[#]} * %{mod:foo} */ /* * This is for efficiency, so we don't search for an xlat, * when what's being referenced is obviously an attribute. */ p = start; for (q = p; *q != '\0'; q++) { if (*q == ':') break; if (isspace((int) *q)) break; if (*q == '[') continue; if (*q == '}') break; } /* * Check for empty expressions %{} */ if ((*q == '}') && (q == p)) { talloc_free(node); *error = "Empty expression is invalid"; return (-(p - start)) - 2; /* error */ } /* * Might be a module name reference. * * If it's not, it's an attribute or parse error. */ if (*q == ':') { *q = '\0'; node->xlat = xlat_func_find(node->fmt); if (node->xlat) { /* * %{mod:foo} */ node->type = XLAT_FUNC; p = q + 1; XLAT_DEBUG("MOD <-- %s ... %s", node->fmt, p); slen = xlat_tokenize_literal(node, p, &node->child, true, error); if (slen < 0) { talloc_free(node); return (slen - (p - start)) - 2; /* error */ } p += slen; node->async_safe = (node->xlat->async_safe && node->child->async_safe); *head = node; rad_assert(node->next == NULL); node->len = p - start; MEM(start = talloc_realloc_bstr(start, node->len)); return 2 + node->len; } *q = ':'; /* Avoids a talloc_strdup */ } /* * The first token ends with: * - '[' - Which is an attribute index, so it must be an attribute. * - '}' - The end of the expansion, which means it was a bareword. */ slen = tmpl_afrom_attr_substr(node, &node->attr, p, &(vp_tmpl_rules_t){ .allow_undefined = true, .allow_unknown = true });