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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * Copyright 2000 The FreeRADIUS server project
22 * Copyright 2000 Alan DeKok <aland@ox.org>
25 static const char rcsid[] =
28 #include <freeradius-devel/autoconf.h>
35 #include <freeradius-devel/radiusd.h>
37 #include <freeradius-devel/rad_assert.h>
39 typedef struct xlat_t {
40 char module[MAX_STRING_LEN];
43 RAD_XLAT_FUNC do_xlat;
44 int internal; /* not allowed to re-define these */
47 static rbtree_t *xlat_root = NULL;
50 * Define all xlat's in the structure.
52 static const char * const internal_xlat[] = {"check",
59 #if REQUEST_MAX_REGEX > 8
60 #error Please fix the following line
62 static const int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; /* up to 8 for regex */
66 * Convert the value on a VALUE_PAIR to string
68 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
69 int type, RADIUS_ESCAPE_STRING func)
71 char buffer[MAX_STRING_LEN * 4];
74 vp_prints_value(buffer, sizeof(buffer), pair, -1);
75 return func(out, outlen, buffer);
80 strNcpy(out,"_",outlen);
82 case PW_TYPE_INTEGER :
83 strNcpy(out,"0",outlen);
86 strNcpy(out,"?.?.?.?",outlen);
88 case PW_TYPE_IPV6ADDR :
89 strNcpy(out,":?:",outlen);
92 strNcpy(out,"0",outlen);
95 strNcpy(out,"unknown_type",outlen);
102 * Dynamically translate for check:, request:, reply:, etc.
104 static int xlat_packet(void *instance, REQUEST *request,
105 char *fmt, char *out, size_t outlen,
106 RADIUS_ESCAPE_STRING func)
110 VALUE_PAIR *vps = NULL;
111 RADIUS_PACKET *packet = NULL;
113 switch (*(int*) instance) {
115 vps = request->config_items;
119 vps = request->packet->vps;
120 packet = request->packet;
124 vps = request->reply->vps;
125 packet = request->reply;
129 if (request->proxy) vps = request->proxy->vps;
130 packet = request->proxy;
134 if (request->proxy_reply) vps = request->proxy_reply->vps;
135 packet = request->proxy_reply;
143 * The "format" string is the attribute name.
145 da = dict_attrbyname(fmt);
148 const char *p = strchr(fmt, '[');
152 if (strlen(fmt) > sizeof(buffer)) return 0;
154 strNcpy(buffer, fmt, p - fmt + 1);
156 da = dict_attrbyname(buffer);
160 * %{Attribute-Name[#]} returns the count of
161 * attributes of that name in the list.
163 if ((p[1] == '#') && (p[2] == ']')) {
166 for (vp = pairfind(vps, da->attr);
168 vp = pairfind(vp->next, da->attr)) {
171 snprintf(out, outlen, "%d", count);
176 * %{Attribute-Name[*]} returns ALL of the
177 * the attributes, separated by a newline.
179 if ((p[1] == '*') && (p[2] == ']')) {
182 for (vp = pairfind(vps, da->attr);
184 vp = pairfind(vp->next, da->attr)) {
185 count = valuepair2str(out, outlen - 1, vp, da->type, func);
186 rad_assert(count <= outlen);
188 outlen -= (count + 1);
193 if (outlen == 0) break;
204 p += 1 + strspn(p + 1, "0123456789");
206 DEBUG2("xlat: Invalid array reference in string at %s %s",
212 * Find the N'th value.
214 for (vp = pairfind(vps, da->attr);
216 vp = pairfind(vp->next, da->attr)) {
217 if (count == 0) break;
222 * Non-existent array reference.
226 return valuepair2str(out, outlen, vp, da->type, func);
229 vp = pairfind(vps, da->attr);
232 * Some "magic" handlers, which are never in VP's, but
233 * which are in the packet.
235 * FIXME: We should really do this in a more
241 localvp.vp_strvalue[0] = 0;
248 dval = dict_valbyattr(da->attr, packet->code);
250 snprintf(out, outlen, "%s", dval->name);
252 snprintf(out, outlen, "%d", packet->code);
258 case PW_CLIENT_IP_ADDRESS: /* the same as below */
259 case PW_PACKET_SRC_IP_ADDRESS:
260 if (packet->src_ipaddr.af != AF_INET) {
263 localvp.attribute = da->attr;
264 localvp.lvalue = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
267 case PW_PACKET_DST_IP_ADDRESS:
268 if (packet->dst_ipaddr.af != AF_INET) {
271 localvp.attribute = da->attr;
272 localvp.lvalue = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
275 case PW_PACKET_SRC_PORT:
276 localvp.attribute = da->attr;
277 localvp.lvalue = packet->src_port;
280 case PW_PACKET_DST_PORT:
281 localvp.attribute = da->attr;
282 localvp.lvalue = packet->dst_port;
285 case PW_PACKET_AUTHENTICATION_VECTOR:
286 localvp.attribute = da->attr;
287 memcpy(localvp.vp_strvalue, packet->vector,
288 sizeof(packet->vector));
289 localvp.length = sizeof(packet->vector);
293 * Authorization, accounting, etc.
295 case PW_REQUEST_PROCESSING_STAGE:
296 if (request->component) {
297 strNcpy(out, request->component, outlen);
299 strNcpy(out, "server_core", outlen);
303 case PW_PACKET_SRC_IPV6_ADDRESS:
304 if (packet->src_ipaddr.af != AF_INET6) {
307 localvp.attribute = da->attr;
308 memcpy(localvp.vp_strvalue,
309 &packet->src_ipaddr.ipaddr.ip4addr.s_addr,
310 sizeof(packet->src_ipaddr.ipaddr.ip4addr.s_addr));
313 case PW_PACKET_DST_IPV6_ADDRESS:
314 if (packet->dst_ipaddr.af != AF_INET6) {
317 localvp.attribute = da->attr;
318 memcpy(localvp.vp_strvalue,
319 &packet->dst_ipaddr.ipaddr.ip4addr.s_addr,
320 sizeof(packet->dst_ipaddr.ipaddr.ip4addr.s_addr));
323 case PW_SERVER_IDENTITY:
324 if (!request->listener->identity) return 0;
326 snprintf(out, outlen, "%s", request->listener->identity);
331 return 0; /* not found */
335 localvp.type = da->type;
336 return valuepair2str(out, outlen, &localvp,
346 if (!vps) return 0; /* silently fail */
349 * Convert the VP to a string, and return it.
351 return valuepair2str(out, outlen, vp, da->type, func);
356 * Pull %{0} to %{8} out of the packet.
358 static int xlat_regex(void *instance, REQUEST *request,
359 char *fmt, char *out, size_t outlen,
360 RADIUS_ESCAPE_STRING func)
365 * We cheat: fmt is "0" to "8", but those numbers
366 * are already in the "instance".
368 fmt = fmt; /* -Wunused */
369 func = func; /* -Wunused FIXME: do escaping? */
371 regex = request_data_get(request, request,
372 REQUEST_DATA_REGEX | *(int *)instance);
373 if (!regex) return 0;
376 * Copy UP TO "freespace" bytes, including
379 strNcpy(out, regex, outlen);
380 free(regex); /* was strdup'd */
383 #endif /* HAVE_REGEX_H */
386 * Compare two xlat_t structs, based ONLY on the module name.
388 static int xlat_cmp(const void *a, const void *b)
390 if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
391 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
394 return memcmp(((const xlat_t *)a)->module,
395 ((const xlat_t *)b)->module,
396 ((const xlat_t *)a)->length);
401 * find the appropriate registered xlat function.
403 static const xlat_t *xlat_find(const char *module)
408 * Look for dictionary attributes first.
410 if ((dict_attrbyname(module) != NULL) ||
411 (strchr(module, '[') != NULL)) {
412 static const xlat_t dict_xlat = {
423 strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
424 my_xlat.length = strlen(my_xlat.module);
426 return rbtree_finddata(xlat_root, &my_xlat);
431 * Register an xlat function.
433 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
438 if ((module == NULL) || (strlen(module) == 0)) {
439 DEBUG("xlat_register: Invalid module name");
444 * First time around, build up the tree...
446 * FIXME: This code should be hoisted out of this function,
447 * and into a global "initialization". But it isn't critical...
455 xlat_root = rbtree_create(xlat_cmp, free, 0);
457 DEBUG("xlat_register: Failed to create tree.");
462 * Register the internal packet xlat's.
464 for (i = 0; internal_xlat[i] != NULL; i++) {
465 xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
466 c = xlat_find(internal_xlat[i]);
467 rad_assert(c != NULL);
473 * Register xlat's for regexes.
476 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
478 xlat_register(buffer, xlat_regex, &xlat_inst[i]);
479 c = xlat_find(buffer);
480 rad_assert(c != NULL);
483 #endif /* HAVE_REGEX_H */
487 * If it already exists, replace the instance.
489 strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
490 my_xlat.length = strlen(my_xlat.module);
491 c = rbtree_finddata(xlat_root, &my_xlat);
494 DEBUG("xlat_register: Cannot re-define internal xlat");
499 c->instance = instance;
504 * Doesn't exist. Create it.
506 c = rad_malloc(sizeof(xlat_t));
507 memset(c, 0, sizeof(*c));
510 strNcpy(c->module, module, sizeof(c->module));
511 c->length = strlen(c->module);
512 c->instance = instance;
514 rbtree_insert(xlat_root, c);
520 * Unregister an xlat function.
522 * We can only have one function to call per name, so the
523 * passing of "func" here is extraneous.
525 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
530 func = func; /* -Wunused */
534 strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
535 my_xlat.length = strlen(my_xlat.module);
537 node = rbtree_find(xlat_root, &my_xlat);
540 rbtree_delete(xlat_root, node);
544 * De-register all xlat functions,
545 * used mainly for debugging.
549 rbtree_free(xlat_root);
554 * Decode an attribute name into a string.
556 static void decode_attribute(const char **from, char **to, int freespace,
557 int *open, REQUEST *request,
558 RADIUS_ESCAPE_STRING func)
562 char *xlat_string = NULL; /* can be large */
563 int free_xlat_string = FALSE;
566 int found=0, retlen=0;
567 int openbraces = *open;
577 * Skip the '{' at the front of 'p'
578 * Increment open braces
589 * First, copy the xlat key name to one buffer
591 while (*p && (*p != '}') && (*p != ':')) {
594 if (pa >= (xlat_name + sizeof(xlat_name) - 1)) {
596 * Skip to the end of the input
599 DEBUG("xlat: Module name is too long in string %%%s",
607 DEBUG("xlat: Invalid syntax in %s", *from);
610 * %{name} is a simple attribute reference,
611 * or regex reference.
613 } else if (*p == '}') {
615 rad_assert(openbraces == *open);
618 xlat_string = xlat_name;
621 } else if (p[1] == '-') { /* handle ':- */
623 xlat_string = xlat_name;
626 } else { /* module name, followed by per-module string */
628 int delimitbrace = *open;
630 rad_assert(*p == ':');
631 p++; /* skip the ':' */
634 * If there's a brace immediately following the colon,
635 * then we've chosen to delimite the per-module string,
636 * so keep track of that.
639 delimitbrace = openbraces;
644 xlat_string = rad_malloc(strlen(p) + 1); /* always returns */
645 free_xlat_string = TRUE;
649 * Copy over the rest of the string, which is per-module
652 while (*p && !stop) {
655 * What the heck is this supposed
664 * This is pretty hokey... we
665 * should use the functions in
675 if (openbraces == delimitbrace) {
692 * Now check to see if we're at the end of the string
693 * we were sent. If we're not, check for :-
695 if (openbraces == delimitbrace) {
696 if (p[0] == ':' && p[1] == '-') {
702 * Look up almost everything in the new tree of xlat
703 * functions. This makes it a little quicker...
706 if ((c = xlat_find(xlat_name)) != NULL) {
707 if (!c->internal) DEBUG("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
708 c->module, xlat_string);
709 retlen = c->do_xlat(c->instance, request, xlat_string,
711 /* If retlen is 0, treat it as not found */
712 if (retlen > 0) found = 1;
716 * No attribute by that name, return an error.
718 DEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
724 * Skip to last '}' if attr is found
725 * The rest of the stuff within the braces is
726 * useless if we found what we need
730 snprintf(q, freespace, "%d", retlen);
736 while((*p != '\0') && (openbraces > 0)) {
738 * Handle escapes outside of the loop.
743 p++; /* get & ignore next character */
762 p++; /* skip the character */
767 if (free_xlat_string) free(xlat_string);
775 * If the caller doesn't pass xlat an escape function, then
776 * we use this one. It simplifies the coding, as the check for
777 * func == NULL only happens once.
779 static int xlat_copy(char *out, int outlen, const char *in)
781 int freespace = outlen;
783 rad_assert(outlen > 0);
785 while ((*in) && (freespace > 1)) {
789 * FIXME: Do escaping of bad stuff!
797 return (outlen - freespace); /* count does not include NUL */
801 * Replace %<whatever> in a string.
803 * See 'doc/variables.txt' for more information.
805 int radius_xlat(char *out, int outlen, const char *fmt,
806 REQUEST *request, RADIUS_ESCAPE_STRING func)
808 int c, len, freespace;
814 char tmpdt[40]; /* For temporary storing of dates */
820 if (!fmt || !out || !request) return 0;
823 * Ensure that we always have an escaping function.
832 /* Calculate freespace in output */
833 freespace = outlen - (q - out);
838 if ((c != '%') && (c != '$') && (c != '\\')) {
840 * We check if we're inside an open brace. If we are
841 * then we assume this brace is NOT literal, but is
842 * a closing brace and apply it
844 if ((c == '}') && openbraces) {
854 * There's nothing after this character, copy
855 * the last '%' or "$' or '\\' over to the output
882 * Hmmm... ${User-Name} is a synonym for
887 } else if (c == '$') switch(*p) {
888 case '{': /* Attribute by Name */
889 decode_attribute(&p, &q, freespace, &openbraces, request, func);
896 } else if (c == '%') switch(*p) {
898 decode_attribute(&p, &q, freespace, &openbraces, request, func);
904 case 'a': /* Protocol: */
905 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
908 case 'c': /* Callback-Number */
909 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
912 case 'd': /* request day */
913 TM = localtime_r(&request->timestamp, &s_TM);
914 len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
916 strNcpy(q, tmpdt, freespace);
921 case 'f': /* Framed IP address */
922 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
925 case 'i': /* Calling station ID */
926 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
929 case 'l': /* request timestamp */
930 snprintf(tmpdt, sizeof(tmpdt), "%lu",
931 (unsigned long) request->timestamp);
932 strNcpy(q,tmpdt,freespace);
936 case 'm': /* request month */
937 TM = localtime_r(&request->timestamp, &s_TM);
938 len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
940 strNcpy(q, tmpdt, freespace);
945 case 'n': /* NAS IP address */
946 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
949 case 'p': /* Port number */
950 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
953 case 's': /* Speed */
954 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
957 case 't': /* request timestamp */
958 CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
959 nl = strchr(tmpdt, '\n');
961 strNcpy(q, tmpdt, freespace);
965 case 'u': /* User name */
966 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
969 case 'A': /* radacct_dir */
970 strNcpy(q,radacct_dir,freespace-1);
974 case 'C': /* ClientName */
975 strNcpy(q,client_name_old(&request->packet->src_ipaddr),freespace-1);
979 case 'D': /* request date */
980 TM = localtime_r(&request->timestamp, &s_TM);
981 len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
983 strNcpy(q, tmpdt, freespace);
988 case 'H': /* request hour */
989 TM = localtime_r(&request->timestamp, &s_TM);
990 len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
992 strNcpy(q, tmpdt, freespace);
997 case 'L': /* radlog_dir */
998 strNcpy(q,radlog_dir,freespace-1);
1003 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
1006 case 'R': /* radius_dir */
1007 strNcpy(q,radius_dir,freespace-1);
1011 case 'S': /* request timestamp in SQL format*/
1012 TM = localtime_r(&request->timestamp, &s_TM);
1013 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
1015 strNcpy(q, tmpdt, freespace);
1020 case 'T': /* request timestamp */
1021 TM = localtime_r(&request->timestamp, &s_TM);
1022 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
1024 strNcpy(q, tmpdt, freespace);
1029 case 'U': /* Stripped User name */
1030 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
1033 case 'V': /* Request-Authenticator */
1034 if (request->packet->verified)
1035 strNcpy(q,"Verified",freespace-1);
1037 strNcpy(q,"None",freespace-1);
1041 case 'Y': /* request year */
1042 TM = localtime_r(&request->timestamp, &s_TM);
1043 len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
1045 strNcpy(q, tmpdt, freespace);
1050 case 'Z': /* Full request pairs except password */
1051 tmp = request->packet->vps;
1052 while (tmp && (freespace > 3)) {
1053 if (tmp->attribute != PW_PASSWORD) {
1055 len = vp_prints(q, freespace - 2, tmp);
1057 freespace -= (len + 2);
1065 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
1066 if (freespace > 2) {
1075 DEBUG2("radius_xlat: '%s'", out);