Less code, less work
[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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * Copyright 2000  The FreeRADIUS server project
22  * Copyright 2000  Alan DeKok <aland@ox.org>
23  */
24
25 static const char rcsid[] =
26 "$Id$";
27
28 #include        "autoconf.h"
29 #include        "libradius.h"
30
31 #include        <stdio.h>
32 #include        <stdlib.h>
33 #include        <string.h>
34 #include        <ctype.h>
35
36 #include        "radiusd.h"
37
38 #include        "rad_assert.h"
39
40 typedef struct xlat_t {
41         char            module[MAX_STRING_LEN];
42         int             length;
43         void            *instance;
44         RAD_XLAT_FUNC   do_xlat;
45         int             internal;       /* not allowed to re-define these */
46 } xlat_t;
47
48 static rbtree_t *xlat_root = NULL;
49
50 /*
51  *      Define all xlat's in the structure.
52  */
53 static const char *internal_xlat[] = {"check",
54                                       "request",
55                                       "reply",
56                                       "proxy-request",
57                                       "proxy-reply",
58                                       NULL};
59
60 #if REQUEST_MAX_REGEX > 8
61 #error Please fix the following line
62 #endif
63 static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; /* up to 8 for regex */
64
65
66 /*
67  *      Convert the value on a VALUE_PAIR to string
68  */
69 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
70                          int type, RADIUS_ESCAPE_STRING func)
71 {
72         char buffer[MAX_STRING_LEN * 4];
73
74         if (pair != NULL) {
75                 vp_prints_value(buffer, sizeof(buffer), pair, -1);
76                 return func(out, outlen, buffer);
77         }
78
79         switch (type) {
80         case PW_TYPE_STRING :
81                 strNcpy(out,"_",outlen);
82                 break;
83         case PW_TYPE_INTEGER :
84                 strNcpy(out,"0",outlen);
85                 break;
86         case PW_TYPE_IPADDR :
87                 strNcpy(out,"?.?.?.?",outlen);
88                 break;
89         case PW_TYPE_DATE :
90                 strNcpy(out,"0",outlen);
91                 break;
92         default :
93                 strNcpy(out,"unknown_type",outlen);
94         }
95         return strlen(out);
96 }
97
98
99 /*
100  *      Dynamically translate for check:, request:, reply:, etc.
101  */
102 static int xlat_packet(void *instance, REQUEST *request,
103                        char *fmt, char *out, size_t outlen,
104                        RADIUS_ESCAPE_STRING func)
105 {
106         DICT_ATTR       *da;
107         VALUE_PAIR      *vp;
108         VALUE_PAIR      *vps = NULL;
109         RADIUS_PACKET   *packet = NULL;
110
111         switch (*(int*) instance) {
112         case 0:
113                 vps = request->config_items;
114                 break;
115
116         case 1:
117                 vps = request->packet->vps;
118                 packet = request->packet;
119                 break;
120
121         case 2:
122                 vps = request->reply->vps;
123                 packet = request->reply;
124                 break;
125
126         case 3:
127                 if (request->proxy) vps = request->proxy->vps;
128                 packet = request->proxy;
129                 break;
130
131         case 4:
132                 if (request->proxy_reply) vps = request->proxy_reply->vps;
133                 packet = request->proxy_reply;
134                 break;
135
136         default:                /* WTF? */
137                 return 0;
138         }
139
140         /*
141          *      The "format" string is the attribute name.
142          */
143         da = dict_attrbyname(fmt);
144         if (!da) {
145                 int index;
146                 const char *p = strchr(fmt, '[');
147                 char buffer[256];
148
149                 if (!p) return 0;
150                 if (strlen(fmt) > sizeof(buffer)) return 0;
151
152                 strNcpy(buffer, fmt, p - fmt + 1);
153
154                 da = dict_attrbyname(buffer);
155                 if (!da) return 0;
156
157                 /*
158                  *      %{Attribute-Name[#]} returns the count of
159                  *      attributes of that name in the list.
160                  */
161                 if ((p[1] == '#') && (p[2] == ']')) {
162                         index = 0;
163
164                         for (vp = pairfind(vps, da->attr);
165                              vp != NULL;
166                              vp = pairfind(vp->next, da->attr)) {
167                                 index++;
168                         }
169                         snprintf(out, outlen, "%d", index);
170                         return strlen(out);
171                 }
172
173                 /*
174                  *      %{Attribute-Name[*]} returns ALL of the
175                  *      the attributes, separated by a newline.
176                  */             
177                 if ((p[1] == '*') && (p[2] == ']')) {
178                         int total = 0;
179
180                         for (vp = pairfind(vps, da->attr);
181                              vp != NULL;
182                              vp = pairfind(vp->next, da->attr)) {
183                                 index = valuepair2str(out, outlen - 1, vp, da->type, func);
184                                 rad_assert(index <= outlen);
185                                 total += index + 1;
186                                 outlen -= (index + 1);
187                                 out += index;
188                                 
189                                 *(out++) = '\n';
190
191                                 if (outlen == 0) break;
192                         }
193
194                         return total;
195                 }
196                 
197                 index = atoi(p + 1);
198
199                 /*
200                  *      Skip the numbers.
201                  */
202                 p += 1 + strspn(p + 1, "0123456789");
203                 if (*p != ']') {
204                         DEBUG2("xlat: Invalid array reference in string at %s %s",
205                                fmt, p);
206                         return 0;
207                 }
208
209                 /*
210                  *      Find the N'th value.
211                  */
212                 for (vp = pairfind(vps, da->attr);
213                      vp != NULL;
214                      vp = pairfind(vp->next, da->attr)) {
215                         if (index == 0) break;
216                         index--;
217                 }
218
219                 /*
220                  *      Non-existent array reference.
221                  */
222                 if (!vp) return 0;
223
224                 return valuepair2str(out, outlen, vp, da->type, func);
225         }
226
227         vp = pairfind(vps, da->attr);
228         if (!vp) {
229                 /*
230                  *      Some "magic" handlers, which are never in VP's, but
231                  *      which are in the packet.
232                  */
233                 if (packet) {
234                         VALUE_PAIR localvp;
235
236                         localvp.strvalue[0] = 0;
237
238                         switch (da->attr) {
239                         case PW_PACKET_TYPE:
240                         {
241                                 DICT_VALUE *dval;
242                                 
243                                 dval = dict_valbyattr(da->attr, packet->code);
244                                 if (dval) {
245                                         snprintf(out, outlen, "%s", dval->name);
246                                 } else {
247                                         snprintf(out, outlen, "%d", packet->code);
248                                 }
249                                 return strlen(out);
250                         }
251                         break;
252
253                         case PW_PACKET_SRC_IP_ADDRESS:
254                                 localvp.attribute = da->attr;
255                                 localvp.lvalue = packet->src_ipaddr;
256                                 break;
257                         
258                         case PW_PACKET_DST_IP_ADDRESS:
259                                 localvp.attribute = da->attr;
260                                 localvp.lvalue = packet->dst_ipaddr;
261                                 break;
262                         
263                         case PW_PACKET_SRC_PORT:
264                                 localvp.attribute = da->attr;
265                                 localvp.lvalue = packet->src_port;
266                                 break;
267                         
268                         case PW_PACKET_DST_PORT:
269                                 localvp.attribute = da->attr;
270                                 localvp.lvalue = packet->dst_port;
271                                 break;
272
273                         case PW_PACKET_AUTHENTICATION_VECTOR:
274                                 localvp.attribute = da->attr;
275                                 memcpy(localvp.strvalue, packet->vector,
276                                        sizeof(packet->vector));
277                                 localvp.length = sizeof(packet->vector);
278                                 break;
279
280                                 /*
281                                  *      Authorization, accounting, etc.
282                                  */
283                         case PW_REQUEST_PROCESSING_STAGE:
284                                 if (request->component) {
285                                         strNcpy(out, request->component, outlen);
286                                 } else {
287                                         strNcpy(out, "server_core", outlen);
288                                 }
289                                 return strlen(out);
290                         
291                         default:
292                                 return 0; /* not found */
293                                 break;
294                         }
295
296                         localvp.type = da->type;
297                         return valuepair2str(out, outlen, &localvp,
298                                              da->type, func);
299                 }
300
301                 /*
302                  *      Not found, die.
303                  */
304                 return 0;
305         }
306
307         if (!vps) return 0;     /* silently fail */
308
309         /*
310          *      Convert the VP to a string, and return it.
311          */
312         return valuepair2str(out, outlen, vp, da->type, func);
313 }
314
315 #ifdef HAVE_REGEX_H
316 /*
317  *      Pull %{0} to %{8} out of the packet.
318  */
319 static int xlat_regex(void *instance, REQUEST *request,
320                       char *fmt, char *out, size_t outlen,
321                       RADIUS_ESCAPE_STRING func)
322 {
323         char *regex;
324
325         /*
326          *      We cheat: fmt is "0" to "8", but those numbers
327          *      are already in the "instance".
328          */
329         fmt = fmt;              /* -Wunused */
330         func = func;            /* -Wunused FIXME: do escaping? */
331         
332         regex = request_data_get(request, request,
333                                  REQUEST_DATA_REGEX | *(int *)instance);
334         if (!regex) return 0;
335
336         /*
337          *      Copy UP TO "freespace" bytes, including
338          *      a zero byte.
339          */
340         strNcpy(out, regex, outlen);
341         free(regex); /* was strdup'd */
342         return strlen(out);
343 }
344 #endif                          /* HAVE_REGEX_H */
345
346 /*
347  *      Compare two xlat_t structs, based ONLY on the module name.
348  */
349 static int xlat_cmp(const void *a, const void *b)
350 {
351         if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
352                 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
353         }
354
355         return memcmp(((const xlat_t *)a)->module,
356                       ((const xlat_t *)b)->module,
357                       ((const xlat_t *)a)->length);
358 }
359
360
361 /*
362  *      find the appropriate registered xlat function.
363  */
364 static xlat_t *xlat_find(const char *module)
365 {
366         char *p;
367         xlat_t my_xlat;
368
369         p = my_xlat.module;
370         my_xlat.length = 0;
371         while (*module && (*module != ':')) {
372                 *(p++) = *(module++);
373                 my_xlat.length++;
374         }
375         *p = '\0';
376
377         return rbtree_finddata(xlat_root, &my_xlat);
378 }
379
380
381 /*
382  *      Register an xlat function.
383  */
384 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
385 {
386         xlat_t  *c;
387         xlat_t  my_xlat;
388
389         if ((module == NULL) || (strlen(module) == 0)) {
390                 DEBUG("xlat_register: Invalid module name");
391                 return -1;
392         }
393
394         /*
395          *      First time around, build up the tree...
396          *
397          *      FIXME: This code should be hoisted out of this function,
398          *      and into a global "initialization".  But it isn't critical...
399          */
400         if (!xlat_root) {
401                 int i;
402 #ifdef HAVE_REGEX_H
403                 char buffer[2];
404 #endif
405
406                 xlat_root = rbtree_create(xlat_cmp, free, 0);
407                 if (!xlat_root) {
408                         DEBUG("xlat_register: Failed to create tree.");
409                         return -1;
410                 }
411
412                 /*
413                  *      Register the internal packet xlat's.
414                  */
415                 for (i = 0; internal_xlat[i] != NULL; i++) {
416                         xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
417                         c = xlat_find(internal_xlat[i]);
418                         rad_assert(c != NULL);
419                         c->internal = TRUE;
420                 }
421
422 #ifdef HAVE_REGEX_H
423                 /*
424                  *      Register xlat's for regexes.
425                  */
426                 buffer[1] = '\0';
427                 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
428                         buffer[0] = '0' + i;
429                         xlat_register(buffer, xlat_regex, &xlat_inst[i]);
430                         c = xlat_find(buffer);
431                         rad_assert(c != NULL);
432                         c->internal = TRUE;
433                 }
434 #endif /* HAVE_REGEX_H */
435         }
436
437         /*
438          *      If it already exists, replace the instance.
439          */
440         strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
441         my_xlat.length = strlen(my_xlat.module);
442         c = rbtree_finddata(xlat_root, &my_xlat);
443         if (c) {
444                 if (c->internal) {
445                         DEBUG("xlat_register: Cannot re-define internal xlat");
446                         return -1;
447                 }
448
449                 c->do_xlat = func;
450                 c->instance = instance;
451                 return 0;
452         }
453
454         /*
455          *      Doesn't exist.  Create it.
456          */
457         c = rad_malloc(sizeof(xlat_t));
458         memset(c, 0, sizeof(*c));
459
460         c->do_xlat = func;
461         strNcpy(c->module, module, sizeof(c->module));
462         c->length = strlen(c->module);
463         c->instance = instance;
464
465         rbtree_insert(xlat_root, c);
466
467         return 0;
468 }
469
470 /*
471  *      Unregister an xlat function.
472  *
473  *      We can only have one function to call per name, so the
474  *      passing of "func" here is extraneous.
475  */
476 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
477 {
478         rbnode_t        *node;
479         xlat_t          my_xlat;
480
481         func = func;            /* -Wunused */
482
483         strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
484         my_xlat.length = strlen(my_xlat.module);
485
486         node = rbtree_find(xlat_root, &my_xlat);
487         if (!node) return;
488
489         rbtree_delete(xlat_root, node);
490 }
491
492 /*
493  *      De-register all xlat functions,
494  *      used mainly for debugging.
495  */
496 void xlat_free(void)
497 {
498         rbtree_free(xlat_root);
499 }
500
501
502 /*
503  *      Decode an attribute name into a string.
504  */
505 static void decode_attribute(const char **from, char **to, int freespace,
506                              int *open, REQUEST *request,
507                              RADIUS_ESCAPE_STRING func)
508 {
509         int     do_length = 0;
510         char attrname[256];
511         const char *p;
512         char *q, *pa;
513         int stop=0, found=0, retlen=0;
514         int openbraces = *open;
515         xlat_t *c;
516
517         p = *from;
518         q = *to;
519         pa = &attrname[0];
520
521         *q = '\0';
522
523         /*
524          * Skip the '{' at the front of 'p'
525          * Increment open braces
526          */
527         p++;
528         openbraces++;
529
530         if (*p == '#') {
531                 p++;
532                 do_length = 1;
533         }
534
535         /*
536          *  Copy over the rest of the string.
537          */
538         while ((*p) && (!stop)) {
539                 switch(*p) {
540                         /*
541                          *  Allow braces inside things, too.
542                          */
543                         case '\\':
544                                 p++; /* skip it */
545                                 *pa++ = *p++;
546                                 break;
547
548                         case '{':
549                                 openbraces++;
550                                 *pa++ = *p++;
551                                 break;
552
553                         case '}':
554                                 openbraces--;
555                                 if (openbraces == *open) {
556                                         p++;
557                                         stop=1;
558                                 } else {
559                                         *pa++ = *p++;
560                                 }
561                                 break;
562
563                                 /*
564                                  *  Attr-Name1:-Attr-Name2
565                                  *
566                                  *  Use Attr-Name1, and if not found,
567                                  *  use Attr-Name2.
568                                  */
569                         case ':':
570                                 if (p[1] == '-') {
571                                         p += 2;
572                                         stop = 1;
573                                         break;
574                                 }
575                                 /* else FALL-THROUGH */
576
577                         default:
578                                 *pa++ = *p++;
579                                 break;
580                 }
581         }
582         *pa = '\0';
583
584         /*
585          *      Look up almost everything in the new tree of xlat
586          *      functions.  this makes it a little quicker...
587          */
588         if ((c = xlat_find(attrname)) != NULL) {
589                 if (!c->internal) DEBUG("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
590                                         c->module, attrname+ c->length + 1);
591                 retlen = c->do_xlat(c->instance, request, attrname+(c->length+1), q, freespace, func);
592                 /* If retlen is 0, treat it as not found */
593                 if (retlen > 0) found = 1;
594
595                 /*
596                  *      Not in the default xlat database.  Must be
597                  *      a bare attribute number.
598                  */
599         } else if ((retlen = xlat_packet(&xlat_inst[1], request, attrname,
600                                          q, freespace, func)) > 0) {
601                 found = 1;
602
603                 /*
604                  *      Look up the name, in order to get the correct
605                  *      debug message.
606                  */
607 #ifndef NDEBUG
608         } else if (dict_attrbyname(attrname) == NULL) {
609                 /*
610                  *      No attribute by that name, return an error.
611                  */
612                 DEBUG2("WARNING: Attempt to use unknown xlat function, or non-existent attribute in string %%{%s}", attrname);
613 #endif
614         } /* else the attribute is known, but not in the request */
615
616         /*
617          * Skip to last '}' if attr is found
618          * The rest of the stuff within the braces is
619          * useless if we found what we need
620          */
621         if (found) {
622                 if (do_length) {
623                         snprintf(q, freespace, "%d", retlen);
624                         retlen = strlen(q);
625                 }
626
627                 q += retlen;
628
629                 while((*p != '\0') && (openbraces > 0)) {
630                         /*
631                          *      Handle escapes outside of the loop.
632                          */
633                         if (*p == '\\') {
634                                 p++;
635                                 if (!*p) break;
636                                 p++; /* get & ignore next character */
637                                 continue;
638                         }
639
640                         switch (*p) {
641                         default:
642                                 break;
643
644                                 /*
645                                  *  Bare brace
646                                  */
647                         case '{':
648                                 openbraces++;
649                                 break;
650
651                         case '}':
652                                 openbraces--;
653                                 break;
654                         }
655                         p++;    /* skip the character */
656                 }
657         }
658
659         *open = openbraces;
660         *from = p;
661         *to = q;
662 }
663
664 /*
665  *  If the caller doesn't pass xlat an escape function, then
666  *  we use this one.  It simplifies the coding, as the check for
667  *  func == NULL only happens once.
668  */
669 static int xlat_copy(char *out, int outlen, const char *in)
670 {
671         int len = 0;
672
673         while (*in) {
674                 /*
675                  *  Truncate, if too much.
676                  */
677                 if (len >= outlen) {
678                         break;
679                 }
680
681                 /*
682                  *  Copy data.
683                  *
684                  *  FIXME: Do escaping of bad stuff!
685                  */
686                 *out = *in;
687
688                 out++;
689                 in++;
690                 len++;
691         }
692
693         *out = '\0';
694         return len;
695 }
696
697 /*
698  *      Replace %<whatever> in a string.
699  *
700  *      See 'doc/variables.txt' for more information.
701  */
702 int radius_xlat(char *out, int outlen, const char *fmt,
703                 REQUEST *request, RADIUS_ESCAPE_STRING func)
704 {
705         int i, c,freespace;
706         const char *p;
707         char *q;
708         VALUE_PAIR *tmp;
709         struct tm *TM, s_TM;
710         char tmpdt[40]; /* For temporary storing of dates */
711         int openbraces=0;
712
713         /*
714          *      Catch bad modules.
715          */
716         if (!fmt || !out || !request) return 0;
717
718         /*
719          *  Ensure that we always have an escaping function.
720          */
721         if (func == NULL) {
722                 func = xlat_copy;
723         }
724
725         q = out;
726         p = fmt;
727         while (*p) {
728                 /* Calculate freespace in output */
729                 freespace = outlen - (q - out);
730                 if (freespace <= 1)
731                         break;
732                 c = *p;
733
734                 if ((c != '%') && (c != '$') && (c != '\\')) {
735                         /*
736                          * We check if we're inside an open brace.  If we are
737                          * then we assume this brace is NOT literal, but is
738                          * a closing brace and apply it
739                          */
740                         if ((c == '}') && openbraces) {
741                                 openbraces--;
742                                 p++; /* skip it */
743                                 continue;
744                         }
745                         *q++ = *p++;
746                         continue;
747                 }
748
749                 /*
750                  *      There's nothing after this character, copy
751                  *      the last '%' or "$' or '\\' over to the output
752                  *      buffer, and exit.
753                  */
754                 if (*++p == '\0') {
755                         *q++ = c;
756                         break;
757                 }
758
759                 if (c == '\\') {
760                         switch(*p) {
761                         case '\\':
762                                 *q++ = *p;
763                                 break;
764                         case 't':
765                                 *q++ = '\t';
766                                 break;
767                         case 'n':
768                                 *q++ = '\n';
769                                 break;
770                         default:
771                                 *q++ = c;
772                                 *q++ = *p;
773                                 break;
774                         }
775                         p++;
776
777                         /*
778                          *      Hmmm... ${User-Name} is a synonym for
779                          *      %{User-Name}.
780                          *
781                          *      Why, exactly?
782                          */
783                 } else if (c == '$') switch(*p) {
784                         case '{': /* Attribute by Name */
785                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
786                                 break;
787                         default:
788                                 *q++ = c;
789                                 *q++ = *p++;
790                                 break;
791
792                 } else if (c == '%') switch(*p) {
793                         case '{':
794                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
795                                 break;
796
797                         case '%':
798                                 *q++ = *p++;
799                                 break;
800                         case 'a': /* Protocol: */
801                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
802                                 p++;
803                                 break;
804                         case 'c': /* Callback-Number */
805                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
806                                 p++;
807                                 break;
808                         case 'd': /* request day */
809                                 TM = localtime_r(&request->timestamp, &s_TM);
810                                 strftime(tmpdt,sizeof(tmpdt),"%d",TM);
811                                 strNcpy(q,tmpdt,freespace);
812                                 q += strlen(q);
813                                 p++;
814                                 break;
815                         case 'f': /* Framed IP address */
816                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
817                                 p++;
818                                 break;
819                         case 'i': /* Calling station ID */
820                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
821                                 p++;
822                                 break;
823                         case 'l': /* request timestamp */
824                                 snprintf(tmpdt, sizeof(tmpdt), "%lu",
825                                          (unsigned long) request->timestamp);
826                                 strNcpy(q,tmpdt,freespace);
827                                 q += strlen(q);
828                                 p++;
829                                 break;
830                         case 'm': /* request month */
831                                 TM = localtime_r(&request->timestamp, &s_TM);
832                                 strftime(tmpdt,sizeof(tmpdt),"%m",TM);
833                                 strNcpy(q,tmpdt,freespace);
834                                 q += strlen(q);
835                                 p++;
836                                 break;
837                         case 'n': /* NAS IP address */
838                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
839                                 p++;
840                                 break;
841                         case 'p': /* Port number */
842                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
843                                 p++;
844                                 break;
845                         case 's': /* Speed */
846                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
847                                 p++;
848                                 break;
849                         case 't': /* request timestamp */
850                                 CTIME_R(&request->timestamp, q, freespace);
851                                 q += strlen(q);
852                                 p++;
853                                 break;
854                         case 'u': /* User name */
855                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
856                                 p++;
857                                 break;
858                         case 'A': /* radacct_dir */
859                                 strNcpy(q,radacct_dir,freespace-1);
860                                 q += strlen(q);
861                                 p++;
862                                 break;
863                         case 'C': /* ClientName */
864                                 strNcpy(q,client_name(request->packet->src_ipaddr),freespace-1);
865                                 q += strlen(q);
866                                 p++;
867                                 break;
868                         case 'D': /* request date */
869                                 TM = localtime_r(&request->timestamp, &s_TM);
870                                 strftime(tmpdt,sizeof(tmpdt),"%Y%m%d",TM);
871                                 strNcpy(q,tmpdt,freespace);
872                                 q += strlen(q);
873                                 p++;
874                                 break;
875                         case 'H': /* request hour */
876                                 TM = localtime_r(&request->timestamp, &s_TM);
877                                 strftime(tmpdt,sizeof(tmpdt),"%H",TM);
878                                 strNcpy(q,tmpdt,freespace);
879                                 q += strlen(q);
880                                 p++;
881                                 break;
882                         case 'L': /* radlog_dir */
883                                 strNcpy(q,radlog_dir,freespace-1);
884                                 q += strlen(q);
885                                 p++;
886                                 break;
887                         case 'M': /* MTU */
888                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
889                                 p++;
890                                 break;
891                         case 'R': /* radius_dir */
892                                 strNcpy(q,radius_dir,freespace-1);
893                                 q += strlen(q);
894                                 p++;
895                                 break;
896                         case 'S': /* request timestamp in SQL format*/
897                                 TM = localtime_r(&request->timestamp, &s_TM);
898                                 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d %H:%M:%S",TM);
899                                 strNcpy(q,tmpdt,freespace);
900                                 q += strlen(q);
901                                 p++;
902                                 break;
903                         case 'T': /* request timestamp */
904                                 TM = localtime_r(&request->timestamp, &s_TM);
905                                 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d-%H.%M.%S.000000",TM);
906                                 strNcpy(q,tmpdt,freespace);
907                                 q += strlen(q);
908                                 p++;
909                                 break;
910                         case 'U': /* Stripped User name */
911                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
912                                 p++;
913                                 break;
914                         case 'V': /* Request-Authenticator */
915                                 if (request->packet->verified)
916                                         strNcpy(q,"Verified",freespace-1);
917                                 else
918                                         strNcpy(q,"None",freespace-1);
919                                 q += strlen(q);
920                                 p++;
921                                 break;
922                         case 'Y': /* request year */
923                                 TM = localtime_r(&request->timestamp, &s_TM);
924                                 strftime(tmpdt,sizeof(tmpdt),"%Y",TM);
925                                 strNcpy(q,tmpdt,freespace);
926                                 q += strlen(q);
927                                 p++;
928                                 break;
929                         case 'Z': /* Full request pairs except password */
930                                 tmp = request->packet->vps;
931                                 while (tmp && (freespace > 3)) {
932                                         if (tmp->attribute != PW_PASSWORD) {
933                                                 *q++ = '\t';
934                                                 i = vp_prints(q,freespace-2,tmp);
935                                                 q += i;
936                                                 freespace -= (i+2);
937                                                 *q++ = '\n';
938                                         }
939                                         tmp = tmp->next;
940                                 }
941                                 p++;
942                                 break;
943                         default:
944                                 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
945                                 if (freespace > 2) {
946                                         *q++ = '%';
947                                         *q++ = *p++;
948                                 }
949                                 break;
950                 }
951         }
952         *q = '\0';
953
954         DEBUG2("radius_xlat:  '%s'", out);
955
956         return strlen(out);
957 }