2 * xlat.c Translate strings. This is the first version of xlat
3 * incorporated to RADIUS
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * Copyright 2000,2006 The FreeRADIUS server project
22 * Copyright 2000 Alan DeKok <aland@ox.org>
25 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/rad_assert.h>
33 typedef struct xlat_t {
34 char module[MAX_STRING_LEN];
37 RAD_XLAT_FUNC do_xlat;
38 int internal; /* not allowed to re-define these */
41 static rbtree_t *xlat_root = NULL;
44 * Define all xlat's in the structure.
46 static const char * const internal_xlat[] = {"check",
55 #if REQUEST_MAX_REGEX > 8
56 #error Please fix the following line
58 static const int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; /* up to 8 for regex */
62 * Convert the value on a VALUE_PAIR to string
64 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
65 int type, RADIUS_ESCAPE_STRING func)
67 char buffer[MAX_STRING_LEN * 4];
70 vp_prints_value(buffer, sizeof(buffer), pair, -1);
71 return func(out, outlen, buffer);
76 strlcpy(out,"_",outlen);
78 case PW_TYPE_INTEGER :
79 strlcpy(out,"0",outlen);
82 strlcpy(out,"?.?.?.?",outlen);
84 case PW_TYPE_IPV6ADDR :
85 strlcpy(out,":?:",outlen);
88 strlcpy(out,"0",outlen);
91 strlcpy(out,"unknown_type",outlen);
98 * Dynamically translate for check:, request:, reply:, etc.
100 static size_t xlat_packet(void *instance, REQUEST *request,
101 char *fmt, char *out, size_t outlen,
102 RADIUS_ESCAPE_STRING func)
106 VALUE_PAIR *vps = NULL;
107 RADIUS_PACKET *packet = NULL;
109 switch (*(int*) instance) {
111 vps = request->config_items;
115 vps = request->packet->vps;
116 packet = request->packet;
120 vps = request->reply->vps;
121 packet = request->reply;
126 if (request->proxy) vps = request->proxy->vps;
127 packet = request->proxy;
133 if (request->proxy_reply) vps = request->proxy_reply->vps;
134 packet = request->proxy_reply;
139 if (request->parent) {
140 vps = request->parent->packet->vps;
141 packet = request->parent->packet;
146 if (request->parent && request->parent->reply) {
147 vps = request->parent->reply->vps;
148 packet = request->parent->reply;
157 * The "format" string is the attribute name.
159 da = dict_attrbyname(fmt);
162 const char *p = strchr(fmt, '[');
166 if (strlen(fmt) > sizeof(buffer)) return 0;
168 strlcpy(buffer, fmt, p - fmt + 1);
170 da = dict_attrbyname(buffer);
174 * %{Attribute-Name[#]} returns the count of
175 * attributes of that name in the list.
177 if ((p[1] == '#') && (p[2] == ']')) {
180 for (vp = pairfind(vps, da->attr);
182 vp = pairfind(vp->next, da->attr)) {
185 snprintf(out, outlen, "%d", count);
190 * %{Attribute-Name[*]} returns ALL of the
191 * the attributes, separated by a newline.
193 if ((p[1] == '*') && (p[2] == ']')) {
196 for (vp = pairfind(vps, da->attr);
198 vp = pairfind(vp->next, da->attr)) {
199 count = valuepair2str(out, outlen - 1, vp, da->type, func);
200 rad_assert(count <= outlen);
202 outlen -= (count + 1);
207 if (outlen == 0) break;
218 p += 1 + strspn(p + 1, "0123456789");
220 RDEBUG2("xlat: Invalid array reference in string at %s %s",
226 * Find the N'th value.
228 for (vp = pairfind(vps, da->attr);
230 vp = pairfind(vp->next, da->attr)) {
231 if (count == 0) break;
236 * Non-existent array reference.
240 return valuepair2str(out, outlen, vp, da->type, func);
243 vp = pairfind(vps, da->attr);
246 * Some "magic" handlers, which are never in VP's, but
247 * which are in the packet.
249 * FIXME: We should really do this in a more
255 memset(&localvp, 0, sizeof(localvp));
262 dval = dict_valbyattr(da->attr, packet->code);
264 snprintf(out, outlen, "%s", dval->name);
266 snprintf(out, outlen, "%d", packet->code);
272 case PW_CLIENT_SHORTNAME:
273 if (request->client && request->client->shortname) {
274 strlcpy(out, request->client->shortname, outlen);
276 strlcpy(out, "<UNKNOWN-CLIENT>", outlen);
280 case PW_CLIENT_IP_ADDRESS: /* the same as below */
281 case PW_PACKET_SRC_IP_ADDRESS:
282 if (packet->src_ipaddr.af != AF_INET) {
285 localvp.attribute = da->attr;
286 localvp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
289 case PW_PACKET_DST_IP_ADDRESS:
290 if (packet->dst_ipaddr.af != AF_INET) {
293 localvp.attribute = da->attr;
294 localvp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
297 case PW_PACKET_SRC_PORT:
298 localvp.attribute = da->attr;
299 localvp.vp_integer = packet->src_port;
302 case PW_PACKET_DST_PORT:
303 localvp.attribute = da->attr;
304 localvp.vp_integer = packet->dst_port;
307 case PW_PACKET_AUTHENTICATION_VECTOR:
308 localvp.attribute = da->attr;
309 memcpy(localvp.vp_strvalue, packet->vector,
310 sizeof(packet->vector));
311 localvp.length = sizeof(packet->vector);
315 * Authorization, accounting, etc.
317 case PW_REQUEST_PROCESSING_STAGE:
318 if (request->component) {
319 strlcpy(out, request->component, outlen);
321 strlcpy(out, "server_core", outlen);
325 case PW_PACKET_SRC_IPV6_ADDRESS:
326 if (packet->src_ipaddr.af != AF_INET6) {
329 localvp.attribute = da->attr;
330 memcpy(localvp.vp_strvalue,
331 &packet->src_ipaddr.ipaddr.ip6addr,
332 sizeof(packet->src_ipaddr.ipaddr.ip6addr));
335 case PW_PACKET_DST_IPV6_ADDRESS:
336 if (packet->dst_ipaddr.af != AF_INET6) {
339 localvp.attribute = da->attr;
340 memcpy(localvp.vp_strvalue,
341 &packet->dst_ipaddr.ipaddr.ip6addr,
342 sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
345 case PW_VIRTUAL_SERVER:
346 if (!request->server) return 0;
348 snprintf(out, outlen, "%s", request->server);
352 case PW_MODULE_RETURN_CODE:
353 localvp.attribute = da->attr;
356 * See modcall.c for a bit of a hack.
358 localvp.vp_integer = request->simul_max;
362 return 0; /* not found */
366 localvp.type = da->type;
367 return valuepair2str(out, outlen, &localvp,
377 if (!vps) return 0; /* silently fail */
380 * Convert the VP to a string, and return it.
382 return valuepair2str(out, outlen, vp, da->type, func);
387 * Pull %{0} to %{8} out of the packet.
389 static size_t xlat_regex(void *instance, REQUEST *request,
390 char *fmt, char *out, size_t outlen,
391 RADIUS_ESCAPE_STRING func)
396 * We cheat: fmt is "0" to "8", but those numbers
397 * are already in the "instance".
399 fmt = fmt; /* -Wunused */
400 func = func; /* -Wunused FIXME: do escaping? */
402 regex = request_data_reference(request, request,
403 REQUEST_DATA_REGEX | *(int *)instance);
404 if (!regex) return 0;
407 * Copy UP TO "freespace" bytes, including
410 strlcpy(out, regex, outlen);
413 #endif /* HAVE_REGEX_H */
417 * Change the debugging level.
419 static size_t xlat_debug(UNUSED void *instance, REQUEST *request,
420 char *fmt, char *out, size_t outlen,
421 UNUSED RADIUS_ESCAPE_STRING func)
425 if (*fmt) level = atoi(fmt);
428 request->options = RAD_REQUEST_OPTION_NONE;
429 request->radlog = NULL;
431 if (level > 4) level = 4;
433 request->options = level;
434 request->radlog = radlog_request;
437 snprintf(out, outlen, "%d", level);
443 * Compare two xlat_t structs, based ONLY on the module name.
445 static int xlat_cmp(const void *a, const void *b)
447 if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
448 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
451 return memcmp(((const xlat_t *)a)->module,
452 ((const xlat_t *)b)->module,
453 ((const xlat_t *)a)->length);
458 * find the appropriate registered xlat function.
460 static xlat_t *xlat_find(const char *module)
465 * Look for dictionary attributes first.
467 if ((dict_attrbyname(module) != NULL) ||
468 (strchr(module, '[') != NULL)) {
472 strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
473 my_xlat.length = strlen(my_xlat.module);
475 return rbtree_finddata(xlat_root, &my_xlat);
480 * Register an xlat function.
482 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
487 if ((module == NULL) || (strlen(module) == 0)) {
488 DEBUG("xlat_register: Invalid module name");
493 * First time around, build up the tree...
495 * FIXME: This code should be hoisted out of this function,
496 * and into a global "initialization". But it isn't critical...
504 xlat_root = rbtree_create(xlat_cmp, free, 0);
506 DEBUG("xlat_register: Failed to create tree.");
511 * Register the internal packet xlat's.
513 for (i = 0; internal_xlat[i] != NULL; i++) {
514 xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
515 c = xlat_find(internal_xlat[i]);
516 rad_assert(c != NULL);
521 * New name: "control"
523 xlat_register("control", xlat_packet, &xlat_inst[0]);
524 c = xlat_find("control");
525 rad_assert(c != NULL);
530 * Register xlat's for regexes.
533 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
535 xlat_register(buffer, xlat_regex, &xlat_inst[i]);
536 c = xlat_find(buffer);
537 rad_assert(c != NULL);
540 #endif /* HAVE_REGEX_H */
543 xlat_register("debug", xlat_debug, &xlat_inst[0]);
544 c = xlat_find("debug");
545 rad_assert(c != NULL);
550 * If it already exists, replace the instance.
552 strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
553 my_xlat.length = strlen(my_xlat.module);
554 c = rbtree_finddata(xlat_root, &my_xlat);
557 DEBUG("xlat_register: Cannot re-define internal xlat");
562 c->instance = instance;
567 * Doesn't exist. Create it.
569 c = rad_malloc(sizeof(*c));
570 memset(c, 0, sizeof(*c));
573 strlcpy(c->module, module, sizeof(c->module));
574 c->length = strlen(c->module);
575 c->instance = instance;
577 rbtree_insert(xlat_root, c);
583 * Unregister an xlat function.
585 * We can only have one function to call per name, so the
586 * passing of "func" here is extraneous.
588 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
593 func = func; /* -Wunused */
597 strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
598 my_xlat.length = strlen(my_xlat.module);
600 node = rbtree_find(xlat_root, &my_xlat);
603 rbtree_delete(xlat_root, node);
607 * De-register all xlat functions,
608 * used mainly for debugging.
612 rbtree_free(xlat_root);
617 * Decode an attribute name into a string.
619 static void decode_attribute(const char **from, char **to, int freespace,
620 int *open_p, REQUEST *request,
621 RADIUS_ESCAPE_STRING func)
625 char *xlat_string = NULL; /* can be large */
626 int free_xlat_string = FALSE;
629 int found=0, retlen=0;
630 int openbraces = *open_p;
641 * Skip the '{' at the front of 'p'
642 * Increment open braces
653 * Handle %{%{foo}:-%{bar}}, which is useful, too.
655 * Did I mention that this parser is garbage?
657 if ((p[0] == '%') && (p[1] == '{')) {
659 * This is really bad, but it works.
662 size_t mylen = strlen(p);
663 char *first = rad_malloc(mylen);
664 char *second = rad_malloc(mylen);
667 len1 = rad_copy_variable(first, p);
669 RDEBUG2("Badly formatted variable: %s", p);
673 if ((p[len1] != ':') || (p[len1 + 1] != '-')) {
674 RDEBUG2("No trailing :- after variable at %s", p);
680 if ((p[0] == '%') && (p[1] == '{')) {
681 len2 = rad_copy_variable(second, p);
685 RDEBUG2("Invalid text after :- at %s", p);
690 } else if ((p[0] == '"') || p[0] == '\'') {
691 getstring(&p, second, mylen);
696 while (*p && (*p != '}')) {
703 RDEBUG2("Failed to find trailing '}' in string");
707 mylen = radius_xlat(q, freespace, first, request, func);
714 strlcpy(q, second, freespace);
717 mylen = radius_xlat(q, freespace, second,
726 * Else the output is an empty string.
736 * First, copy the xlat key name to one buffer
738 while (*p && (*p != '}') && (*p != ':')) {
741 if (pa >= (xlat_name + sizeof(xlat_name) - 1)) {
743 * Skip to the end of the input
746 RDEBUG("xlat: Module name is too long in string %%%s",
754 RDEBUG("xlat: Invalid syntax in %s", *from);
757 * %{name} is a simple attribute reference,
758 * or regex reference.
760 } else if (*p == '}') {
762 rad_assert(openbraces == *open_p);
765 xlat_string = xlat_name;
768 } else if ((p[0] == ':') && (p[1] == '-')) { /* handle ':- */
769 RDEBUG2("WARNING: Deprecated conditional expansion \":-\". See \"man unlang\" for details");
771 xlat_string = xlat_name;
774 } else { /* module name, followed by per-module string */
776 int delimitbrace = *open_p;
778 rad_assert(*p == ':');
779 p++; /* skip the ':' */
782 * If there's a brace immediately following the colon,
783 * then we've chosen to delimite the per-module string,
784 * so keep track of that.
787 delimitbrace = openbraces;
792 xlat_string = rad_malloc(strlen(p) + 1); /* always returns */
793 free_xlat_string = TRUE;
797 * Copy over the rest of the string, which is per-module
800 while (*p && !stop) {
804 * What the heck is this supposed
813 if (!spaces && p[1] == '-') {
822 * This is pretty hokey... we
823 * should use the functions in
833 if (openbraces == delimitbrace) {
855 * Now check to see if we're at the end of the string
856 * we were sent. If we're not, check for :-
858 if (openbraces == delimitbrace) {
859 if (p[0] == ':' && p[1] == '-') {
865 * Look up almost everything in the new tree of xlat
866 * functions. This makes it a little quicker...
869 if ((c = xlat_find(xlat_name)) != NULL) {
870 if (!c->internal) RDEBUG3("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
871 c->module, xlat_string);
872 retlen = c->do_xlat(c->instance, request, xlat_string,
874 /* If retlen is 0, treat it as not found */
875 if (retlen > 0) found = 1;
880 * No attribute by that name, return an error.
882 RDEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
888 * Skip to last '}' if attr is found
889 * The rest of the stuff within the braces is
890 * useless if we found what we need
894 snprintf(q, freespace, "%d", retlen);
900 while((*p != '\0') && (openbraces > *open_p)) {
902 * Handle escapes outside of the loop.
907 p++; /* get & ignore next character */
926 p++; /* skip the character */
931 if (free_xlat_string) free(xlat_string);
933 *open_p = openbraces;
939 * If the caller doesn't pass xlat an escape function, then
940 * we use this one. It simplifies the coding, as the check for
941 * func == NULL only happens once.
943 static size_t xlat_copy(char *out, size_t outlen, const char *in)
945 int freespace = outlen;
947 rad_assert(outlen > 0);
949 while ((*in) && (freespace > 1)) {
953 * FIXME: Do escaping of bad stuff!
961 return (outlen - freespace); /* count does not include NUL */
965 * Replace %<whatever> in a string.
967 * See 'doc/variables.txt' for more information.
969 int radius_xlat(char *out, int outlen, const char *fmt,
970 REQUEST *request, RADIUS_ESCAPE_STRING func)
972 int c, len, freespace;
978 char tmpdt[40]; /* For temporary storing of dates */
984 if (!fmt || !out || !request) return 0;
987 * Ensure that we always have an escaping function.
996 /* Calculate freespace in output */
997 freespace = outlen - (q - out);
1002 if ((c != '%') && (c != '$') && (c != '\\')) {
1004 * We check if we're inside an open brace. If we are
1005 * then we assume this brace is NOT literal, but is
1006 * a closing brace and apply it
1008 if ((c == '}') && openbraces) {
1018 * There's nothing after this character, copy
1019 * the last '%' or "$' or '\\' over to the output
1045 } else if (c == '%') switch(*p) {
1047 decode_attribute(&p, &q, freespace, &openbraces, request, func);
1053 case 'a': /* Protocol: */
1054 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
1057 case 'c': /* Callback-Number */
1058 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
1061 case 'd': /* request day */
1062 TM = localtime_r(&request->timestamp, &s_TM);
1063 len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
1065 strlcpy(q, tmpdt, freespace);
1070 case 'f': /* Framed IP address */
1071 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
1074 case 'i': /* Calling station ID */
1075 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
1078 case 'l': /* request timestamp */
1079 snprintf(tmpdt, sizeof(tmpdt), "%lu",
1080 (unsigned long) request->received.tv_sec);
1081 strlcpy(q,tmpdt,freespace);
1085 case 'm': /* request month */
1086 TM = localtime_r(&request->timestamp, &s_TM);
1087 len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
1089 strlcpy(q, tmpdt, freespace);
1094 case 'n': /* NAS IP address */
1095 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
1098 case 'p': /* Port number */
1099 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
1102 case 's': /* Speed */
1103 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
1106 case 't': /* request timestamp */
1107 CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
1108 nl = strchr(tmpdt, '\n');
1110 strlcpy(q, tmpdt, freespace);
1114 case 'u': /* User name */
1115 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
1118 case 'A': /* radacct_dir */
1119 strlcpy(q,radacct_dir,freespace);
1123 case 'C': /* ClientName */
1124 strlcpy(q,request->client->shortname,freespace);
1128 case 'D': /* request date */
1129 TM = localtime_r(&request->timestamp, &s_TM);
1130 len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
1132 strlcpy(q, tmpdt, freespace);
1137 case 'H': /* request hour */
1138 TM = localtime_r(&request->timestamp, &s_TM);
1139 len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
1141 strlcpy(q, tmpdt, freespace);
1146 case 'L': /* radlog_dir */
1147 strlcpy(q,radlog_dir,freespace);
1152 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
1155 case 'R': /* radius_dir */
1156 strlcpy(q,radius_dir,freespace);
1160 case 'S': /* request timestamp in SQL format*/
1161 TM = localtime_r(&request->timestamp, &s_TM);
1162 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
1164 strlcpy(q, tmpdt, freespace);
1169 case 'T': /* request timestamp */
1170 TM = localtime_r(&request->timestamp, &s_TM);
1171 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
1173 strlcpy(q, tmpdt, freespace);
1178 case 'U': /* Stripped User name */
1179 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
1182 case 'V': /* Request-Authenticator */
1183 strlcpy(q,"Verified",freespace);
1187 case 'Y': /* request year */
1188 TM = localtime_r(&request->timestamp, &s_TM);
1189 len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
1191 strlcpy(q, tmpdt, freespace);
1196 case 'Z': /* Full request pairs except password */
1197 tmp = request->packet->vps;
1198 while (tmp && (freespace > 3)) {
1199 if (tmp->attribute != PW_USER_PASSWORD) {
1201 len = vp_prints(q, freespace - 2, tmp);
1203 freespace -= (len + 2);
1211 RDEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
1212 if (freespace > 2) {
1221 RDEBUG2("\texpand: %s -> %s", fmt, out);