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