Moved conditional syntax to %{%{foo}:-%{bar}}, which is more
[freeradius.git] / src / main / xlat.c
1 /*
2  * xlat.c       Translate strings.  This is the first version of xlat
3  *              incorporated to RADIUS
4  *
5  * Version:     $Id$
6  *
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.
11  *
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.
16  *
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
20  *
21  * Copyright 2000,2006  The FreeRADIUS server project
22  * Copyright 2000  Alan DeKok <aland@ox.org>
23  */
24
25 #include <freeradius-devel/ident.h>
26 RCSID("$Id$")
27
28 #include        <freeradius-devel/radiusd.h>
29 #include        <freeradius-devel/rad_assert.h>
30
31 #include        <ctype.h>
32
33 typedef struct xlat_t {
34         char            module[MAX_STRING_LEN];
35         int             length;
36         void            *instance;
37         RAD_XLAT_FUNC   do_xlat;
38         int             internal;       /* not allowed to re-define these */
39 } xlat_t;
40
41 static rbtree_t *xlat_root = NULL;
42
43 /*
44  *      Define all xlat's in the structure.
45  */
46 static const char * const internal_xlat[] = {"check",
47                                              "request",
48                                              "reply",
49                                              "proxy-request",
50                                              "proxy-reply",
51                                              NULL};
52
53 #if REQUEST_MAX_REGEX > 8
54 #error Please fix the following line
55 #endif
56 static const int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };   /* up to 8 for regex */
57
58
59 /*
60  *      Convert the value on a VALUE_PAIR to string
61  */
62 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
63                          int type, RADIUS_ESCAPE_STRING func)
64 {
65         char buffer[MAX_STRING_LEN * 4];
66
67         if (pair != NULL) {
68                 vp_prints_value(buffer, sizeof(buffer), pair, -1);
69                 return func(out, outlen, buffer);
70         }
71
72         switch (type) {
73         case PW_TYPE_STRING :
74                 strlcpy(out,"_",outlen);
75                 break;
76         case PW_TYPE_INTEGER :
77                 strlcpy(out,"0",outlen);
78                 break;
79         case PW_TYPE_IPADDR :
80                 strlcpy(out,"?.?.?.?",outlen);
81                 break;
82         case PW_TYPE_IPV6ADDR :
83                 strlcpy(out,":?:",outlen);
84                 break;
85         case PW_TYPE_DATE :
86                 strlcpy(out,"0",outlen);
87                 break;
88         default :
89                 strlcpy(out,"unknown_type",outlen);
90         }
91         return strlen(out);
92 }
93
94
95 /*
96  *      Dynamically translate for check:, request:, reply:, etc.
97  */
98 static int xlat_packet(void *instance, REQUEST *request,
99                        char *fmt, char *out, size_t outlen,
100                        RADIUS_ESCAPE_STRING func)
101 {
102         DICT_ATTR       *da;
103         VALUE_PAIR      *vp;
104         VALUE_PAIR      *vps = NULL;
105         RADIUS_PACKET   *packet = NULL;
106
107         switch (*(int*) instance) {
108         case 0:
109                 vps = request->config_items;
110                 break;
111
112         case 1:
113                 vps = request->packet->vps;
114                 packet = request->packet;
115                 break;
116
117         case 2:
118                 vps = request->reply->vps;
119                 packet = request->reply;
120                 break;
121
122         case 3:
123                 if (request->proxy) vps = request->proxy->vps;
124                 packet = request->proxy;
125                 break;
126
127         case 4:
128                 if (request->proxy_reply) vps = request->proxy_reply->vps;
129                 packet = request->proxy_reply;
130                 break;
131
132         default:                /* WTF? */
133                 return 0;
134         }
135
136         /*
137          *      The "format" string is the attribute name.
138          */
139         da = dict_attrbyname(fmt);
140         if (!da) {
141                 int count;
142                 const char *p = strchr(fmt, '[');
143                 char buffer[256];
144
145                 if (!p) return 0;
146                 if (strlen(fmt) > sizeof(buffer)) return 0;
147
148                 strlcpy(buffer, fmt, p - fmt + 1);
149
150                 da = dict_attrbyname(buffer);
151                 if (!da) return 0;
152
153                 /*
154                  *      %{Attribute-Name[#]} returns the count of
155                  *      attributes of that name in the list.
156                  */
157                 if ((p[1] == '#') && (p[2] == ']')) {
158                         count = 0;
159
160                         for (vp = pairfind(vps, da->attr);
161                              vp != NULL;
162                              vp = pairfind(vp->next, da->attr)) {
163                                 count++;
164                         }
165                         snprintf(out, outlen, "%d", count);
166                         return strlen(out);
167                 }
168
169                 /*
170                  *      %{Attribute-Name[*]} returns ALL of the
171                  *      the attributes, separated by a newline.
172                  */
173                 if ((p[1] == '*') && (p[2] == ']')) {
174                         int total = 0;
175
176                         for (vp = pairfind(vps, da->attr);
177                              vp != NULL;
178                              vp = pairfind(vp->next, da->attr)) {
179                                 count = valuepair2str(out, outlen - 1, vp, da->type, func);
180                                 rad_assert(count <= outlen);
181                                 total += count + 1;
182                                 outlen -= (count + 1);
183                                 out += count;
184
185                                 *(out++) = '\n';
186
187                                 if (outlen == 0) break;
188                         }
189
190                         return total;
191                 }
192
193                 count = atoi(p + 1);
194
195                 /*
196                  *      Skip the numbers.
197                  */
198                 p += 1 + strspn(p + 1, "0123456789");
199                 if (*p != ']') {
200                         DEBUG2("xlat: Invalid array reference in string at %s %s",
201                                fmt, p);
202                         return 0;
203                 }
204
205                 /*
206                  *      Find the N'th value.
207                  */
208                 for (vp = pairfind(vps, da->attr);
209                      vp != NULL;
210                      vp = pairfind(vp->next, da->attr)) {
211                         if (count == 0) break;
212                         count--;
213                 }
214
215                 /*
216                  *      Non-existent array reference.
217                  */
218                 if (!vp) return 0;
219
220                 return valuepair2str(out, outlen, vp, da->type, func);
221         }
222
223         vp = pairfind(vps, da->attr);
224         if (!vp) {
225                 /*
226                  *      Some "magic" handlers, which are never in VP's, but
227                  *      which are in the packet.
228                  *
229                  *      FIXME: We should really do this in a more
230                  *      intelligent way...
231                  */
232                 if (packet) {
233                         VALUE_PAIR localvp;
234
235                         memset(&localvp, 0, sizeof(localvp));
236
237                         switch (da->attr) {
238                         case PW_PACKET_TYPE:
239                         {
240                                 DICT_VALUE *dval;
241
242                                 dval = dict_valbyattr(da->attr, packet->code);
243                                 if (dval) {
244                                         snprintf(out, outlen, "%s", dval->name);
245                                 } else {
246                                         snprintf(out, outlen, "%d", packet->code);
247                                 }
248                                 return strlen(out);
249                         }
250                         break;
251
252                         case PW_CLIENT_IP_ADDRESS: /* the same as below */
253                         case PW_PACKET_SRC_IP_ADDRESS:
254                                 if (packet->src_ipaddr.af != AF_INET) {
255                                         return 0;
256                                 }
257                                 localvp.attribute = da->attr;
258                                 localvp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
259                                 break;
260
261                         case PW_PACKET_DST_IP_ADDRESS:
262                                 if (packet->dst_ipaddr.af != AF_INET) {
263                                         return 0;
264                                 }
265                                 localvp.attribute = da->attr;
266                                 localvp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
267                                 break;
268
269                         case PW_PACKET_SRC_PORT:
270                                 localvp.attribute = da->attr;
271                                 localvp.vp_integer = packet->src_port;
272                                 break;
273
274                         case PW_PACKET_DST_PORT:
275                                 localvp.attribute = da->attr;
276                                 localvp.vp_integer = packet->dst_port;
277                                 break;
278
279                         case PW_PACKET_AUTHENTICATION_VECTOR:
280                                 localvp.attribute = da->attr;
281                                 memcpy(localvp.vp_strvalue, packet->vector,
282                                        sizeof(packet->vector));
283                                 localvp.length = sizeof(packet->vector);
284                                 break;
285
286                                 /*
287                                  *      Authorization, accounting, etc.
288                                  */
289                         case PW_REQUEST_PROCESSING_STAGE:
290                                 if (request->component) {
291                                         strlcpy(out, request->component, outlen);
292                                 } else {
293                                         strlcpy(out, "server_core", outlen);
294                                 }
295                                 return strlen(out);
296
297                         case PW_PACKET_SRC_IPV6_ADDRESS:
298                                 if (packet->src_ipaddr.af != AF_INET6) {
299                                         return 0;
300                                 }
301                                 localvp.attribute = da->attr;
302                                 memcpy(localvp.vp_strvalue,
303                                        &packet->src_ipaddr.ipaddr.ip6addr,
304                                        sizeof(packet->src_ipaddr.ipaddr.ip6addr));
305                                 break;
306
307                         case PW_PACKET_DST_IPV6_ADDRESS:
308                                 if (packet->dst_ipaddr.af != AF_INET6) {
309                                         return 0;
310                                 }
311                                 localvp.attribute = da->attr;
312                                 memcpy(localvp.vp_strvalue,
313                                        &packet->dst_ipaddr.ipaddr.ip6addr,
314                                        sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
315                                 break;
316
317                         case PW_VIRTUAL_SERVER:
318                                 if (!request->server) return 0;
319
320                                 snprintf(out, outlen, "%s", request->server);
321                                 return strlen(out);
322                                 break;
323
324                         default:
325                                 return 0; /* not found */
326                                 break;
327                         }
328
329                         localvp.type = da->type;
330                         return valuepair2str(out, outlen, &localvp,
331                                              da->type, func);
332                 }
333
334                 /*
335                  *      Not found, die.
336                  */
337                 return 0;
338         }
339
340         if (!vps) return 0;     /* silently fail */
341
342         /*
343          *      Convert the VP to a string, and return it.
344          */
345         return valuepair2str(out, outlen, vp, da->type, func);
346 }
347
348 #ifdef HAVE_REGEX_H
349 /*
350  *      Pull %{0} to %{8} out of the packet.
351  */
352 static int xlat_regex(void *instance, REQUEST *request,
353                       char *fmt, char *out, size_t outlen,
354                       RADIUS_ESCAPE_STRING func)
355 {
356         char *regex;
357
358         /*
359          *      We cheat: fmt is "0" to "8", but those numbers
360          *      are already in the "instance".
361          */
362         fmt = fmt;              /* -Wunused */
363         func = func;            /* -Wunused FIXME: do escaping? */
364
365         regex = request_data_reference(request, request,
366                                  REQUEST_DATA_REGEX | *(int *)instance);
367         if (!regex) return 0;
368
369         /*
370          *      Copy UP TO "freespace" bytes, including
371          *      a zero byte.
372          */
373         strlcpy(out, regex, outlen);
374         return strlen(out);
375 }
376 #endif                          /* HAVE_REGEX_H */
377
378
379 /*
380  *      Compare two xlat_t structs, based ONLY on the module name.
381  */
382 static int xlat_cmp(const void *a, const void *b)
383 {
384         if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
385                 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
386         }
387
388         return memcmp(((const xlat_t *)a)->module,
389                       ((const xlat_t *)b)->module,
390                       ((const xlat_t *)a)->length);
391 }
392
393
394 /*
395  *      find the appropriate registered xlat function.
396  */
397 static const xlat_t *xlat_find(const char *module)
398 {
399         xlat_t my_xlat;
400
401         /*
402          *      Look for dictionary attributes first.
403          */
404         if ((dict_attrbyname(module) != NULL) ||
405             (strchr(module, '[') != NULL)) {
406                 static const xlat_t dict_xlat = {
407                         "request",
408                         7,
409                         &xlat_inst[1],
410                         xlat_packet,
411                         TRUE
412                 };
413
414                 return &dict_xlat;
415         }
416
417         strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
418         my_xlat.length = strlen(my_xlat.module);
419
420         return rbtree_finddata(xlat_root, &my_xlat);
421 }
422
423
424 /*
425  *      Register an xlat function.
426  */
427 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
428 {
429         xlat_t  *c;
430         xlat_t  my_xlat;
431
432         if ((module == NULL) || (strlen(module) == 0)) {
433                 DEBUG("xlat_register: Invalid module name");
434                 return -1;
435         }
436
437         /*
438          *      First time around, build up the tree...
439          *
440          *      FIXME: This code should be hoisted out of this function,
441          *      and into a global "initialization".  But it isn't critical...
442          */
443         if (!xlat_root) {
444                 int i;
445 #ifdef HAVE_REGEX_H
446                 char buffer[2];
447 #endif
448
449                 xlat_root = rbtree_create(xlat_cmp, free, 0);
450                 if (!xlat_root) {
451                         DEBUG("xlat_register: Failed to create tree.");
452                         return -1;
453                 }
454
455                 /*
456                  *      Register the internal packet xlat's.
457                  */
458                 for (i = 0; internal_xlat[i] != NULL; i++) {
459                         xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
460                         c = xlat_find(internal_xlat[i]);
461                         rad_assert(c != NULL);
462                         c->internal = TRUE;
463                 }
464
465                 /*
466                  *      New name: "control"
467                  */
468                 xlat_register("control", xlat_packet, &xlat_inst[0]);
469                 c = xlat_find("control");
470                 rad_assert(c != NULL);
471                 c->internal = TRUE;
472
473 #ifdef HAVE_REGEX_H
474                 /*
475                  *      Register xlat's for regexes.
476                  */
477                 buffer[1] = '\0';
478                 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
479                         buffer[0] = '0' + i;
480                         xlat_register(buffer, xlat_regex, &xlat_inst[i]);
481                         c = xlat_find(buffer);
482                         rad_assert(c != NULL);
483                         c->internal = TRUE;
484                 }
485 #endif /* HAVE_REGEX_H */
486         }
487
488         /*
489          *      If it already exists, replace the instance.
490          */
491         strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
492         my_xlat.length = strlen(my_xlat.module);
493         c = rbtree_finddata(xlat_root, &my_xlat);
494         if (c) {
495                 if (c->internal) {
496                         DEBUG("xlat_register: Cannot re-define internal xlat");
497                         return -1;
498                 }
499
500                 c->do_xlat = func;
501                 c->instance = instance;
502                 return 0;
503         }
504
505         /*
506          *      Doesn't exist.  Create it.
507          */
508         c = rad_malloc(sizeof(*c));
509         memset(c, 0, sizeof(*c));
510
511         c->do_xlat = func;
512         strlcpy(c->module, module, sizeof(c->module));
513         c->length = strlen(c->module);
514         c->instance = instance;
515
516         rbtree_insert(xlat_root, c);
517
518         return 0;
519 }
520
521 /*
522  *      Unregister an xlat function.
523  *
524  *      We can only have one function to call per name, so the
525  *      passing of "func" here is extraneous.
526  */
527 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
528 {
529         rbnode_t        *node;
530         xlat_t          my_xlat;
531
532         func = func;            /* -Wunused */
533
534         if (!module) return;
535
536         strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
537         my_xlat.length = strlen(my_xlat.module);
538
539         node = rbtree_find(xlat_root, &my_xlat);
540         if (!node) return;
541
542         rbtree_delete(xlat_root, node);
543 }
544
545 /*
546  *      De-register all xlat functions,
547  *      used mainly for debugging.
548  */
549 void xlat_free(void)
550 {
551         rbtree_free(xlat_root);
552 }
553
554
555 /*
556  *      Decode an attribute name into a string.
557  */
558 static void decode_attribute(const char **from, char **to, int freespace,
559                              int *open_p, REQUEST *request,
560                              RADIUS_ESCAPE_STRING func)
561 {
562         int     do_length = 0;
563         char    xlat_name[128];
564         char    *xlat_string = NULL; /* can be large */
565         int     free_xlat_string = FALSE;
566         const char *p;
567         char *q, *pa;
568         int found=0, retlen=0;
569         int openbraces = *open_p;
570         const xlat_t *c;
571         int spaces = FALSE;
572
573         p = *from;
574         q = *to;
575         pa = &xlat_name[0];
576
577         *q = '\0';
578
579         /*
580          * Skip the '{' at the front of 'p'
581          * Increment open braces
582          */
583         p++;
584         openbraces++;
585
586         if (*p == '#') {
587                 p++;
588                 do_length = 1;
589         }
590
591         /*
592          *      Handle %{%{foo}:-%{bar}}, which is useful, too.
593          *
594          *      Did I mention that this parser is garbage?
595          */
596         if ((p[0] == '%') && (p[1] == '{')) {
597                 /*
598                  *      This is really bad, but it works.
599                  */
600                 size_t len1, len2;
601                 size_t mylen = strlen(p);
602                 char *first = rad_malloc(mylen);
603                 char *second = rad_malloc(mylen);
604                 int expand2 = FALSE;
605
606                 len1 = rad_copy_variable(first, p);
607                 if (len1 < 0) {
608                         DEBUG2("Badly formatted variable: %s", p);
609                         goto done;
610                 }
611
612                 if ((p[len1] != ':') || (p[len1 + 1] != '-')) {
613                         DEBUG2("No trailing :- after variable at %s", p);
614                         goto done;
615                 }
616
617                 p += len1 + 2;
618
619                 if ((p[0] == '%') && (p[1] == '{')) {
620                         len2 = rad_copy_variable(second, p);
621
622                         expand2 = TRUE;
623                         if (len2 < 0) {
624                                 DEBUG2("Invalid text after :- at %s", p);
625                                 goto done;
626                         }
627                         p += len2;
628
629                 } else if ((p[0] == '"') || p[0] == '\'') {
630                         getstring(&p, second, mylen);
631
632                 } else {
633                         char *s = second;
634
635                         while (*p && (*p != '}')) {
636                                 *(s++) = *(p++);
637                         }
638                         *s = '\0';
639                 }
640
641                 if (*p != '}') {
642                         DEBUG2("Failed to find trailing '}' in string");
643                         goto done;
644                 }
645
646                 mylen = radius_xlat(q, freespace, first, request, func);
647                 free(first);
648                 if (mylen) {
649                         free(second);
650
651                         q += mylen;
652                         goto done;
653                 }
654
655                 if (!expand2) {
656                         strlcpy(q, second, freespace);
657                         q += strlen(q);
658                 } else {
659                         mylen = radius_xlat(q, freespace, second,
660                                             request, func);
661                         free(second);
662                         if (mylen) {
663                                 q += mylen;
664                                 goto done;
665                         }
666                 }
667
668                 /*
669                  *      Else the output is an empty string.
670                  */
671                 goto done;
672         }
673
674
675         /*
676          *      First, copy the xlat key name to one buffer
677          */
678         while (*p && (*p != '}') && (*p != ':')) {
679                 *pa++ = *p++;
680
681                 if (pa >= (xlat_name + sizeof(xlat_name) - 1)) {
682                         /*
683                          *      Skip to the end of the input
684                          */
685                         p += strlen(p);
686                         DEBUG("xlat: Module name is too long in string %%%s",
687                               *from);
688                         goto done;
689                 }
690         }
691         *pa = '\0';
692
693         if (!*p) {
694                 DEBUG("xlat: Invalid syntax in %s", *from);
695
696                 /*
697                  *      %{name} is a simple attribute reference,
698                  *      or regex reference.
699                  */
700         } else if (*p == '}') {
701                 openbraces--;
702                 rad_assert(openbraces == *open_p);
703
704                 p++;
705                 xlat_string = xlat_name;
706                 goto do_xlat;
707
708         } else if ((p[0] == ':') && (p[1] == '-')) { /* handle ':- */
709                 DEBUG2("WARNING: Deprecated conditional expansion \":-\".  See \"man unlang\" for details");
710                 p += 2;
711                 xlat_string = xlat_name;
712                 goto do_xlat;
713
714         } else {      /* module name, followed by per-module string */
715                 int stop = 0;
716                 int delimitbrace = *open_p;
717
718                 rad_assert(*p == ':');
719                 p++;                    /* skip the ':' */
720
721                 /*
722                  *  If there's a brace immediately following the colon,
723                  *  then we've chosen to delimite the per-module string,
724                  *  so keep track of that.
725                  */
726                 if (*p == '{') {
727                         delimitbrace = openbraces;
728                         openbraces++;
729                         p++;
730                 }
731
732                 xlat_string = rad_malloc(strlen(p) + 1); /* always returns */
733                 free_xlat_string = TRUE;
734                 pa = xlat_string;
735
736                 /*
737                  *  Copy over the rest of the string, which is per-module
738                  *  data.
739                  */
740                 while (*p && !stop) {
741                         switch(*p) {
742
743                                 /*
744                                  *      What the heck is this supposed
745                                  *      to be doing?
746                                  */
747                         case '\\':
748                                 p++; /* skip it */
749                                 *pa++ = *p++;
750                                 break;
751
752                         case ':':
753                                 if (!spaces && p[1] == '-') {
754                                         p += 2;
755                                         stop = 1;
756                                         break;
757                                 }
758                                 *pa++ = *p++;
759                                 break;
760
761                                 /*
762                                  *      This is pretty hokey...  we
763                                  *      should use the functions in
764                                  *      util.c
765                                  */
766                         case '{':
767                                 openbraces++;
768                                 *pa++ = *p++;
769                                 break;
770
771                         case '}':
772                                 openbraces--;
773                                 if (openbraces == delimitbrace) {
774                                         p++;
775                                         stop=1;
776                                 } else {
777                                         *pa++ = *p++;
778                                 }
779                                 break;
780
781                         case ' ':
782                         case '\t':
783                                 spaces = TRUE;
784                                 /* FALL-THROUGH */
785
786                         default:
787                                 *pa++ = *p++;
788                                 break;
789                         }
790                 }
791
792                 *pa = '\0';
793
794                 /*
795                  *      Now check to see if we're at the end of the string
796                  *      we were sent.  If we're not, check for :-
797                  */
798                 if (openbraces == delimitbrace) {
799                         if (p[0] == ':' && p[1] == '-') {
800                                 p += 2;
801                         }
802                 }
803
804                 /*
805                  *      Look up almost everything in the new tree of xlat
806                  *      functions.  This makes it a little quicker...
807                  */
808         do_xlat:
809                 if ((c = xlat_find(xlat_name)) != NULL) {
810                         if (!c->internal) DEBUG3("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
811                                                 c->module, xlat_string);
812                         retlen = c->do_xlat(c->instance, request, xlat_string,
813                                             q, freespace, func);
814                         /* If retlen is 0, treat it as not found */
815                         if (retlen > 0) found = 1;
816
817 #ifndef NDEBUG
818                 } else {
819                         /*
820                          *      No attribute by that name, return an error.
821                          */
822                         DEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
823 #endif
824                 }
825         }
826
827         /*
828          * Skip to last '}' if attr is found
829          * The rest of the stuff within the braces is
830          * useless if we found what we need
831          */
832         if (found) {
833                 if (do_length) {
834                         snprintf(q, freespace, "%d", retlen);
835                         retlen = strlen(q);
836                 }
837
838                 q += retlen;
839
840                 while((*p != '\0') && (openbraces > *open_p)) {
841                         /*
842                          *      Handle escapes outside of the loop.
843                          */
844                         if (*p == '\\') {
845                                 p++;
846                                 if (!*p) break;
847                                 p++; /* get & ignore next character */
848                                 continue;
849                         }
850
851                         switch (*p) {
852                         default:
853                                 break;
854
855                                 /*
856                                  *  Bare brace
857                                  */
858                         case '{':
859                                 openbraces++;
860                                 break;
861
862                         case '}':
863                                 openbraces--;
864                                 break;
865                         }
866                         p++;    /* skip the character */
867                 }
868         }
869
870         done:
871         if (free_xlat_string) free(xlat_string);
872
873         *open_p = openbraces;
874         *from = p;
875         *to = q;
876 }
877
878 /*
879  *  If the caller doesn't pass xlat an escape function, then
880  *  we use this one.  It simplifies the coding, as the check for
881  *  func == NULL only happens once.
882  */
883 static int xlat_copy(char *out, int outlen, const char *in)
884 {
885         int freespace = outlen;
886
887         rad_assert(outlen > 0);
888
889         while ((*in) && (freespace > 1)) {
890                 /*
891                  *  Copy data.
892                  *
893                  *  FIXME: Do escaping of bad stuff!
894                  */
895                 *(out++) = *(in++);
896
897                 freespace--;
898         }
899         *out = '\0';
900
901         return (outlen - freespace); /* count does not include NUL */
902 }
903
904 /*
905  *      Replace %<whatever> in a string.
906  *
907  *      See 'doc/variables.txt' for more information.
908  */
909 int radius_xlat(char *out, int outlen, const char *fmt,
910                 REQUEST *request, RADIUS_ESCAPE_STRING func)
911 {
912         int c, len, freespace;
913         const char *p;
914         char *q;
915         char *nl;
916         VALUE_PAIR *tmp;
917         struct tm *TM, s_TM;
918         char tmpdt[40]; /* For temporary storing of dates */
919         int openbraces=0;
920
921         /*
922          *      Catch bad modules.
923          */
924         if (!fmt || !out || !request) return 0;
925
926         /*
927          *  Ensure that we always have an escaping function.
928          */
929         if (func == NULL) {
930                 func = xlat_copy;
931         }
932
933         q = out;
934         p = fmt;
935         while (*p) {
936                 /* Calculate freespace in output */
937                 freespace = outlen - (q - out);
938                 if (freespace <= 1)
939                         break;
940                 c = *p;
941
942                 if ((c != '%') && (c != '$') && (c != '\\')) {
943                         /*
944                          * We check if we're inside an open brace.  If we are
945                          * then we assume this brace is NOT literal, but is
946                          * a closing brace and apply it
947                          */
948                         if ((c == '}') && openbraces) {
949                                 openbraces--;
950                                 p++; /* skip it */
951                                 continue;
952                         }
953                         *q++ = *p++;
954                         continue;
955                 }
956
957                 /*
958                  *      There's nothing after this character, copy
959                  *      the last '%' or "$' or '\\' over to the output
960                  *      buffer, and exit.
961                  */
962                 if (*++p == '\0') {
963                         *q++ = c;
964                         break;
965                 }
966
967                 if (c == '\\') {
968                         switch(*p) {
969                         case '\\':
970                                 *q++ = *p;
971                                 break;
972                         case 't':
973                                 *q++ = '\t';
974                                 break;
975                         case 'n':
976                                 *q++ = '\n';
977                                 break;
978                         default:
979                                 *q++ = c;
980                                 *q++ = *p;
981                                 break;
982                         }
983                         p++;
984
985                 } else if (c == '%') switch(*p) {
986                         case '{':
987                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
988                                 break;
989
990                         case '%':
991                                 *q++ = *p++;
992                                 break;
993                         case 'a': /* Protocol: */
994                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
995                                 p++;
996                                 break;
997                         case 'c': /* Callback-Number */
998                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
999                                 p++;
1000                                 break;
1001                         case 'd': /* request day */
1002                                 TM = localtime_r(&request->timestamp, &s_TM);
1003                                 len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
1004                                 if (len > 0) {
1005                                         strlcpy(q, tmpdt, freespace);
1006                                         q += strlen(q);
1007                                 }
1008                                 p++;
1009                                 break;
1010                         case 'f': /* Framed IP address */
1011                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
1012                                 p++;
1013                                 break;
1014                         case 'i': /* Calling station ID */
1015                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
1016                                 p++;
1017                                 break;
1018                         case 'l': /* request timestamp */
1019                                 snprintf(tmpdt, sizeof(tmpdt), "%lu",
1020                                          (unsigned long) request->received.tv_sec);
1021                                 strlcpy(q,tmpdt,freespace);
1022                                 q += strlen(q);
1023                                 p++;
1024                                 break;
1025                         case 'm': /* request month */
1026                                 TM = localtime_r(&request->timestamp, &s_TM);
1027                                 len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
1028                                 if (len > 0) {
1029                                         strlcpy(q, tmpdt, freespace);
1030                                         q += strlen(q);
1031                                 }
1032                                 p++;
1033                                 break;
1034                         case 'n': /* NAS IP address */
1035                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
1036                                 p++;
1037                                 break;
1038                         case 'p': /* Port number */
1039                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
1040                                 p++;
1041                                 break;
1042                         case 's': /* Speed */
1043                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
1044                                 p++;
1045                                 break;
1046                         case 't': /* request timestamp */
1047                                 CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
1048                                 nl = strchr(tmpdt, '\n');
1049                                 if (nl) *nl = '\0';
1050                                 strlcpy(q, tmpdt, freespace);
1051                                 q += strlen(q);
1052                                 p++;
1053                                 break;
1054                         case 'u': /* User name */
1055                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
1056                                 p++;
1057                                 break;
1058                         case 'A': /* radacct_dir */
1059                                 strlcpy(q,radacct_dir,freespace);
1060                                 q += strlen(q);
1061                                 p++;
1062                                 break;
1063                         case 'C': /* ClientName */
1064                                 strlcpy(q,client_name_old(&request->packet->src_ipaddr),freespace);
1065                                 q += strlen(q);
1066                                 p++;
1067                                 break;
1068                         case 'D': /* request date */
1069                                 TM = localtime_r(&request->timestamp, &s_TM);
1070                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
1071                                 if (len > 0) {
1072                                         strlcpy(q, tmpdt, freespace);
1073                                         q += strlen(q);
1074                                 }
1075                                 p++;
1076                                 break;
1077                         case 'H': /* request hour */
1078                                 TM = localtime_r(&request->timestamp, &s_TM);
1079                                 len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
1080                                 if (len > 0) {
1081                                         strlcpy(q, tmpdt, freespace);
1082                                         q += strlen(q);
1083                                 }
1084                                 p++;
1085                                 break;
1086                         case 'L': /* radlog_dir */
1087                                 strlcpy(q,radlog_dir,freespace);
1088                                 q += strlen(q);
1089                                 p++;
1090                                 break;
1091                         case 'M': /* MTU */
1092                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
1093                                 p++;
1094                                 break;
1095                         case 'R': /* radius_dir */
1096                                 strlcpy(q,radius_dir,freespace);
1097                                 q += strlen(q);
1098                                 p++;
1099                                 break;
1100                         case 'S': /* request timestamp in SQL format*/
1101                                 TM = localtime_r(&request->timestamp, &s_TM);
1102                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
1103                                 if (len > 0) {
1104                                         strlcpy(q, tmpdt, freespace);
1105                                         q += strlen(q);
1106                                 }
1107                                 p++;
1108                                 break;
1109                         case 'T': /* request timestamp */
1110                                 TM = localtime_r(&request->timestamp, &s_TM);
1111                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
1112                                 if (len > 0) {
1113                                         strlcpy(q, tmpdt, freespace);
1114                                         q += strlen(q);
1115                                 }
1116                                 p++;
1117                                 break;
1118                         case 'U': /* Stripped User name */
1119                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
1120                                 p++;
1121                                 break;
1122                         case 'V': /* Request-Authenticator */
1123                                 strlcpy(q,"Verified",freespace);
1124                                 q += strlen(q);
1125                                 p++;
1126                                 break;
1127                         case 'Y': /* request year */
1128                                 TM = localtime_r(&request->timestamp, &s_TM);
1129                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
1130                                 if (len > 0) {
1131                                         strlcpy(q, tmpdt, freespace);
1132                                         q += strlen(q);
1133                                 }
1134                                 p++;
1135                                 break;
1136                         case 'Z': /* Full request pairs except password */
1137                                 tmp = request->packet->vps;
1138                                 while (tmp && (freespace > 3)) {
1139                                         if (tmp->attribute != PW_USER_PASSWORD) {
1140                                                 *q++ = '\t';
1141                                                 len = vp_prints(q, freespace - 2, tmp);
1142                                                 q += len;
1143                                                 freespace -= (len + 2);
1144                                                 *q++ = '\n';
1145                                         }
1146                                         tmp = tmp->next;
1147                                 }
1148                                 p++;
1149                                 break;
1150                         default:
1151                                 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
1152                                 if (freespace > 2) {
1153                                         *q++ = '%';
1154                                         *q++ = *p++;
1155                                 }
1156                                 break;
1157                 }
1158         }
1159         *q = '\0';
1160
1161         DEBUG2("\texpand: %s -> %s", fmt, out);
1162
1163         return strlen(out);
1164 }