#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/md5.h>
#include <freeradius-devel/rad_assert.h>
#include <ctype.h>
typedef struct xlat_t {
char module[MAX_STRING_LEN];
int length;
void *instance;
RAD_XLAT_FUNC do_xlat;
int internal;
} xlat_t;
static rbtree_t *xlat_root = NULL;
static const char * const internal_xlat[] = {"check",
"request",
"reply",
"proxy-request",
"proxy-reply",
"outer.request",
"outer.reply",
NULL};
#if REQUEST_MAX_REGEX > 8
#error Please fix the following line
#endif
static const int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
int type, RADIUS_ESCAPE_STRING func)
{
char buffer[MAX_STRING_LEN * 4];
if (pair != NULL) {
vp_prints_value(buffer, sizeof(buffer), pair, -1);
return func(out, outlen, buffer);
}
switch (type) {
case PW_TYPE_STRING :
strlcpy(out,"_",outlen);
break;
case PW_TYPE_INTEGER :
strlcpy(out,"0",outlen);
break;
case PW_TYPE_IPADDR :
strlcpy(out,"?.?.?.?",outlen);
break;
case PW_TYPE_IPV6ADDR :
strlcpy(out,":?:",outlen);
break;
case PW_TYPE_DATE :
strlcpy(out,"0",outlen);
break;
default :
strlcpy(out,"unknown_type",outlen);
}
return strlen(out);
}
static size_t xlat_packet(void *instance, REQUEST *request,
char *fmt, char *out, size_t outlen,
RADIUS_ESCAPE_STRING func)
{
DICT_ATTR *da;
VALUE_PAIR *vp;
VALUE_PAIR *vps = NULL;
RADIUS_PACKET *packet = NULL;
switch (*(int*) instance) {
case 0:
vps = request->config_items;
break;
case 1:
vps = request->packet->vps;
packet = request->packet;
break;
case 2:
vps = request->reply->vps;
packet = request->reply;
break;
case 3:
#ifdef WITH_PROXY
if (request->proxy) vps = request->proxy->vps;
packet = request->proxy;
#endif
break;
case 4:
#ifdef WITH_PROXY
if (request->proxy_reply) vps = request->proxy_reply->vps;
packet = request->proxy_reply;
#endif
break;
case 5:
if (request->parent) {
vps = request->parent->packet->vps;
packet = request->parent->packet;
}
break;
case 6:
if (request->parent && request->parent->reply) {
vps = request->parent->reply->vps;
packet = request->parent->reply;
}
break;
default:
return 0;
}
da = dict_attrbyname(fmt);
if (!da) {
int do_number = FALSE;
size_t count;
const char *p;
char buffer[256];
if (strlen(fmt) > sizeof(buffer)) return 0;
p = strchr(fmt, '[');
if (!p) {
p = strchr(fmt, '#');
if (!p) return 0;
do_number = TRUE;
}
strlcpy(buffer, fmt, p - fmt + 1);
da = dict_attrbyname(buffer);
if (!da) return 0;
if (do_number) {
vp = pairfind(vps, da->attr);
if (!vp) return 0;
switch (da->type) {
default:
break;
case PW_TYPE_INTEGER:
case PW_TYPE_DATE:
case PW_TYPE_SHORT:
case PW_TYPE_BYTE:
snprintf(out, outlen, "%u", vp->lvalue);
return strlen(out);
}
goto just_print;
}
if ((p[1] == '#') && (p[2] == ']')) {
count = 0;
for (vp = pairfind(vps, da->attr);
vp != NULL;
vp = pairfind(vp->next, da->attr)) {
count++;
}
snprintf(out, outlen, "%d", (int) count);
return strlen(out);
}
if ((p[1] == '*') && (p[2] == ']')) {
int total = 0;
for (vp = pairfind(vps, da->attr);
vp != NULL;
vp = pairfind(vp->next, da->attr)) {
count = valuepair2str(out, outlen - 1, vp, da->type, func);
rad_assert(count <= outlen);
total += count + 1;
outlen -= (count + 1);
out += count;
*(out++) = '\n';
if (outlen == 0) break;
}
return total;
}
count = atoi(p + 1);
p += 1 + strspn(p + 1, "0123456789");
if (*p != ']') {
RDEBUG2("xlat: Invalid array reference in string at %s %s",
fmt, p);
return 0;
}
for (vp = pairfind(vps, da->attr);
vp != NULL;
vp = pairfind(vp->next, da->attr)) {
if (count == 0) break;
count--;
}
if (!vp) return 0;
just_print:
return valuepair2str(out, outlen, vp, da->type, func);
}
vp = pairfind(vps, da->attr);
if (!vp) {
if (packet) {
VALUE_PAIR localvp;
memset(&localvp, 0, sizeof(localvp));
switch (da->attr) {
case PW_PACKET_TYPE:
{
DICT_VALUE *dval;
dval = dict_valbyattr(da->attr, packet->code);
if (dval) {
snprintf(out, outlen, "%s", dval->name);
} else {
snprintf(out, outlen, "%d", packet->code);
}
return strlen(out);
}
break;
case PW_CLIENT_SHORTNAME:
if (request->client && request->client->shortname) {
strlcpy(out, request->client->shortname, outlen);
} else {
strlcpy(out, "<UNKNOWN-CLIENT>", outlen);
}
return strlen(out);
case PW_CLIENT_IP_ADDRESS:
case PW_PACKET_SRC_IP_ADDRESS:
if (packet->src_ipaddr.af != AF_INET) {
return 0;
}
localvp.attribute = da->attr;
localvp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
break;
case PW_PACKET_DST_IP_ADDRESS:
if (packet->dst_ipaddr.af != AF_INET) {
return 0;
}
localvp.attribute = da->attr;
localvp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
break;
case PW_PACKET_SRC_PORT:
localvp.attribute = da->attr;
localvp.vp_integer = packet->src_port;
break;
case PW_PACKET_DST_PORT:
localvp.attribute = da->attr;
localvp.vp_integer = packet->dst_port;
break;
case PW_PACKET_AUTHENTICATION_VECTOR:
localvp.attribute = da->attr;
memcpy(localvp.vp_strvalue, packet->vector,
sizeof(packet->vector));
localvp.length = sizeof(packet->vector);
break;
case PW_REQUEST_PROCESSING_STAGE:
if (request->component) {
strlcpy(out, request->component, outlen);
} else {
strlcpy(out, "server_core", outlen);
}
return strlen(out);
case PW_PACKET_SRC_IPV6_ADDRESS:
if (packet->src_ipaddr.af != AF_INET6) {
return 0;
}
localvp.attribute = da->attr;
memcpy(localvp.vp_strvalue,
&packet->src_ipaddr.ipaddr.ip6addr,
sizeof(packet->src_ipaddr.ipaddr.ip6addr));
break;
case PW_PACKET_DST_IPV6_ADDRESS:
if (packet->dst_ipaddr.af != AF_INET6) {
return 0;
}
localvp.attribute = da->attr;
memcpy(localvp.vp_strvalue,
&packet->dst_ipaddr.ipaddr.ip6addr,
sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
break;
case PW_VIRTUAL_SERVER:
if (!request->server) return 0;
snprintf(out, outlen, "%s", request->server);
return strlen(out);
break;
case PW_MODULE_RETURN_CODE:
localvp.attribute = da->attr;
localvp.vp_integer = request->simul_max;
break;
default:
return 0;
break;
}
localvp.type = da->type;
return valuepair2str(out, outlen, &localvp,
da->type, func);
}
return 0;
}
if (!vps) return 0;
return valuepair2str(out, outlen, vp, da->type, func);
}
#ifdef HAVE_REGEX_H
static size_t xlat_regex(void *instance, REQUEST *request,
char *fmt, char *out, size_t outlen,
RADIUS_ESCAPE_STRING func)
{
char *regex;
fmt = fmt;
func = func;
regex = request_data_reference(request, request,
REQUEST_DATA_REGEX | *(int *)instance);
if (!regex) return 0;
strlcpy(out, regex, outlen);
return strlen(out);
}
#endif
static size_t xlat_debug(UNUSED void *instance, REQUEST *request,
char *fmt, char *out, size_t outlen,
UNUSED RADIUS_ESCAPE_STRING func)
{
int level = 0;
if (*fmt) level = atoi(fmt);
if (level == 0) {
request->options = RAD_REQUEST_OPTION_NONE;
request->radlog = NULL;
} else {
if (level > 4) level = 4;
request->options = level;
request->radlog = radlog_request;
}
snprintf(out, outlen, "%d", level);
return strlen(out);
}
static size_t xlat_md5(UNUSED void *instance, REQUEST *request,
char *fmt, char *out, size_t outlen,
UNUSED RADIUS_ESCAPE_STRING func)
{
int i;
uint8_t digest[16];
FR_MD5_CTX ctx;
char buffer[1024];
if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
*out = '\0';
return 0;
}
fr_MD5Init(&ctx);
fr_MD5Update(&ctx, (void *) buffer, strlen(buffer));
fr_MD5Final(digest, &ctx);
if (outlen < 33) {
snprintf(out, outlen, "md5_overflow");
return strlen(out);
}
for (i = 0; i < 16; i++) {
snprintf(out + i * 2, 3, "%02x", digest[i]);
}
return strlen(out);
}
static size_t xlat_lc(UNUSED void *instance, REQUEST *request,
char *fmt, char *out, size_t outlen,
UNUSED RADIUS_ESCAPE_STRING func)
{
char *p, *q;
char buffer[1024];
if (outlen <= 1) return 0;
if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
*out = '\0';
return 0;
}
for (p = buffer, q = out; *p != '\0'; p++, outlen--) {
if (outlen <= 1) break;
*(q++) = tolower((int) *p);
}
*q = '\0';
return strlen(out);
}
static size_t xlat_uc(UNUSED void *instance, REQUEST *request,
char *fmt, char *out, size_t outlen,
UNUSED RADIUS_ESCAPE_STRING func)
{
char *p, *q;
char buffer[1024];
if (outlen <= 1) return 0;
if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
*out = '\0';
return 0;
}
for (p = buffer, q = out; *p != '\0'; p++, outlen--) {
if (outlen <= 1) break;
*(q++) = toupper((int) *p);
}
*q = '\0';
return strlen(out);
}
static int xlat_cmp(const void *a, const void *b)
{
if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
}
return memcmp(((const xlat_t *)a)->module,
((const xlat_t *)b)->module,
((const xlat_t *)a)->length);
}
static xlat_t *xlat_find(const char *module)
{
xlat_t my_xlat;
if ((dict_attrbyname(module) != NULL) ||
(strchr(module, '[') != NULL) ||
(strchr(module, '#') != NULL)) {
module = "request";
}
strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
my_xlat.length = strlen(my_xlat.module);
return rbtree_finddata(xlat_root, &my_xlat);
}
int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
{
xlat_t *c;
xlat_t my_xlat;
if ((module == NULL) || (strlen(module) == 0)) {
DEBUG("xlat_register: Invalid module name");
return -1;
}
if (!xlat_root) {
int i;
#ifdef HAVE_REGEX_H
char buffer[2];
#endif
xlat_root = rbtree_create(xlat_cmp, free, 0);
if (!xlat_root) {
DEBUG("xlat_register: Failed to create tree.");
return -1;
}
for (i = 0; internal_xlat[i] != NULL; i++) {
xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
c = xlat_find(internal_xlat[i]);
rad_assert(c != NULL);
c->internal = TRUE;
}
xlat_register("control", xlat_packet, &xlat_inst[0]);
c = xlat_find("control");
rad_assert(c != NULL);
c->internal = TRUE;
#ifdef HAVE_REGEX_H
buffer[1] = '\0';
for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
buffer[0] = '0' + i;
xlat_register(buffer, xlat_regex, &xlat_inst[i]);
c = xlat_find(buffer);
rad_assert(c != NULL);
c->internal = TRUE;
}
#endif
xlat_register("debug", xlat_debug, &xlat_inst[0]);
c = xlat_find("debug");
rad_assert(c != NULL);
c->internal = TRUE;
xlat_register("md5", xlat_md5, &xlat_inst[0]);
c = xlat_find("md5");
rad_assert(c != NULL);
c->internal = TRUE;
xlat_register("tolower", xlat_lc, &xlat_inst[0]);
c = xlat_find("tolower");
rad_assert(c != NULL);
c->internal = TRUE;
xlat_register("toupper", xlat_uc, &xlat_inst[0]);
c = xlat_find("toupper");
rad_assert(c != NULL);
c->internal = TRUE;
}
strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
my_xlat.length = strlen(my_xlat.module);
c = rbtree_finddata(xlat_root, &my_xlat);
if (c) {
if (c->internal) {
DEBUG("xlat_register: Cannot re-define internal xlat");
return -1;
}
c->do_xlat = func;
c->instance = instance;
return 0;
}
c = rad_malloc(sizeof(*c));
memset(c, 0, sizeof(*c));
c->do_xlat = func;
strlcpy(c->module, module, sizeof(c->module));
c->length = strlen(c->module);
c->instance = instance;
rbtree_insert(xlat_root, c);
return 0;
}
void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
{
rbnode_t *node;
xlat_t my_xlat;
func = func;
if (!module) return;
strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
my_xlat.length = strlen(my_xlat.module);
node = rbtree_find(xlat_root, &my_xlat);
if (!node) return;
rbtree_delete(xlat_root, node);
}
void xlat_free(void)
{
rbtree_free(xlat_root);
}
static int decode_attribute(const char **from, char **to, int freespace,
REQUEST *request,
RADIUS_ESCAPE_STRING func)
{
int do_length = 0;
char *xlat_name, *xlat_string;
char *p, *q, *l, *next = NULL;
int retlen=0;
const xlat_t *c;
int varlen;
char buffer[8192];
q = *to;
*q = '\0';
varlen = rad_copy_variable(buffer, *from);
if (varlen < 0) {
RDEBUG2("Badly formatted variable: %s", *from);
return -1;
}
*from += varlen;
p = buffer;
p[varlen - 1] = '\0';
p += 2;
if (*p == '#') {
p++;
do_length = 1;
}
if ((p[0] == '%') && (p[1] == '{')) {
int len1, len2;
int expand2 = FALSE;
len1 = rad_copy_variable(buffer, p);
if (len1 < 0) {
RDEBUG2("Badly formatted variable: %s", p);
return -1;
}
if (!p[len1]) {
RDEBUG2("Improperly nested variable; %%{%s}", p);
return -1;
}
if ((p[len1] != ':') || (p[len1 + 1] != '-')) {
RDEBUG2("No trailing :- after variable at %s", p);
return -1;
}
p += len1 + 2;
l = buffer + len1 + 1;
if ((p[0] == '%') && (p[1] == '{')) {
len2 = rad_copy_variable(l, p);
if (len2 < 0) {
RDEBUG2("Invalid text after :- at %s", p);
return -1;
}
p += len2;
expand2 = TRUE;
} else if ((p[0] == '"') || p[0] == '\'') {
getstring(&p, l, strlen(l));
} else {
l = p;
}
retlen = radius_xlat(q, freespace, buffer, request, func);
if (retlen) {
q += retlen;
goto done;
}
RDEBUG2("\t... expanding second conditional");
if (expand2) {
retlen = radius_xlat(q, freespace, l,
request, func);
if (retlen) {
q += retlen;
}
} else {
strlcpy(q, l, freespace);
q += strlen(q);
}
goto done;
}
xlat_name = NULL;
for (l = p; *l != '\0'; l++) {
if (*l == '\\') {
l++;
continue;
}
if (*l == ':') {
xlat_name = p;
*l = '\0';
p = l + 1;
break;
}
if ((*l == ' ') || (*l == '\t')) break;
}
if (!xlat_name) {
xlat_name = xlat_string = p;
goto do_xlat;
}
if (*p == '-') {
RDEBUG2("WARNING: Deprecated conditional expansion \":-\". See \"man unlang\" for details");
p++;
xlat_string = xlat_name;
next = p;
goto do_xlat;
}
xlat_string = p;
do_xlat:
if ((c = xlat_find(xlat_name)) != NULL) {
if (!c->internal) RDEBUG3("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
c->module, xlat_string);
retlen = c->do_xlat(c->instance, request, xlat_string,
q, freespace, func);
if (retlen > 0) {
if (do_length) {
snprintf(q, freespace, "%d", retlen);
retlen = strlen(q);
}
} else if (next) {
RDEBUG2("\t... expanding second conditional");
retlen = radius_xlat(q, freespace, next, request, func);
}
q += retlen;
} else {
RDEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
return -1;
}
done:
*to = q;
return 0;
}
static size_t xlat_copy(char *out, size_t outlen, const char *in)
{
int freespace = outlen;
rad_assert(outlen > 0);
while ((*in) && (freespace > 1)) {
*(out++) = *(in++);
freespace--;
}
*out = '\0';
return (outlen - freespace);
}
int radius_xlat(char *out, int outlen, const char *fmt,
REQUEST *request, RADIUS_ESCAPE_STRING func)
{
int c, len, freespace;
const char *p;
char *q;
char *nl;
VALUE_PAIR *tmp;
struct tm *TM, s_TM;
char tmpdt[40];
int openbraces=0;
if (!fmt || !out || !request) return 0;
if (func == NULL) {
func = xlat_copy;
}
q = out;
p = fmt;
while (*p) {
freespace = outlen - (q - out);
if (freespace <= 1)
break;
c = *p;
if ((c != '%') && (c != '$') && (c != '\\')) {
if ((c == '}') && openbraces) {
openbraces--;
p++;
continue;
}
*q++ = *p++;
continue;
}
if (*++p == '\0') {
*q++ = c;
break;
}
if (c == '\\') {
switch(*p) {
case '\\':
*q++ = *p;
break;
case 't':
*q++ = '\t';
break;
case 'n':
*q++ = '\n';
break;
default:
*q++ = c;
*q++ = *p;
break;
}
p++;
} else if (c == '%') switch(*p) {
case '{':
p--;
if (decode_attribute(&p, &q, freespace, request, func) < 0) return 0;
break;
case '%':
*q++ = *p++;
break;
case 'a':
q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
p++;
break;
case 'c':
q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
p++;
break;
case 'd':
TM = localtime_r(&request->timestamp, &s_TM);
len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
if (len > 0) {
strlcpy(q, tmpdt, freespace);
q += strlen(q);
}
p++;
break;
case 'f':
q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
p++;
break;
case 'i':
q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
p++;
break;
case 'l':
snprintf(tmpdt, sizeof(tmpdt), "%lu",
(unsigned long) request->timestamp);
strlcpy(q,tmpdt,freespace);
q += strlen(q);
p++;
break;
case 'm':
TM = localtime_r(&request->timestamp, &s_TM);
len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
if (len > 0) {
strlcpy(q, tmpdt, freespace);
q += strlen(q);
}
p++;
break;
case 'n':
q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
p++;
break;
case 'p':
q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
p++;
break;
case 's':
q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
p++;
break;
case 't':
CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
nl = strchr(tmpdt, '\n');
if (nl) *nl = '\0';
strlcpy(q, tmpdt, freespace);
q += strlen(q);
p++;
break;
case 'u':
q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
p++;
break;
case 'A':
strlcpy(q,radacct_dir,freespace);
q += strlen(q);
p++;
break;
case 'C':
strlcpy(q,request->client->shortname,freespace);
q += strlen(q);
p++;
break;
case 'D':
TM = localtime_r(&request->timestamp, &s_TM);
len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
if (len > 0) {
strlcpy(q, tmpdt, freespace);
q += strlen(q);
}
p++;
break;
case 'H':
TM = localtime_r(&request->timestamp, &s_TM);
len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
if (len > 0) {
strlcpy(q, tmpdt, freespace);
q += strlen(q);
}
p++;
break;
case 'L':
strlcpy(q,radlog_dir,freespace);
q += strlen(q);
p++;
break;
case 'M':
q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
p++;
break;
case 'R':
strlcpy(q,radius_dir,freespace);
q += strlen(q);
p++;
break;
case 'S':
TM = localtime_r(&request->timestamp, &s_TM);
len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
if (len > 0) {
strlcpy(q, tmpdt, freespace);
q += strlen(q);
}
p++;
break;
case 'T':
TM = localtime_r(&request->timestamp, &s_TM);
len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
if (len > 0) {
strlcpy(q, tmpdt, freespace);
q += strlen(q);
}
p++;
break;
case 'U':
q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
p++;
break;
case 'V':
strlcpy(q,"Verified",freespace);
q += strlen(q);
p++;
break;
case 'Y':
TM = localtime_r(&request->timestamp, &s_TM);
len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
if (len > 0) {
strlcpy(q, tmpdt, freespace);
q += strlen(q);
}
p++;
break;
case 'Z':
tmp = request->packet->vps;
while (tmp && (freespace > 3)) {
if (tmp->attribute != PW_USER_PASSWORD) {
*q++ = '\t';
len = vp_prints(q, freespace - 2, tmp);
q += len;
freespace -= (len + 2);
*q++ = '\n';
}
tmp = tmp->next;
}
p++;
break;
default:
RDEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
if (freespace > 2) {
*q++ = '%';
*q++ = *p++;
}
break;
}
}
*q = '\0';
RDEBUG2("\texpand: %s -> %s", fmt, out);
return strlen(out);
}