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