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",
59 static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; /* up to 8 for regex */
63 * Convert the value on a VALUE_PAIR to string
65 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
66 int type, RADIUS_ESCAPE_STRING func)
68 char buffer[MAX_STRING_LEN * 4];
71 vp_prints_value(buffer, sizeof(buffer), pair, 0);
72 return func(out, outlen, buffer);
77 strNcpy(out,"_",outlen);
79 case PW_TYPE_INTEGER :
80 strNcpy(out,"0",outlen);
83 strNcpy(out,"?.?.?.?",outlen);
86 strNcpy(out,"0",outlen);
89 strNcpy(out,"unknown_type",outlen);
96 * Dynamically translate for check:, request:, reply:, etc.
98 static int xlat_packet(void *instance, REQUEST *request,
99 char *fmt, char *out, size_t outlen,
100 RADIUS_ESCAPE_STRING func)
104 VALUE_PAIR *vps = NULL;
105 RADIUS_PACKET *packet = NULL;
107 switch (*(int*) instance) {
109 vps = request->config_items;
113 vps = request->packet->vps;
114 packet = request->packet;
118 vps = request->reply->vps;
119 packet = request->reply;
123 if (request->proxy) vps = request->proxy->vps;
124 packet = request->proxy;
128 if (request->proxy_reply) vps = request->proxy_reply->vps;
129 packet = request->proxy_reply;
137 * The "format" string is the attribute name.
139 da = dict_attrbyname(fmt);
142 const char *p = strchr(fmt, '[');
146 if (strlen(fmt) > sizeof(buffer)) return 0;
148 strNcpy(buffer, fmt, p - fmt + 1);
150 da = dict_attrbyname(buffer);
154 * %{Attribute-Name[#]} returns the count of
155 * attributes of that name in the list.
157 if ((p[1] == '#') && (p[2] == ']')) {
160 for (vp = pairfind(vps, da->attr);
162 vp = pairfind(vp->next, da->attr)) {
165 snprintf(out, outlen, "%d", index);
170 * %{Attribute-Name[*]} returns ALL of the
171 * the attributes, separated by a newline.
173 if ((p[1] == '*') && (p[2] == ']')) {
176 for (vp = pairfind(vps, da->attr);
178 vp = pairfind(vp->next, da->attr)) {
179 index = valuepair2str(out, outlen - 1, vp, da->type, func);
180 rad_assert(index <= outlen);
182 outlen -= (index + 1);
187 if (outlen == 0) break;
198 p += 1 + strspn(p + 1, "0123456789");
200 DEBUG2("xlat: Invalid array reference in string at %s %s",
206 * Find the N'th value.
208 for (vp = pairfind(vps, da->attr);
210 vp = pairfind(vp->next, da->attr)) {
211 if (index == 0) break;
216 * Non-existent array reference.
220 return valuepair2str(out, outlen, vp, da->type, func);
223 vp = pairfind(vps, da->attr);
226 * Some "magic" handlers, which are never in VP's, but
227 * which are in the packet.
229 * FIXME: Add SRC/DST IP address!
237 dval = dict_valbyattr(da->attr, packet->code);
239 snprintf(out, outlen, "%s", dval->name);
241 snprintf(out, outlen, "%d", packet->code);
258 if (!vps) return 0; /* silently fail */
261 * Convert the VP to a string, and return it.
263 return valuepair2str(out, outlen, vp, da->type, func);
268 * Pull %{0} to %{8} out of the packet.
270 static int xlat_regex(void *instance, REQUEST *request,
271 char *fmt, char *out, size_t outlen,
272 RADIUS_ESCAPE_STRING func)
277 * We cheat: fmt is "0" to "8", but those numbers
278 * are already in the "instance".
280 fmt = fmt; /* -Wunused */
281 func = func; /* -Wunused FIXME: do escaping? */
283 regex = request_data_get(request, request,
284 REQUEST_DATA_REGEX | *(int *)instance);
285 if (!regex) return 0;
288 * Copy UP TO "freespace" bytes, including
291 strNcpy(out, regex, outlen);
292 free(regex); /* was strdup'd */
295 #endif /* HAVE_REGEX_H */
298 * Compare two xlat_t structs, based ONLY on the module name.
300 static int xlat_cmp(const void *a, const void *b)
302 if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
303 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
306 return memcmp(((const xlat_t *)a)->module,
307 ((const xlat_t *)b)->module,
308 ((const xlat_t *)a)->length);
313 * find the appropriate registered xlat function.
315 static xlat_t *xlat_find(const char *module)
320 strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
323 * We get passed the WHOLE string, and all we want here
324 * is the first piece.
326 p = strchr(my_xlat.module, ':');
329 my_xlat.length = strlen(my_xlat.module);
331 return rbtree_finddata(xlat_root, &my_xlat);
336 * Register an xlat function.
338 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
343 if ((module == NULL) || (strlen(module) == 0)) {
344 DEBUG("xlat_register: Invalid module name");
349 * First time around, build up the tree...
351 * FIXME: This code should be hoisted out of this function,
352 * and into a global "initialization". But it isn't critical...
360 xlat_root = rbtree_create(xlat_cmp, free, 0);
362 DEBUG("xlat_register: Failed to create tree.");
367 * Register the internal packet xlat's.
369 for (i = 0; internal_xlat[i] != NULL; i++) {
370 xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
371 c = xlat_find(internal_xlat[i]);
372 rad_assert(c != NULL);
378 * Register xlat's for regexes.
381 for (i = 0; i <= 8; i++) {
383 xlat_register(buffer, xlat_regex, &xlat_inst[i]);
384 c = xlat_find(buffer);
385 rad_assert(c != NULL);
388 #endif /* HAVE_REGEX_H */
392 * If it already exists, replace the instance.
394 strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
395 my_xlat.length = strlen(my_xlat.module);
396 c = rbtree_finddata(xlat_root, &my_xlat);
399 DEBUG("xlat_register: Cannot re-define internal xlat");
404 c->instance = instance;
409 * Doesn't exist. Create it.
411 c = rad_malloc(sizeof(xlat_t));
412 memset(c, 0, sizeof(*c));
415 strNcpy(c->module, module, sizeof(c->module));
416 c->length = strlen(c->module);
417 c->instance = instance;
419 rbtree_insert(xlat_root, c);
425 * Unregister an xlat function.
427 * We can only have one function to call per name, so the
428 * passing of "func" here is extraneous.
430 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
435 func = func; /* -Wunused */
437 strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
438 my_xlat.length = strlen(my_xlat.module);
440 node = rbtree_find(xlat_root, &my_xlat);
443 rbtree_delete(xlat_root, node);
448 * Decode an attribute name into a string.
450 static void decode_attribute(const char **from, char **to, int freespace,
451 int *open, REQUEST *request,
452 RADIUS_ESCAPE_STRING func)
458 int stop=0, found=0, retlen=0;
459 int openbraces = *open;
469 * Skip the '{' at the front of 'p'
470 * Increment open braces
481 * Copy over the rest of the string.
483 while ((*p) && (!stop)) {
486 * Allow braces inside things, too.
500 if (openbraces == *open) {
509 * Attr-Name1:-Attr-Name2
511 * Use Attr-Name1, and if not found,
520 /* else FALL-THROUGH */
530 * Look up almost everything in the new tree of xlat
531 * functions. this makes it a little quicker...
533 if ((c = xlat_find(attrname)) != NULL) {
534 if (!c->internal) DEBUG("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
535 c->module, attrname+ c->length + 1);
536 retlen = c->do_xlat(c->instance, request, attrname+(c->length+1), q, freespace, func);
537 /* If retlen is 0, treat it as not found */
538 if (retlen > 0) found = 1;
541 * Not in the default xlat database. Must be
542 * a bare attribute number.
544 } else if ((retlen = xlat_packet(&xlat_inst[1], request, attrname,
545 q, freespace, func)) > 0) {
549 * Look up the name, in order to get the correct
553 } else if (dict_attrbyname(attrname) == NULL) {
555 * No attribute by that name, return an error.
557 DEBUG2("WARNING: Attempt to use unknown xlat function, or non-existent attribute in string %%{%s}", attrname);
559 } /* else the attribute is known, but not in the request */
562 * Skip to last '}' if attr is found
563 * The rest of the stuff within the braces is
564 * useless if we found what we need
568 snprintf(q, freespace, "%d", retlen);
574 while((*p != '\0') && (openbraces > 0)) {
576 * Handle escapes outside of the loop.
581 p++; /* get & ignore next character */
600 p++; /* skip the character */
610 * If the caller doesn't pass xlat an escape function, then
611 * we use this one. It simplifies the coding, as the check for
612 * func == NULL only happens once.
614 static int xlat_copy(char *out, int outlen, const char *in)
620 * Truncate, if too much.
629 * FIXME: Do escaping of bad stuff!
643 * Replace %<whatever> in a string.
645 * See 'doc/variables.txt' for more information.
647 int radius_xlat(char *out, int outlen, const char *fmt,
648 REQUEST *request, RADIUS_ESCAPE_STRING func)
655 char tmpdt[40]; /* For temporary storing of dates */
659 * Ensure that we always have an escaping function.
668 /* Calculate freespace in output */
669 freespace = outlen - (q - out);
674 if ((c != '%') && (c != '$') && (c != '\\')) {
676 * We check if we're inside an open brace. If we are
677 * then we assume this brace is NOT literal, but is
678 * a closing brace and apply it
680 if ((c == '}') && openbraces) {
690 * There's nothing after this character, copy
691 * the last '%' or "$' or '\\' over to the output
718 * Hmmm... ${User-Name} is a synonym for
723 } else if (c == '$') switch(*p) {
724 case '{': /* Attribute by Name */
725 decode_attribute(&p, &q, freespace, &openbraces, request, func);
732 } else if (c == '%') switch(*p) {
734 decode_attribute(&p, &q, freespace, &openbraces, request, func);
740 case 'a': /* Protocol: */
741 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
744 case 'c': /* Callback-Number */
745 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
748 case 'd': /* request day */
749 TM = localtime_r(&request->timestamp, &s_TM);
750 strftime(tmpdt,sizeof(tmpdt),"%d",TM);
751 strNcpy(q,tmpdt,freespace);
755 case 'f': /* Framed IP address */
756 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
759 case 'i': /* Calling station ID */
760 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
763 case 'l': /* request timestamp */
764 snprintf(tmpdt, sizeof(tmpdt), "%lu",
765 (unsigned long) request->timestamp);
766 strNcpy(q,tmpdt,freespace);
770 case 'm': /* request month */
771 TM = localtime_r(&request->timestamp, &s_TM);
772 strftime(tmpdt,sizeof(tmpdt),"%m",TM);
773 strNcpy(q,tmpdt,freespace);
777 case 'n': /* NAS IP address */
778 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
781 case 'p': /* Port number */
782 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
785 case 's': /* Speed */
786 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
789 case 't': /* request timestamp */
790 CTIME_R(&request->timestamp, q, freespace);
794 case 'u': /* User name */
795 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
798 case 'A': /* radacct_dir */
799 strNcpy(q,radacct_dir,freespace-1);
803 case 'C': /* ClientName */
804 strNcpy(q,client_name(request->packet->src_ipaddr),freespace-1);
808 case 'D': /* request date */
809 TM = localtime_r(&request->timestamp, &s_TM);
810 strftime(tmpdt,sizeof(tmpdt),"%Y%m%d",TM);
811 strNcpy(q,tmpdt,freespace);
815 case 'H': /* request hour */
816 TM = localtime_r(&request->timestamp, &s_TM);
817 strftime(tmpdt,sizeof(tmpdt),"%H",TM);
818 strNcpy(q,tmpdt,freespace);
822 case 'L': /* radlog_dir */
823 strNcpy(q,radlog_dir,freespace-1);
828 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
831 case 'R': /* radius_dir */
832 strNcpy(q,radius_dir,freespace-1);
836 case 'S': /* request timestamp in SQL format*/
837 TM = localtime_r(&request->timestamp, &s_TM);
838 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d %H:%M:%S",TM);
839 strNcpy(q,tmpdt,freespace);
843 case 'T': /* request timestamp */
844 TM = localtime_r(&request->timestamp, &s_TM);
845 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d-%H.%M.%S.000000",TM);
846 strNcpy(q,tmpdt,freespace);
850 case 'U': /* Stripped User name */
851 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
854 case 'V': /* Request-Authenticator */
855 if (request->packet->verified)
856 strNcpy(q,"Verified",freespace-1);
858 strNcpy(q,"None",freespace-1);
862 case 'Y': /* request year */
863 TM = localtime_r(&request->timestamp, &s_TM);
864 strftime(tmpdt,sizeof(tmpdt),"%Y",TM);
865 strNcpy(q,tmpdt,freespace);
869 case 'Z': /* Full request pairs except password */
870 tmp = request->packet->vps;
871 while (tmp && (freespace > 3)) {
872 if (tmp->attribute != PW_PASSWORD) {
874 i = vp_prints(q,freespace-2,tmp);
884 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
894 DEBUG2("radius_xlat: '%s'", out);