control attribute list
[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_SERVER_IDENTITY:
318                                 if (!request->listener || !request->listener->identity) return 0;
319
320                                 snprintf(out, outlen, "%s", request->listener->identity);
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          *      First, copy the xlat key name to one buffer
593          */
594         while (*p && (*p != '}') && (*p != ':')) {
595                 *pa++ = *p++;
596
597                 if (pa >= (xlat_name + sizeof(xlat_name) - 1)) {
598                         /*
599                          *      Skip to the end of the input
600                          */
601                         p += strlen(p);
602                         DEBUG("xlat: Module name is too long in string %%%s",
603                               *from);
604                         goto done;
605                 }
606         }
607         *pa = '\0';
608
609         if (!*p) {
610                 DEBUG("xlat: Invalid syntax in %s", *from);
611
612                 /*
613                  *      %{name} is a simple attribute reference,
614                  *      or regex reference.
615                  */
616         } else if (*p == '}') {
617                 openbraces--;
618                 rad_assert(openbraces == *open_p);
619
620                 p++;
621                 xlat_string = xlat_name;
622                 goto do_xlat;
623
624         } else if (p[1] == '-') { /* handle ':- */
625                 p += 2;
626                 xlat_string = xlat_name;
627                 goto do_xlat;
628
629         } else {      /* module name, followed by per-module string */
630                 int stop = 0;
631                 int delimitbrace = *open_p;
632
633                 rad_assert(*p == ':');
634                 p++;                    /* skip the ':' */
635
636                 /*
637                  *  If there's a brace immediately following the colon,
638                  *  then we've chosen to delimite the per-module string,
639                  *  so keep track of that.
640                  */
641                 if (*p == '{') {
642                         delimitbrace = openbraces;
643                         openbraces++;
644                         p++;
645                 }
646
647                 xlat_string = rad_malloc(strlen(p) + 1); /* always returns */
648                 free_xlat_string = TRUE;
649                 pa = xlat_string;
650
651                 /*
652                  *  Copy over the rest of the string, which is per-module
653                  *  data.
654                  */
655                 while (*p && !stop) {
656                         switch(*p) {
657
658                                 /*
659                                  *      What the heck is this supposed
660                                  *      to be doing?
661                                  */
662                         case '\\':
663                                 p++; /* skip it */
664                                 *pa++ = *p++;
665                                 break;
666
667                         case ':':
668                                 if (!spaces && p[1] == '-') {
669                                         p += 2;
670                                         stop = 1;
671                                         break;
672                                 }
673
674                                 /*
675                                  *      This is pretty hokey...  we
676                                  *      should use the functions in
677                                  *      util.c
678                                  */
679                         case '{':
680                                 openbraces++;
681                                 *pa++ = *p++;
682                                 break;
683
684                         case '}':
685                                 openbraces--;
686                                 if (openbraces == delimitbrace) {
687                                         p++;
688                                         stop=1;
689                                 } else {
690                                         *pa++ = *p++;
691                                 }
692                                 break;
693
694                         case ' ':
695                         case '\t':
696                                 spaces = TRUE;
697                                 /* FALL-THROUGH */
698
699                         default:
700                                 *pa++ = *p++;
701                                 break;
702                         }
703                 }
704
705                 *pa = '\0';
706
707                 /*
708                  *      Now check to see if we're at the end of the string
709                  *      we were sent.  If we're not, check for :-
710                  */
711                 if (openbraces == delimitbrace) {
712                         if (p[0] == ':' && p[1] == '-') {
713                                 p += 2;
714                         }
715                 }
716
717                 /*
718                  *      Look up almost everything in the new tree of xlat
719                  *      functions.  This makes it a little quicker...
720                  */
721         do_xlat:
722                 if ((c = xlat_find(xlat_name)) != NULL) {
723                         if (!c->internal) DEBUG3("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
724                                                 c->module, xlat_string);
725                         retlen = c->do_xlat(c->instance, request, xlat_string,
726                                             q, freespace, func);
727                         /* If retlen is 0, treat it as not found */
728                         if (retlen > 0) found = 1;
729 #ifndef NDEBUG
730                 } else {
731                         /*
732                          *      No attribute by that name, return an error.
733                          */
734                         DEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
735 #endif
736                 }
737         }
738
739         /*
740          * Skip to last '}' if attr is found
741          * The rest of the stuff within the braces is
742          * useless if we found what we need
743          */
744         if (found) {
745                 if (do_length) {
746                         snprintf(q, freespace, "%d", retlen);
747                         retlen = strlen(q);
748                 }
749
750                 q += retlen;
751
752                 while((*p != '\0') && (openbraces > *open_p)) {
753                         /*
754                          *      Handle escapes outside of the loop.
755                          */
756                         if (*p == '\\') {
757                                 p++;
758                                 if (!*p) break;
759                                 p++; /* get & ignore next character */
760                                 continue;
761                         }
762
763                         switch (*p) {
764                         default:
765                                 break;
766
767                                 /*
768                                  *  Bare brace
769                                  */
770                         case '{':
771                                 openbraces++;
772                                 break;
773
774                         case '}':
775                                 openbraces--;
776                                 break;
777                         }
778                         p++;    /* skip the character */
779                 }
780         }
781
782         done:
783         if (free_xlat_string) free(xlat_string);
784
785         *open_p = openbraces;
786         *from = p;
787         *to = q;
788 }
789
790 /*
791  *  If the caller doesn't pass xlat an escape function, then
792  *  we use this one.  It simplifies the coding, as the check for
793  *  func == NULL only happens once.
794  */
795 static int xlat_copy(char *out, int outlen, const char *in)
796 {
797         int freespace = outlen;
798
799         rad_assert(outlen > 0);
800
801         while ((*in) && (freespace > 1)) {
802                 /*
803                  *  Copy data.
804                  *
805                  *  FIXME: Do escaping of bad stuff!
806                  */
807                 *(out++) = *(in++);
808
809                 freespace--;
810         }
811         *out = '\0';
812
813         return (outlen - freespace); /* count does not include NUL */
814 }
815
816 /*
817  *      Replace %<whatever> in a string.
818  *
819  *      See 'doc/variables.txt' for more information.
820  */
821 int radius_xlat(char *out, int outlen, const char *fmt,
822                 REQUEST *request, RADIUS_ESCAPE_STRING func)
823 {
824         int c, len, freespace;
825         const char *p;
826         char *q;
827         char *nl;
828         VALUE_PAIR *tmp;
829         struct tm *TM, s_TM;
830         char tmpdt[40]; /* For temporary storing of dates */
831         int openbraces=0;
832
833         /*
834          *      Catch bad modules.
835          */
836         if (!fmt || !out || !request) return 0;
837
838         /*
839          *  Ensure that we always have an escaping function.
840          */
841         if (func == NULL) {
842                 func = xlat_copy;
843         }
844
845         q = out;
846         p = fmt;
847         while (*p) {
848                 /* Calculate freespace in output */
849                 freespace = outlen - (q - out);
850                 if (freespace <= 1)
851                         break;
852                 c = *p;
853
854                 if ((c != '%') && (c != '$') && (c != '\\')) {
855                         /*
856                          * We check if we're inside an open brace.  If we are
857                          * then we assume this brace is NOT literal, but is
858                          * a closing brace and apply it
859                          */
860                         if ((c == '}') && openbraces) {
861                                 openbraces--;
862                                 p++; /* skip it */
863                                 continue;
864                         }
865                         *q++ = *p++;
866                         continue;
867                 }
868
869                 /*
870                  *      There's nothing after this character, copy
871                  *      the last '%' or "$' or '\\' over to the output
872                  *      buffer, and exit.
873                  */
874                 if (*++p == '\0') {
875                         *q++ = c;
876                         break;
877                 }
878
879                 if (c == '\\') {
880                         switch(*p) {
881                         case '\\':
882                                 *q++ = *p;
883                                 break;
884                         case 't':
885                                 *q++ = '\t';
886                                 break;
887                         case 'n':
888                                 *q++ = '\n';
889                                 break;
890                         default:
891                                 *q++ = c;
892                                 *q++ = *p;
893                                 break;
894                         }
895                         p++;
896
897                 } else if (c == '%') switch(*p) {
898                         case '{':
899                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
900                                 break;
901
902                         case '%':
903                                 *q++ = *p++;
904                                 break;
905                         case 'a': /* Protocol: */
906                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
907                                 p++;
908                                 break;
909                         case 'c': /* Callback-Number */
910                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
911                                 p++;
912                                 break;
913                         case 'd': /* request day */
914                                 TM = localtime_r(&request->timestamp, &s_TM);
915                                 len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
916                                 if (len > 0) {
917                                         strlcpy(q, tmpdt, freespace);
918                                         q += strlen(q);
919                                 }
920                                 p++;
921                                 break;
922                         case 'f': /* Framed IP address */
923                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
924                                 p++;
925                                 break;
926                         case 'i': /* Calling station ID */
927                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
928                                 p++;
929                                 break;
930                         case 'l': /* request timestamp */
931                                 snprintf(tmpdt, sizeof(tmpdt), "%lu",
932                                          (unsigned long) request->received.tv_sec);
933                                 strlcpy(q,tmpdt,freespace);
934                                 q += strlen(q);
935                                 p++;
936                                 break;
937                         case 'm': /* request month */
938                                 TM = localtime_r(&request->timestamp, &s_TM);
939                                 len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
940                                 if (len > 0) {
941                                         strlcpy(q, tmpdt, freespace);
942                                         q += strlen(q);
943                                 }
944                                 p++;
945                                 break;
946                         case 'n': /* NAS IP address */
947                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
948                                 p++;
949                                 break;
950                         case 'p': /* Port number */
951                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
952                                 p++;
953                                 break;
954                         case 's': /* Speed */
955                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
956                                 p++;
957                                 break;
958                         case 't': /* request timestamp */
959                                 CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
960                                 nl = strchr(tmpdt, '\n');
961                                 if (nl) *nl = '\0';
962                                 strlcpy(q, tmpdt, freespace);
963                                 q += strlen(q);
964                                 p++;
965                                 break;
966                         case 'u': /* User name */
967                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
968                                 p++;
969                                 break;
970                         case 'A': /* radacct_dir */
971                                 strlcpy(q,radacct_dir,freespace);
972                                 q += strlen(q);
973                                 p++;
974                                 break;
975                         case 'C': /* ClientName */
976                                 strlcpy(q,client_name_old(&request->packet->src_ipaddr),freespace);
977                                 q += strlen(q);
978                                 p++;
979                                 break;
980                         case 'D': /* request date */
981                                 TM = localtime_r(&request->timestamp, &s_TM);
982                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
983                                 if (len > 0) {
984                                         strlcpy(q, tmpdt, freespace);
985                                         q += strlen(q);
986                                 }
987                                 p++;
988                                 break;
989                         case 'H': /* request hour */
990                                 TM = localtime_r(&request->timestamp, &s_TM);
991                                 len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
992                                 if (len > 0) {
993                                         strlcpy(q, tmpdt, freespace);
994                                         q += strlen(q);
995                                 }
996                                 p++;
997                                 break;
998                         case 'L': /* radlog_dir */
999                                 strlcpy(q,radlog_dir,freespace);
1000                                 q += strlen(q);
1001                                 p++;
1002                                 break;
1003                         case 'M': /* MTU */
1004                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
1005                                 p++;
1006                                 break;
1007                         case 'R': /* radius_dir */
1008                                 strlcpy(q,radius_dir,freespace);
1009                                 q += strlen(q);
1010                                 p++;
1011                                 break;
1012                         case 'S': /* request timestamp in SQL format*/
1013                                 TM = localtime_r(&request->timestamp, &s_TM);
1014                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
1015                                 if (len > 0) {
1016                                         strlcpy(q, tmpdt, freespace);
1017                                         q += strlen(q);
1018                                 }
1019                                 p++;
1020                                 break;
1021                         case 'T': /* request timestamp */
1022                                 TM = localtime_r(&request->timestamp, &s_TM);
1023                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
1024                                 if (len > 0) {
1025                                         strlcpy(q, tmpdt, freespace);
1026                                         q += strlen(q);
1027                                 }
1028                                 p++;
1029                                 break;
1030                         case 'U': /* Stripped User name */
1031                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
1032                                 p++;
1033                                 break;
1034                         case 'V': /* Request-Authenticator */
1035                                 strlcpy(q,"Verified",freespace);
1036                                 q += strlen(q);
1037                                 p++;
1038                                 break;
1039                         case 'Y': /* request year */
1040                                 TM = localtime_r(&request->timestamp, &s_TM);
1041                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
1042                                 if (len > 0) {
1043                                         strlcpy(q, tmpdt, freespace);
1044                                         q += strlen(q);
1045                                 }
1046                                 p++;
1047                                 break;
1048                         case 'Z': /* Full request pairs except password */
1049                                 tmp = request->packet->vps;
1050                                 while (tmp && (freespace > 3)) {
1051                                         if (tmp->attribute != PW_USER_PASSWORD) {
1052                                                 *q++ = '\t';
1053                                                 len = vp_prints(q, freespace - 2, tmp);
1054                                                 q += len;
1055                                                 freespace -= (len + 2);
1056                                                 *q++ = '\n';
1057                                         }
1058                                         tmp = tmp->next;
1059                                 }
1060                                 p++;
1061                                 break;
1062                         default:
1063                                 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
1064                                 if (freespace > 2) {
1065                                         *q++ = '%';
1066                                         *q++ = *p++;
1067                                 }
1068                                 break;
1069                 }
1070         }
1071         *q = '\0';
1072
1073         DEBUG2("\texpand: %s -> %s", fmt, out);
1074
1075         return strlen(out);
1076 }