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[] =
29 #include "libradius.h"
38 #include "rad_assert.h"
40 typedef struct xlat_t {
41 char module[MAX_STRING_LEN];
44 RAD_XLAT_FUNC do_xlat;
45 int internal; /* not allowed to re-define these */
48 static rbtree_t *xlat_root = NULL;
51 * Define all xlat's in the structure.
53 static const char *internal_xlat[] = {"check",
60 #if REQUEST_MAX_REGEX > 8
61 #error Please fix the following line
63 static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; /* up to 8 for regex */
67 * Convert the value on a VALUE_PAIR to string
69 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
70 int type, RADIUS_ESCAPE_STRING func)
72 char buffer[MAX_STRING_LEN * 4];
75 vp_prints_value(buffer, sizeof(buffer), pair, -1);
76 return func(out, outlen, buffer);
81 strNcpy(out,"_",outlen);
83 case PW_TYPE_INTEGER :
84 strNcpy(out,"0",outlen);
87 strNcpy(out,"?.?.?.?",outlen);
90 strNcpy(out,"0",outlen);
93 strNcpy(out,"unknown_type",outlen);
100 * Dynamically translate for check:, request:, reply:, etc.
102 static int xlat_packet(void *instance, REQUEST *request,
103 char *fmt, char *out, size_t outlen,
104 RADIUS_ESCAPE_STRING func)
108 VALUE_PAIR *vps = NULL;
109 RADIUS_PACKET *packet = NULL;
111 switch (*(int*) instance) {
113 vps = request->config_items;
117 vps = request->packet->vps;
118 packet = request->packet;
122 vps = request->reply->vps;
123 packet = request->reply;
127 if (request->proxy) vps = request->proxy->vps;
128 packet = request->proxy;
132 if (request->proxy_reply) vps = request->proxy_reply->vps;
133 packet = request->proxy_reply;
141 * The "format" string is the attribute name.
143 da = dict_attrbyname(fmt);
146 const char *p = strchr(fmt, '[');
150 if (strlen(fmt) > sizeof(buffer)) return 0;
152 strNcpy(buffer, fmt, p - fmt + 1);
154 da = dict_attrbyname(buffer);
158 * %{Attribute-Name[#]} returns the count of
159 * attributes of that name in the list.
161 if ((p[1] == '#') && (p[2] == ']')) {
164 for (vp = pairfind(vps, da->attr);
166 vp = pairfind(vp->next, da->attr)) {
169 snprintf(out, outlen, "%d", index);
174 * %{Attribute-Name[*]} returns ALL of the
175 * the attributes, separated by a newline.
177 if ((p[1] == '*') && (p[2] == ']')) {
180 for (vp = pairfind(vps, da->attr);
182 vp = pairfind(vp->next, da->attr)) {
183 index = valuepair2str(out, outlen - 1, vp, da->type, func);
184 rad_assert(index <= outlen);
186 outlen -= (index + 1);
191 if (outlen == 0) break;
202 p += 1 + strspn(p + 1, "0123456789");
204 DEBUG2("xlat: Invalid array reference in string at %s %s",
210 * Find the N'th value.
212 for (vp = pairfind(vps, da->attr);
214 vp = pairfind(vp->next, da->attr)) {
215 if (index == 0) break;
220 * Non-existent array reference.
224 return valuepair2str(out, outlen, vp, da->type, func);
227 vp = pairfind(vps, da->attr);
230 * Some "magic" handlers, which are never in VP's, but
231 * which are in the packet.
236 localvp.strvalue[0] = 0;
243 dval = dict_valbyattr(da->attr, packet->code);
245 snprintf(out, outlen, "%s", dval->name);
247 snprintf(out, outlen, "%d", packet->code);
253 case PW_PACKET_SRC_IP_ADDRESS:
254 localvp.attribute = da->attr;
255 localvp.lvalue = packet->src_ipaddr;
258 case PW_PACKET_DST_IP_ADDRESS:
259 localvp.attribute = da->attr;
260 localvp.lvalue = packet->dst_ipaddr;
263 case PW_PACKET_SRC_PORT:
264 localvp.attribute = da->attr;
265 localvp.lvalue = packet->src_port;
268 case PW_PACKET_DST_PORT:
269 localvp.attribute = da->attr;
270 localvp.lvalue = packet->dst_port;
273 case PW_PACKET_AUTHENTICATION_VECTOR:
274 localvp.attribute = da->attr;
275 memcpy(localvp.strvalue, packet->vector,
276 sizeof(packet->vector));
277 localvp.length = sizeof(packet->vector);
281 * Authorization, accounting, etc.
283 case PW_REQUEST_PROCESSING_STAGE:
284 if (request->component) {
285 strNcpy(out, request->component, outlen);
287 strNcpy(out, "server_core", outlen);
292 return 0; /* not found */
296 localvp.type = da->type;
297 return valuepair2str(out, outlen, &localvp,
307 if (!vps) return 0; /* silently fail */
310 * Convert the VP to a string, and return it.
312 return valuepair2str(out, outlen, vp, da->type, func);
317 * Pull %{0} to %{8} out of the packet.
319 static int xlat_regex(void *instance, REQUEST *request,
320 char *fmt, char *out, size_t outlen,
321 RADIUS_ESCAPE_STRING func)
326 * We cheat: fmt is "0" to "8", but those numbers
327 * are already in the "instance".
329 fmt = fmt; /* -Wunused */
330 func = func; /* -Wunused FIXME: do escaping? */
332 regex = request_data_get(request, request,
333 REQUEST_DATA_REGEX | *(int *)instance);
334 if (!regex) return 0;
337 * Copy UP TO "freespace" bytes, including
340 strNcpy(out, regex, outlen);
341 free(regex); /* was strdup'd */
344 #endif /* HAVE_REGEX_H */
347 * Compare two xlat_t structs, based ONLY on the module name.
349 static int xlat_cmp(const void *a, const void *b)
351 if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
352 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
355 return memcmp(((const xlat_t *)a)->module,
356 ((const xlat_t *)b)->module,
357 ((const xlat_t *)a)->length);
362 * find the appropriate registered xlat function.
364 static xlat_t *xlat_find(const char *module)
371 while (*module && (*module != ':')) {
372 *(p++) = *(module++);
377 return rbtree_finddata(xlat_root, &my_xlat);
382 * Register an xlat function.
384 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
389 if ((module == NULL) || (strlen(module) == 0)) {
390 DEBUG("xlat_register: Invalid module name");
395 * First time around, build up the tree...
397 * FIXME: This code should be hoisted out of this function,
398 * and into a global "initialization". But it isn't critical...
406 xlat_root = rbtree_create(xlat_cmp, free, 0);
408 DEBUG("xlat_register: Failed to create tree.");
413 * Register the internal packet xlat's.
415 for (i = 0; internal_xlat[i] != NULL; i++) {
416 xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
417 c = xlat_find(internal_xlat[i]);
418 rad_assert(c != NULL);
424 * Register xlat's for regexes.
427 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
429 xlat_register(buffer, xlat_regex, &xlat_inst[i]);
430 c = xlat_find(buffer);
431 rad_assert(c != NULL);
434 #endif /* HAVE_REGEX_H */
438 * If it already exists, replace the instance.
440 strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
441 my_xlat.length = strlen(my_xlat.module);
442 c = rbtree_finddata(xlat_root, &my_xlat);
445 DEBUG("xlat_register: Cannot re-define internal xlat");
450 c->instance = instance;
455 * Doesn't exist. Create it.
457 c = rad_malloc(sizeof(xlat_t));
458 memset(c, 0, sizeof(*c));
461 strNcpy(c->module, module, sizeof(c->module));
462 c->length = strlen(c->module);
463 c->instance = instance;
465 rbtree_insert(xlat_root, c);
471 * Unregister an xlat function.
473 * We can only have one function to call per name, so the
474 * passing of "func" here is extraneous.
476 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
481 func = func; /* -Wunused */
483 strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
484 my_xlat.length = strlen(my_xlat.module);
486 node = rbtree_find(xlat_root, &my_xlat);
489 rbtree_delete(xlat_root, node);
493 * De-register all xlat functions,
494 * used mainly for debugging.
498 rbtree_free(xlat_root);
503 * Decode an attribute name into a string.
505 static void decode_attribute(const char **from, char **to, int freespace,
506 int *open, REQUEST *request,
507 RADIUS_ESCAPE_STRING func)
511 char *xlat_string = NULL; /* can be large */
514 int found=0, retlen=0;
515 int openbraces = *open;
525 * Skip the '{' at the front of 'p'
526 * Increment open braces
537 * First, copy the xlat key name to one buffer
539 while (*p && (*p != '}') && (*p != ':')) {
542 if (pa >= (xlat_name + sizeof(xlat_name) - 1)) {
544 * Skip to the end of the input
547 DEBUG("xlat: Module name is too long in string %%%s",
555 DEBUG("xlat: Invalid syntax in %s", *from);
558 * %{name} is a simple attribute reference, use it.
560 } else if (*p == '}') {
563 rad_assert(openbraces == *open);
565 if ((retlen = xlat_packet(&xlat_inst[1], request, xlat_name,
566 q, freespace, func)) > 0) {
569 } else if (p[1] == '-') { /* handle ':- */
571 if ((retlen = xlat_packet(&xlat_inst[1], request, xlat_name,
572 q, freespace, func)) > 0) {
576 } else { /* module name, followed by per-module string */
579 rad_assert(*p == ':');
580 p++; /* skip the ':' */
582 xlat_string = rad_malloc(strlen(p) + 1); /* always returns */
586 * Copy over the rest of the string, which is per-module
589 while (*p && !stop) {
592 * What the heck is this supposed
601 * This is pretty hokey... we
602 * should use the functions in
612 if (openbraces == *open) {
629 * Look up almost everything in the new tree of xlat
630 * functions. This makes it a little quicker...
632 if ((c = xlat_find(xlat_name)) != NULL) {
633 if (!c->internal) DEBUG("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
634 c->module, xlat_string);
635 retlen = c->do_xlat(c->instance, request, xlat_string,
637 /* If retlen is 0, treat it as not found */
638 if (retlen > 0) found = 1;
642 * No attribute by that name, return an error.
644 DEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
650 * Skip to last '}' if attr is found
651 * The rest of the stuff within the braces is
652 * useless if we found what we need
656 snprintf(q, freespace, "%d", retlen);
662 while((*p != '\0') && (openbraces > 0)) {
664 * Handle escapes outside of the loop.
669 p++; /* get & ignore next character */
688 p++; /* skip the character */
693 if (xlat_string) free(xlat_string);
701 * If the caller doesn't pass xlat an escape function, then
702 * we use this one. It simplifies the coding, as the check for
703 * func == NULL only happens once.
705 static int xlat_copy(char *out, int outlen, const char *in)
711 * Truncate, if too much.
720 * FIXME: Do escaping of bad stuff!
734 * Replace %<whatever> in a string.
736 * See 'doc/variables.txt' for more information.
738 int radius_xlat(char *out, int outlen, const char *fmt,
739 REQUEST *request, RADIUS_ESCAPE_STRING func)
746 char tmpdt[40]; /* For temporary storing of dates */
752 if (!fmt || !out || !request) return 0;
755 * Ensure that we always have an escaping function.
764 /* Calculate freespace in output */
765 freespace = outlen - (q - out);
770 if ((c != '%') && (c != '$') && (c != '\\')) {
772 * We check if we're inside an open brace. If we are
773 * then we assume this brace is NOT literal, but is
774 * a closing brace and apply it
776 if ((c == '}') && openbraces) {
786 * There's nothing after this character, copy
787 * the last '%' or "$' or '\\' over to the output
814 * Hmmm... ${User-Name} is a synonym for
819 } else if (c == '$') switch(*p) {
820 case '{': /* Attribute by Name */
821 decode_attribute(&p, &q, freespace, &openbraces, request, func);
828 } else if (c == '%') switch(*p) {
830 decode_attribute(&p, &q, freespace, &openbraces, request, func);
836 case 'a': /* Protocol: */
837 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
840 case 'c': /* Callback-Number */
841 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
844 case 'd': /* request day */
845 TM = localtime_r(&request->timestamp, &s_TM);
846 strftime(tmpdt,sizeof(tmpdt),"%d",TM);
847 strNcpy(q,tmpdt,freespace);
851 case 'f': /* Framed IP address */
852 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
855 case 'i': /* Calling station ID */
856 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
859 case 'l': /* request timestamp */
860 snprintf(tmpdt, sizeof(tmpdt), "%lu",
861 (unsigned long) request->timestamp);
862 strNcpy(q,tmpdt,freespace);
866 case 'm': /* request month */
867 TM = localtime_r(&request->timestamp, &s_TM);
868 strftime(tmpdt,sizeof(tmpdt),"%m",TM);
869 strNcpy(q,tmpdt,freespace);
873 case 'n': /* NAS IP address */
874 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
877 case 'p': /* Port number */
878 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
881 case 's': /* Speed */
882 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
885 case 't': /* request timestamp */
886 CTIME_R(&request->timestamp, q, freespace);
890 case 'u': /* User name */
891 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
894 case 'A': /* radacct_dir */
895 strNcpy(q,radacct_dir,freespace-1);
899 case 'C': /* ClientName */
900 strNcpy(q,client_name(request->packet->src_ipaddr),freespace-1);
904 case 'D': /* request date */
905 TM = localtime_r(&request->timestamp, &s_TM);
906 strftime(tmpdt,sizeof(tmpdt),"%Y%m%d",TM);
907 strNcpy(q,tmpdt,freespace);
911 case 'H': /* request hour */
912 TM = localtime_r(&request->timestamp, &s_TM);
913 strftime(tmpdt,sizeof(tmpdt),"%H",TM);
914 strNcpy(q,tmpdt,freespace);
918 case 'L': /* radlog_dir */
919 strNcpy(q,radlog_dir,freespace-1);
924 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
927 case 'R': /* radius_dir */
928 strNcpy(q,radius_dir,freespace-1);
932 case 'S': /* request timestamp in SQL format*/
933 TM = localtime_r(&request->timestamp, &s_TM);
934 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d %H:%M:%S",TM);
935 strNcpy(q,tmpdt,freespace);
939 case 'T': /* request timestamp */
940 TM = localtime_r(&request->timestamp, &s_TM);
941 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d-%H.%M.%S.000000",TM);
942 strNcpy(q,tmpdt,freespace);
946 case 'U': /* Stripped User name */
947 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
950 case 'V': /* Request-Authenticator */
951 if (request->packet->verified)
952 strNcpy(q,"Verified",freespace-1);
954 strNcpy(q,"None",freespace-1);
958 case 'Y': /* request year */
959 TM = localtime_r(&request->timestamp, &s_TM);
960 strftime(tmpdt,sizeof(tmpdt),"%Y",TM);
961 strNcpy(q,tmpdt,freespace);
965 case 'Z': /* Full request pairs except password */
966 tmp = request->packet->vps;
967 while (tmp && (freespace > 3)) {
968 if (tmp->attribute != PW_PASSWORD) {
970 i = vp_prints(q,freespace-2,tmp);
980 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
990 DEBUG2("radius_xlat: '%s'", out);