Pull fix for bug #436 from branch_1_1
[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                         localvp.vp_strvalue[0] = 0;
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.lvalue = 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.lvalue = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
267                                 break;
268                         
269                         case PW_PACKET_SRC_PORT:
270                                 localvp.attribute = da->attr;
271                                 localvp.lvalue = packet->src_port;
272                                 break;
273                         
274                         case PW_PACKET_DST_PORT:
275                                 localvp.attribute = da->attr;
276                                 localvp.lvalue = 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  *      Compare two xlat_t structs, based ONLY on the module name.
380  */
381 static int xlat_cmp(const void *a, const void *b)
382 {
383         if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
384                 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
385         }
386
387         return memcmp(((const xlat_t *)a)->module,
388                       ((const xlat_t *)b)->module,
389                       ((const xlat_t *)a)->length);
390 }
391
392
393 /*
394  *      find the appropriate registered xlat function.
395  */
396 static const xlat_t *xlat_find(const char *module)
397 {
398         xlat_t my_xlat;
399
400         /*
401          *      Look for dictionary attributes first.
402          */
403         if ((dict_attrbyname(module) != NULL) ||
404             (strchr(module, '[') != NULL)) {
405                 static const xlat_t dict_xlat = {
406                         "request",
407                         7,
408                         &xlat_inst[1],
409                         xlat_packet,
410                         TRUE
411                 };
412
413                 return &dict_xlat;
414         }
415
416         strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
417         my_xlat.length = strlen(my_xlat.module);
418
419         return rbtree_finddata(xlat_root, &my_xlat);
420 }
421
422
423 /*
424  *      Register an xlat function.
425  */
426 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
427 {
428         xlat_t  *c;
429         xlat_t  my_xlat;
430
431         if ((module == NULL) || (strlen(module) == 0)) {
432                 DEBUG("xlat_register: Invalid module name");
433                 return -1;
434         }
435
436         /*
437          *      First time around, build up the tree...
438          *
439          *      FIXME: This code should be hoisted out of this function,
440          *      and into a global "initialization".  But it isn't critical...
441          */
442         if (!xlat_root) {
443                 int i;
444 #ifdef HAVE_REGEX_H
445                 char buffer[2];
446 #endif
447
448                 xlat_root = rbtree_create(xlat_cmp, free, 0);
449                 if (!xlat_root) {
450                         DEBUG("xlat_register: Failed to create tree.");
451                         return -1;
452                 }
453
454                 /*
455                  *      Register the internal packet xlat's.
456                  */
457                 for (i = 0; internal_xlat[i] != NULL; i++) {
458                         xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
459                         c = xlat_find(internal_xlat[i]);
460                         rad_assert(c != NULL);
461                         c->internal = TRUE;
462                 }
463
464 #ifdef HAVE_REGEX_H
465                 /*
466                  *      Register xlat's for regexes.
467                  */
468                 buffer[1] = '\0';
469                 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
470                         buffer[0] = '0' + i;
471                         xlat_register(buffer, xlat_regex, &xlat_inst[i]);
472                         c = xlat_find(buffer);
473                         rad_assert(c != NULL);
474                         c->internal = TRUE;
475                 }
476 #endif /* HAVE_REGEX_H */
477         }
478
479         /*
480          *      If it already exists, replace the instance.
481          */
482         strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
483         my_xlat.length = strlen(my_xlat.module);
484         c = rbtree_finddata(xlat_root, &my_xlat);
485         if (c) {
486                 if (c->internal) {
487                         DEBUG("xlat_register: Cannot re-define internal xlat");
488                         return -1;
489                 }
490
491                 c->do_xlat = func;
492                 c->instance = instance;
493                 return 0;
494         }
495
496         /*
497          *      Doesn't exist.  Create it.
498          */
499         c = rad_malloc(sizeof(xlat_t));
500         memset(c, 0, sizeof(*c));
501
502         c->do_xlat = func;
503         strlcpy(c->module, module, sizeof(c->module));
504         c->length = strlen(c->module);
505         c->instance = instance;
506
507         rbtree_insert(xlat_root, c);
508
509         return 0;
510 }
511
512 /*
513  *      Unregister an xlat function.
514  *
515  *      We can only have one function to call per name, so the
516  *      passing of "func" here is extraneous.
517  */
518 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
519 {
520         rbnode_t        *node;
521         xlat_t          my_xlat;
522
523         func = func;            /* -Wunused */
524
525         if (!module) return;
526
527         strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
528         my_xlat.length = strlen(my_xlat.module);
529
530         node = rbtree_find(xlat_root, &my_xlat);
531         if (!node) return;
532
533         rbtree_delete(xlat_root, node);
534 }
535
536 /*
537  *      De-register all xlat functions,
538  *      used mainly for debugging.
539  */
540 void xlat_free(void)
541 {
542         rbtree_free(xlat_root);
543 }
544
545
546 /*
547  *      Decode an attribute name into a string.
548  */
549 static void decode_attribute(const char **from, char **to, int freespace,
550                              int *open, REQUEST *request,
551                              RADIUS_ESCAPE_STRING func)
552 {
553         int     do_length = 0;
554         char    xlat_name[128];
555         char    *xlat_string = NULL; /* can be large */
556         int     free_xlat_string = FALSE;
557         const char *p;
558         char *q, *pa;
559         int found=0, retlen=0;
560         int openbraces = *open;
561         const xlat_t *c;
562
563         p = *from;
564         q = *to;
565         pa = &xlat_name[0];
566
567         *q = '\0';
568
569         /*
570          * Skip the '{' at the front of 'p'
571          * Increment open braces
572          */
573         p++;
574         openbraces++;
575
576         if (*p == '#') {
577                 p++;
578                 do_length = 1;
579         }
580
581         /*
582          *      First, copy the xlat key name to one buffer
583          */
584         while (*p && (*p != '}') && (*p != ':')) {
585                 *pa++ = *p++;
586
587                 if (pa >= (xlat_name + sizeof(xlat_name) - 1)) {
588                         /*
589                          *      Skip to the end of the input
590                          */
591                         p += strlen(p);
592                         DEBUG("xlat: Module name is too long in string %%%s",
593                               *from);
594                         goto done;
595                 }
596         }
597         *pa = '\0';
598
599         if (!*p) {
600                 DEBUG("xlat: Invalid syntax in %s", *from);
601
602                 /*
603                  *      %{name} is a simple attribute reference,
604                  *      or regex reference.
605                  */
606         } else if (*p == '}') {
607                 openbraces--;
608                 rad_assert(openbraces == *open);
609
610                 p++;
611                 xlat_string = xlat_name;
612                 goto do_xlat;
613
614         } else if (p[1] == '-') { /* handle ':- */
615                 p += 2;
616                 xlat_string = xlat_name;
617                 goto do_xlat;
618                 
619         } else {      /* module name, followed by per-module string */
620                 int stop = 0;
621                 int delimitbrace = *open;
622
623                 rad_assert(*p == ':');
624                 p++;                    /* skip the ':' */
625
626                 /*
627                  *  If there's a brace immediately following the colon,
628                  *  then we've chosen to delimite the per-module string,
629                  *  so keep track of that.
630                  */
631                 if (*p == '{') {
632                         delimitbrace = openbraces;
633                         openbraces++;
634                         p++;
635                 }
636                 
637                 xlat_string = rad_malloc(strlen(p) + 1); /* always returns */
638                 free_xlat_string = TRUE;
639                 pa = xlat_string;
640                 
641                 /*
642                  *  Copy over the rest of the string, which is per-module
643                  *  data.
644                  */
645                 while (*p && !stop) {
646                         switch(*p) {
647                                 /*
648                                  *      What the heck is this supposed
649                                  *      to be doing?
650                                  */
651                         case '\\':
652                                 p++; /* skip it */
653                                 *pa++ = *p++;
654                                 break;
655
656                                 /*
657                                  *      This is pretty hokey...  we
658                                  *      should use the functions in
659                                  *      util.c
660                                  */
661                         case '{':
662                                 openbraces++;
663                                 *pa++ = *p++;
664                                 break;
665
666                         case '}':
667                                 openbraces--;
668                                 if (openbraces == delimitbrace) {
669                                         p++;
670                                         stop=1;
671                                 } else {
672                                         *pa++ = *p++;
673                                 }
674                                 break;
675                                 
676                         default:
677                                 *pa++ = *p++;
678                                 break;
679                         }
680                 }
681
682                 *pa = '\0';
683
684                 /*
685                  *      Now check to see if we're at the end of the string
686                  *      we were sent.  If we're not, check for :-
687                  */
688                 if (openbraces == delimitbrace) {
689                         if (p[0] == ':' && p[1] == '-') {
690                                 p += 2;
691                         }
692                 }
693                 
694                 /*
695                  *      Look up almost everything in the new tree of xlat
696                  *      functions.  This makes it a little quicker...
697                  */
698         do_xlat:
699                 if ((c = xlat_find(xlat_name)) != NULL) {
700                         if (!c->internal) DEBUG("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
701                                                 c->module, xlat_string);
702                         retlen = c->do_xlat(c->instance, request, xlat_string,
703                                             q, freespace, func);
704                         /* If retlen is 0, treat it as not found */
705                         if (retlen > 0) found = 1;
706 #ifndef NDEBUG
707                 } else {
708                         /*
709                          *      No attribute by that name, return an error.
710                          */
711                         DEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
712 #endif
713                 }
714         }
715
716         /*
717          * Skip to last '}' if attr is found
718          * The rest of the stuff within the braces is
719          * useless if we found what we need
720          */
721         if (found) {
722                 if (do_length) {
723                         snprintf(q, freespace, "%d", retlen);
724                         retlen = strlen(q);
725                 }
726
727                 q += retlen;
728
729                 while((*p != '\0') && (openbraces > *open)) {
730                         /*
731                          *      Handle escapes outside of the loop.
732                          */
733                         if (*p == '\\') {
734                                 p++;
735                                 if (!*p) break;
736                                 p++; /* get & ignore next character */
737                                 continue;
738                         }
739
740                         switch (*p) {
741                         default:
742                                 break;
743
744                                 /*
745                                  *  Bare brace
746                                  */
747                         case '{':
748                                 openbraces++;
749                                 break;
750
751                         case '}':
752                                 openbraces--;
753                                 break;
754                         }
755                         p++;    /* skip the character */
756                 }
757         }
758         
759         done:
760         if (free_xlat_string) free(xlat_string);
761
762         *open = openbraces;
763         *from = p;
764         *to = q;
765 }
766
767 /*
768  *  If the caller doesn't pass xlat an escape function, then
769  *  we use this one.  It simplifies the coding, as the check for
770  *  func == NULL only happens once.
771  */
772 static int xlat_copy(char *out, int outlen, const char *in)
773 {
774         int freespace = outlen;
775
776         rad_assert(outlen > 0);
777
778         while ((*in) && (freespace > 1)) {
779                 /*
780                  *  Copy data.
781                  *
782                  *  FIXME: Do escaping of bad stuff!
783                  */
784                 *(out++) = *(in++);
785
786                 freespace--;
787         }
788         *out = '\0';
789
790         return (outlen - freespace); /* count does not include NUL */
791 }
792
793 /*
794  *      Replace %<whatever> in a string.
795  *
796  *      See 'doc/variables.txt' for more information.
797  */
798 int radius_xlat(char *out, int outlen, const char *fmt,
799                 REQUEST *request, RADIUS_ESCAPE_STRING func)
800 {
801         int c, len, freespace;
802         const char *p;
803         char *q;
804         char *nl;
805         VALUE_PAIR *tmp;
806         struct tm *TM, s_TM;
807         char tmpdt[40]; /* For temporary storing of dates */
808         int openbraces=0;
809
810         /*
811          *      Catch bad modules.
812          */
813         if (!fmt || !out || !request) return 0;
814
815         /*
816          *  Ensure that we always have an escaping function.
817          */
818         if (func == NULL) {
819                 func = xlat_copy;
820         }
821
822         q = out;
823         p = fmt;
824         while (*p) {
825                 /* Calculate freespace in output */
826                 freespace = outlen - (q - out);
827                 if (freespace <= 1)
828                         break;
829                 c = *p;
830
831                 if ((c != '%') && (c != '$') && (c != '\\')) {
832                         /*
833                          * We check if we're inside an open brace.  If we are
834                          * then we assume this brace is NOT literal, but is
835                          * a closing brace and apply it
836                          */
837                         if ((c == '}') && openbraces) {
838                                 openbraces--;
839                                 p++; /* skip it */
840                                 continue;
841                         }
842                         *q++ = *p++;
843                         continue;
844                 }
845
846                 /*
847                  *      There's nothing after this character, copy
848                  *      the last '%' or "$' or '\\' over to the output
849                  *      buffer, and exit.
850                  */
851                 if (*++p == '\0') {
852                         *q++ = c;
853                         break;
854                 }
855
856                 if (c == '\\') {
857                         switch(*p) {
858                         case '\\':
859                                 *q++ = *p;
860                                 break;
861                         case 't':
862                                 *q++ = '\t';
863                                 break;
864                         case 'n':
865                                 *q++ = '\n';
866                                 break;
867                         default:
868                                 *q++ = c;
869                                 *q++ = *p;
870                                 break;
871                         }
872                         p++;
873
874                         /*
875                          *      Hmmm... ${User-Name} is a synonym for
876                          *      %{User-Name}.
877                          *
878                          *      Why, exactly?
879                          */
880                 } else if (c == '$') switch(*p) {
881                         case '{': /* Attribute by Name */
882                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
883                                 break;
884                         default:
885                                 *q++ = c;
886                                 *q++ = *p++;
887                                 break;
888
889                 } else if (c == '%') switch(*p) {
890                         case '{':
891                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
892                                 break;
893
894                         case '%':
895                                 *q++ = *p++;
896                                 break;
897                         case 'a': /* Protocol: */
898                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
899                                 p++;
900                                 break;
901                         case 'c': /* Callback-Number */
902                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
903                                 p++;
904                                 break;
905                         case 'd': /* request day */
906                                 TM = localtime_r(&request->timestamp, &s_TM);
907                                 len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
908                                 if (len > 0) {
909                                         strlcpy(q, tmpdt, freespace);
910                                         q += strlen(q);
911                                 }
912                                 p++;
913                                 break;
914                         case 'f': /* Framed IP address */
915                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
916                                 p++;
917                                 break;
918                         case 'i': /* Calling station ID */
919                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
920                                 p++;
921                                 break;
922                         case 'l': /* request timestamp */
923                                 snprintf(tmpdt, sizeof(tmpdt), "%lu",
924                                          (unsigned long) request->timestamp);
925                                 strlcpy(q,tmpdt,freespace);
926                                 q += strlen(q);
927                                 p++;
928                                 break;
929                         case 'm': /* request month */
930                                 TM = localtime_r(&request->timestamp, &s_TM);
931                                 len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
932                                 if (len > 0) {
933                                         strlcpy(q, tmpdt, freespace);
934                                         q += strlen(q);
935                                 }
936                                 p++;
937                                 break;
938                         case 'n': /* NAS IP address */
939                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
940                                 p++;
941                                 break;
942                         case 'p': /* Port number */
943                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
944                                 p++;
945                                 break;
946                         case 's': /* Speed */
947                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
948                                 p++;
949                                 break;
950                         case 't': /* request timestamp */
951                                 CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
952                                 nl = strchr(tmpdt, '\n');
953                                 if (nl) *nl = '\0';
954                                 strlcpy(q, tmpdt, freespace);
955                                 q += strlen(q);
956                                 p++;
957                                 break;
958                         case 'u': /* User name */
959                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
960                                 p++;
961                                 break;
962                         case 'A': /* radacct_dir */
963                                 strlcpy(q,radacct_dir,freespace);
964                                 q += strlen(q);
965                                 p++;
966                                 break;
967                         case 'C': /* ClientName */
968                                 strlcpy(q,client_name_old(&request->packet->src_ipaddr),freespace);
969                                 q += strlen(q);
970                                 p++;
971                                 break;
972                         case 'D': /* request date */
973                                 TM = localtime_r(&request->timestamp, &s_TM);
974                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
975                                 if (len > 0) {
976                                         strlcpy(q, tmpdt, freespace);
977                                         q += strlen(q);
978                                 }
979                                 p++;
980                                 break;
981                         case 'H': /* request hour */
982                                 TM = localtime_r(&request->timestamp, &s_TM);
983                                 len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
984                                 if (len > 0) {
985                                         strlcpy(q, tmpdt, freespace);
986                                         q += strlen(q);
987                                 }
988                                 p++;
989                                 break;
990                         case 'L': /* radlog_dir */
991                                 strlcpy(q,radlog_dir,freespace);
992                                 q += strlen(q);
993                                 p++;
994                                 break;
995                         case 'M': /* MTU */
996                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
997                                 p++;
998                                 break;
999                         case 'R': /* radius_dir */
1000                                 strlcpy(q,radius_dir,freespace);
1001                                 q += strlen(q);
1002                                 p++;
1003                                 break;
1004                         case 'S': /* request timestamp in SQL format*/
1005                                 TM = localtime_r(&request->timestamp, &s_TM);
1006                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
1007                                 if (len > 0) {
1008                                         strlcpy(q, tmpdt, freespace);
1009                                         q += strlen(q);
1010                                 }
1011                                 p++;
1012                                 break;
1013                         case 'T': /* request timestamp */
1014                                 TM = localtime_r(&request->timestamp, &s_TM);
1015                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
1016                                 if (len > 0) {
1017                                         strlcpy(q, tmpdt, freespace);
1018                                         q += strlen(q);
1019                                 }
1020                                 p++;
1021                                 break;
1022                         case 'U': /* Stripped User name */
1023                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
1024                                 p++;
1025                                 break;
1026                         case 'V': /* Request-Authenticator */
1027                                 if (request->packet->verified)
1028                                         strlcpy(q,"Verified",freespace);
1029                                 else
1030                                         strlcpy(q,"None",freespace);
1031                                 q += strlen(q);
1032                                 p++;
1033                                 break;
1034                         case 'Y': /* request year */
1035                                 TM = localtime_r(&request->timestamp, &s_TM);
1036                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
1037                                 if (len > 0) {
1038                                         strlcpy(q, tmpdt, freespace);
1039                                         q += strlen(q);
1040                                 }
1041                                 p++;
1042                                 break;
1043                         case 'Z': /* Full request pairs except password */
1044                                 tmp = request->packet->vps;
1045                                 while (tmp && (freespace > 3)) {
1046                                         if (tmp->attribute != PW_USER_PASSWORD) {
1047                                                 *q++ = '\t';
1048                                                 len = vp_prints(q, freespace - 2, tmp);
1049                                                 q += len;
1050                                                 freespace -= (len + 2);
1051                                                 *q++ = '\n';
1052                                         }
1053                                         tmp = tmp->next;
1054                                 }
1055                                 p++;
1056                                 break;
1057                         default:
1058                                 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
1059                                 if (freespace > 2) {
1060                                         *q++ = '%';
1061                                         *q++ = *p++;
1062                                 }
1063                                 break;
1064                 }
1065         }
1066         *q = '\0';
1067
1068         DEBUG2("radius_xlat:  '%s'", out);
1069
1070         return strlen(out);
1071 }