Added support for %{Attribute-Name[*]}
[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 static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; /* up to 8 for regex */
60
61
62 /*
63  *      Convert the value on a VALUE_PAIR to string
64  */
65 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
66                          int type, RADIUS_ESCAPE_STRING func)
67 {
68         char buffer[MAX_STRING_LEN * 4];
69
70         if (pair != NULL) {
71                 vp_prints_value(buffer, sizeof(buffer), pair, 0);
72                 return func(out, outlen, buffer);
73         }
74
75         switch (type) {
76         case PW_TYPE_STRING :
77                 strNcpy(out,"_",outlen);
78                 break;
79         case PW_TYPE_INTEGER :
80                 strNcpy(out,"0",outlen);
81                 break;
82         case PW_TYPE_IPADDR :
83                 strNcpy(out,"?.?.?.?",outlen);
84                 break;
85         case PW_TYPE_DATE :
86                 strNcpy(out,"0",outlen);
87                 break;
88         default :
89                 strNcpy(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 index;
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                 strNcpy(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                         index = 0;
159
160                         for (vp = pairfind(vps, da->attr);
161                              vp != NULL;
162                              vp = pairfind(vp->next, da->attr)) {
163                                 index++;
164                         }
165                         snprintf(out, outlen, "%d", index);
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                                 index = valuepair2str(out, outlen - 1, vp, da->type, func);
180                                 rad_assert(index <= outlen);
181                                 total += index + 1;
182                                 outlen -= (index + 1);
183                                 out += index;
184                                 
185                                 *(out++) = '\n';
186
187                                 if (outlen == 0) break;
188                         }
189
190                         return total;
191                 }
192                 
193                 index = 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 (index == 0) break;
212                         index--;
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: Add SRC/DST IP address!
230                  */
231                 if (packet) {
232                         switch (da->attr) {
233                         case PW_PACKET_TYPE:
234                         {
235                                 DICT_VALUE *dval;
236                                 
237                                 dval = dict_valbyattr(da->attr, packet->code);
238                                 if (dval) {
239                                         snprintf(out, outlen, "%s", dval->name);
240                                 } else {
241                                         snprintf(out, outlen, "%d", packet->code);
242                                 }
243                                 return strlen(out);
244                         }
245                         break;
246                         
247                         default:
248                                 break;
249                         }
250                 }
251
252                 /*
253                  *      Not found, die.
254                  */
255                 return 0;
256         }
257
258         if (!vps) return 0;     /* silently fail */
259
260         /*
261          *      Convert the VP to a string, and return it.
262          */
263         return valuepair2str(out, outlen, vp, da->type, func);
264 }
265
266 #ifdef HAVE_REGEX_H
267 /*
268  *      Pull %{0} to %{8} out of the packet.
269  */
270 static int xlat_regex(void *instance, REQUEST *request,
271                       char *fmt, char *out, size_t outlen,
272                       RADIUS_ESCAPE_STRING func)
273 {
274         char *regex;
275
276         /*
277          *      We cheat: fmt is "0" to "8", but those numbers
278          *      are already in the "instance".
279          */
280         fmt = fmt;              /* -Wunused */
281         func = func;            /* -Wunused FIXME: do escaping? */
282         
283         regex = request_data_get(request, request,
284                                  REQUEST_DATA_REGEX | *(int *)instance);
285         if (!regex) return 0;
286
287         /*
288          *      Copy UP TO "freespace" bytes, including
289          *      a zero byte.
290          */
291         strNcpy(out, regex, outlen);
292         free(regex); /* was strdup'd */
293         return strlen(out);
294 }
295 #endif                          /* HAVE_REGEX_H */
296
297 /*
298  *      Compare two xlat_t structs, based ONLY on the module name.
299  */
300 static int xlat_cmp(const void *a, const void *b)
301 {
302         if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
303                 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
304         }
305
306         return memcmp(((const xlat_t *)a)->module,
307                       ((const xlat_t *)b)->module,
308                       ((const xlat_t *)a)->length);
309 }
310
311
312 /*
313  *      find the appropriate registered xlat function.
314  */
315 static xlat_t *xlat_find(const char *module)
316 {
317         char *p;
318         xlat_t my_xlat;
319
320         strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
321
322         /*
323          *      We get passed the WHOLE string, and all we want here
324          *      is the first piece.
325          */
326         p = strchr(my_xlat.module, ':');
327         if (p) *p = '\0';
328
329         my_xlat.length = strlen(my_xlat.module);
330
331         return rbtree_finddata(xlat_root, &my_xlat);
332 }
333
334
335 /*
336  *      Register an xlat function.
337  */
338 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
339 {
340         xlat_t  *c;
341         xlat_t  my_xlat;
342
343         if ((module == NULL) || (strlen(module) == 0)) {
344                 DEBUG("xlat_register: Invalid module name");
345                 return -1;
346         }
347
348         /*
349          *      First time around, build up the tree...
350          *
351          *      FIXME: This code should be hoisted out of this function,
352          *      and into a global "initialization".  But it isn't critical...
353          */
354         if (!xlat_root) {
355                 int i;
356 #ifdef HAVE_REGEX_H
357                 char buffer[2];
358 #endif
359
360                 xlat_root = rbtree_create(xlat_cmp, free, 0);
361                 if (!xlat_root) {
362                         DEBUG("xlat_register: Failed to create tree.");
363                         return -1;
364                 }
365
366                 /*
367                  *      Register the internal packet xlat's.
368                  */
369                 for (i = 0; internal_xlat[i] != NULL; i++) {
370                         xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
371                         c = xlat_find(internal_xlat[i]);
372                         rad_assert(c != NULL);
373                         c->internal = TRUE;
374                 }
375
376 #ifdef HAVE_REGEX_H
377                 /*
378                  *      Register xlat's for regexes.
379                  */
380                 buffer[1] = '\0';
381                 for (i = 0; i <= 8; i++) {
382                         buffer[0] = '0' + i;
383                         xlat_register(buffer, xlat_regex, &xlat_inst[i]);
384                         c = xlat_find(buffer);
385                         rad_assert(c != NULL);
386                         c->internal = TRUE;
387                 }
388 #endif /* HAVE_REGEX_H */
389         }
390
391         /*
392          *      If it already exists, replace the instance.
393          */
394         strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
395         my_xlat.length = strlen(my_xlat.module);
396         c = rbtree_finddata(xlat_root, &my_xlat);
397         if (c) {
398                 if (c->internal) {
399                         DEBUG("xlat_register: Cannot re-define internal xlat");
400                         return -1;
401                 }
402
403                 c->do_xlat = func;
404                 c->instance = instance;
405                 return 0;
406         }
407
408         /*
409          *      Doesn't exist.  Create it.
410          */
411         c = rad_malloc(sizeof(xlat_t));
412         memset(c, 0, sizeof(*c));
413
414         c->do_xlat = func;
415         strNcpy(c->module, module, sizeof(c->module));
416         c->length = strlen(c->module);
417         c->instance = instance;
418
419         rbtree_insert(xlat_root, c);
420
421         return 0;
422 }
423
424 /*
425  *      Unregister an xlat function.
426  *
427  *      We can only have one function to call per name, so the
428  *      passing of "func" here is extraneous.
429  */
430 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
431 {
432         rbnode_t        *node;
433         xlat_t          my_xlat;
434
435         func = func;            /* -Wunused */
436
437         strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
438         my_xlat.length = strlen(my_xlat.module);
439
440         node = rbtree_find(xlat_root, &my_xlat);
441         if (!node) return;
442
443         rbtree_delete(xlat_root, node);
444 }
445
446
447 /*
448  *      Decode an attribute name into a string.
449  */
450 static void decode_attribute(const char **from, char **to, int freespace,
451                              int *open, REQUEST *request,
452                              RADIUS_ESCAPE_STRING func)
453 {
454         int     do_length = 0;
455         char attrname[256];
456         const char *p;
457         char *q, *pa;
458         int stop=0, found=0, retlen=0;
459         int openbraces = *open;
460         xlat_t *c;
461
462         p = *from;
463         q = *to;
464         pa = &attrname[0];
465
466         *q = '\0';
467
468         /*
469          * Skip the '{' at the front of 'p'
470          * Increment open braces
471          */
472         p++;
473         openbraces++;
474
475         if (*p == '#') {
476                 p++;
477                 do_length = 1;
478         }
479
480         /*
481          *  Copy over the rest of the string.
482          */
483         while ((*p) && (!stop)) {
484                 switch(*p) {
485                         /*
486                          *  Allow braces inside things, too.
487                          */
488                         case '\\':
489                                 p++; /* skip it */
490                                 *pa++ = *p++;
491                                 break;
492
493                         case '{':
494                                 openbraces++;
495                                 *pa++ = *p++;
496                                 break;
497
498                         case '}':
499                                 openbraces--;
500                                 if (openbraces == *open) {
501                                         p++;
502                                         stop=1;
503                                 } else {
504                                         *pa++ = *p++;
505                                 }
506                                 break;
507
508                                 /*
509                                  *  Attr-Name1:-Attr-Name2
510                                  *
511                                  *  Use Attr-Name1, and if not found,
512                                  *  use Attr-Name2.
513                                  */
514                         case ':':
515                                 if (p[1] == '-') {
516                                         p += 2;
517                                         stop = 1;
518                                         break;
519                                 }
520                                 /* else FALL-THROUGH */
521
522                         default:
523                                 *pa++ = *p++;
524                                 break;
525                 }
526         }
527         *pa = '\0';
528
529         /*
530          *      Look up almost everything in the new tree of xlat
531          *      functions.  this makes it a little quicker...
532          */
533         if ((c = xlat_find(attrname)) != NULL) {
534                 if (!c->internal) DEBUG("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
535                                         c->module, attrname+ c->length + 1);
536                 retlen = c->do_xlat(c->instance, request, attrname+(c->length+1), q, freespace, func);
537                 /* If retlen is 0, treat it as not found */
538                 if (retlen > 0) found = 1;
539
540                 /*
541                  *      Not in the default xlat database.  Must be
542                  *      a bare attribute number.
543                  */
544         } else if ((retlen = xlat_packet(&xlat_inst[1], request, attrname,
545                                          q, freespace, func)) > 0) {
546                 found = 1;
547
548                 /*
549                  *      Look up the name, in order to get the correct
550                  *      debug message.
551                  */
552 #ifndef NDEBUG
553         } else if (dict_attrbyname(attrname) == NULL) {
554                 /*
555                  *      No attribute by that name, return an error.
556                  */
557                 DEBUG2("WARNING: Attempt to use unknown xlat function, or non-existent attribute in string %%{%s}", attrname);
558 #endif
559         } /* else the attribute is known, but not in the request */
560
561         /*
562          * Skip to last '}' if attr is found
563          * The rest of the stuff within the braces is
564          * useless if we found what we need
565          */
566         if (found) {
567                 if (do_length) {
568                         snprintf(q, freespace, "%d", retlen);
569                         retlen = strlen(q);
570                 }
571
572                 q += retlen;
573
574                 while((*p != '\0') && (openbraces > 0)) {
575                         /*
576                          *      Handle escapes outside of the loop.
577                          */
578                         if (*p == '\\') {
579                                 p++;
580                                 if (!*p) break;
581                                 p++; /* get & ignore next character */
582                                 continue;
583                         }
584
585                         switch (*p) {
586                         default:
587                                 break;
588
589                                 /*
590                                  *  Bare brace
591                                  */
592                         case '{':
593                                 openbraces++;
594                                 break;
595
596                         case '}':
597                                 openbraces--;
598                                 break;
599                         }
600                         p++;    /* skip the character */
601                 }
602         }
603
604         *open = openbraces;
605         *from = p;
606         *to = q;
607 }
608
609 /*
610  *  If the caller doesn't pass xlat an escape function, then
611  *  we use this one.  It simplifies the coding, as the check for
612  *  func == NULL only happens once.
613  */
614 static int xlat_copy(char *out, int outlen, const char *in)
615 {
616         int len = 0;
617
618         while (*in) {
619                 /*
620                  *  Truncate, if too much.
621                  */
622                 if (len >= outlen) {
623                         break;
624                 }
625
626                 /*
627                  *  Copy data.
628                  *
629                  *  FIXME: Do escaping of bad stuff!
630                  */
631                 *out = *in;
632
633                 out++;
634                 in++;
635                 len++;
636         }
637
638         *out = '\0';
639         return len;
640 }
641
642 /*
643  *      Replace %<whatever> in a string.
644  *
645  *      See 'doc/variables.txt' for more information.
646  */
647 int radius_xlat(char *out, int outlen, const char *fmt,
648                 REQUEST *request, RADIUS_ESCAPE_STRING func)
649 {
650         int i, c,freespace;
651         const char *p;
652         char *q;
653         VALUE_PAIR *tmp;
654         struct tm *TM, s_TM;
655         char tmpdt[40]; /* For temporary storing of dates */
656         int openbraces=0;
657
658         /*
659          *  Ensure that we always have an escaping function.
660          */
661         if (func == NULL) {
662                 func = xlat_copy;
663         }
664
665         q = out;
666         p = fmt;
667         while (*p) {
668                 /* Calculate freespace in output */
669                 freespace = outlen - (q - out);
670                 if (freespace <= 1)
671                         break;
672                 c = *p;
673
674                 if ((c != '%') && (c != '$') && (c != '\\')) {
675                         /*
676                          * We check if we're inside an open brace.  If we are
677                          * then we assume this brace is NOT literal, but is
678                          * a closing brace and apply it
679                          */
680                         if ((c == '}') && openbraces) {
681                                 openbraces--;
682                                 p++; /* skip it */
683                                 continue;
684                         }
685                         *q++ = *p++;
686                         continue;
687                 }
688
689                 /*
690                  *      There's nothing after this character, copy
691                  *      the last '%' or "$' or '\\' over to the output
692                  *      buffer, and exit.
693                  */
694                 if (*++p == '\0') {
695                         *q++ = c;
696                         break;
697                 }
698
699                 if (c == '\\') {
700                         switch(*p) {
701                         case '\\':
702                                 *q++ = *p;
703                                 break;
704                         case 't':
705                                 *q++ = '\t';
706                                 break;
707                         case 'n':
708                                 *q++ = '\n';
709                                 break;
710                         default:
711                                 *q++ = c;
712                                 *q++ = *p;
713                                 break;
714                         }
715                         p++;
716
717                         /*
718                          *      Hmmm... ${User-Name} is a synonym for
719                          *      %{User-Name}.
720                          *
721                          *      Why, exactly?
722                          */
723                 } else if (c == '$') switch(*p) {
724                         case '{': /* Attribute by Name */
725                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
726                                 break;
727                         default:
728                                 *q++ = c;
729                                 *q++ = *p++;
730                                 break;
731
732                 } else if (c == '%') switch(*p) {
733                         case '{':
734                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
735                                 break;
736
737                         case '%':
738                                 *q++ = *p++;
739                                 break;
740                         case 'a': /* Protocol: */
741                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
742                                 p++;
743                                 break;
744                         case 'c': /* Callback-Number */
745                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
746                                 p++;
747                                 break;
748                         case 'd': /* request day */
749                                 TM = localtime_r(&request->timestamp, &s_TM);
750                                 strftime(tmpdt,sizeof(tmpdt),"%d",TM);
751                                 strNcpy(q,tmpdt,freespace);
752                                 q += strlen(q);
753                                 p++;
754                                 break;
755                         case 'f': /* Framed IP address */
756                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
757                                 p++;
758                                 break;
759                         case 'i': /* Calling station ID */
760                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
761                                 p++;
762                                 break;
763                         case 'l': /* request timestamp */
764                                 snprintf(tmpdt, sizeof(tmpdt), "%lu",
765                                          (unsigned long) request->timestamp);
766                                 strNcpy(q,tmpdt,freespace);
767                                 q += strlen(q);
768                                 p++;
769                                 break;
770                         case 'm': /* request month */
771                                 TM = localtime_r(&request->timestamp, &s_TM);
772                                 strftime(tmpdt,sizeof(tmpdt),"%m",TM);
773                                 strNcpy(q,tmpdt,freespace);
774                                 q += strlen(q);
775                                 p++;
776                                 break;
777                         case 'n': /* NAS IP address */
778                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
779                                 p++;
780                                 break;
781                         case 'p': /* Port number */
782                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
783                                 p++;
784                                 break;
785                         case 's': /* Speed */
786                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
787                                 p++;
788                                 break;
789                         case 't': /* request timestamp */
790                                 CTIME_R(&request->timestamp, q, freespace);
791                                 q += strlen(q);
792                                 p++;
793                                 break;
794                         case 'u': /* User name */
795                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
796                                 p++;
797                                 break;
798                         case 'A': /* radacct_dir */
799                                 strNcpy(q,radacct_dir,freespace-1);
800                                 q += strlen(q);
801                                 p++;
802                                 break;
803                         case 'C': /* ClientName */
804                                 strNcpy(q,client_name(request->packet->src_ipaddr),freespace-1);
805                                 q += strlen(q);
806                                 p++;
807                                 break;
808                         case 'D': /* request date */
809                                 TM = localtime_r(&request->timestamp, &s_TM);
810                                 strftime(tmpdt,sizeof(tmpdt),"%Y%m%d",TM);
811                                 strNcpy(q,tmpdt,freespace);
812                                 q += strlen(q);
813                                 p++;
814                                 break;
815                         case 'H': /* request hour */
816                                 TM = localtime_r(&request->timestamp, &s_TM);
817                                 strftime(tmpdt,sizeof(tmpdt),"%H",TM);
818                                 strNcpy(q,tmpdt,freespace);
819                                 q += strlen(q);
820                                 p++;
821                                 break;
822                         case 'L': /* radlog_dir */
823                                 strNcpy(q,radlog_dir,freespace-1);
824                                 q += strlen(q);
825                                 p++;
826                                 break;
827                         case 'M': /* MTU */
828                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
829                                 p++;
830                                 break;
831                         case 'R': /* radius_dir */
832                                 strNcpy(q,radius_dir,freespace-1);
833                                 q += strlen(q);
834                                 p++;
835                                 break;
836                         case 'S': /* request timestamp in SQL format*/
837                                 TM = localtime_r(&request->timestamp, &s_TM);
838                                 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d %H:%M:%S",TM);
839                                 strNcpy(q,tmpdt,freespace);
840                                 q += strlen(q);
841                                 p++;
842                                 break;
843                         case 'T': /* request timestamp */
844                                 TM = localtime_r(&request->timestamp, &s_TM);
845                                 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d-%H.%M.%S.000000",TM);
846                                 strNcpy(q,tmpdt,freespace);
847                                 q += strlen(q);
848                                 p++;
849                                 break;
850                         case 'U': /* Stripped User name */
851                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
852                                 p++;
853                                 break;
854                         case 'V': /* Request-Authenticator */
855                                 if (request->packet->verified)
856                                         strNcpy(q,"Verified",freespace-1);
857                                 else
858                                         strNcpy(q,"None",freespace-1);
859                                 q += strlen(q);
860                                 p++;
861                                 break;
862                         case 'Y': /* request year */
863                                 TM = localtime_r(&request->timestamp, &s_TM);
864                                 strftime(tmpdt,sizeof(tmpdt),"%Y",TM);
865                                 strNcpy(q,tmpdt,freespace);
866                                 q += strlen(q);
867                                 p++;
868                                 break;
869                         case 'Z': /* Full request pairs except password */
870                                 tmp = request->packet->vps;
871                                 while (tmp && (freespace > 3)) {
872                                         if (tmp->attribute != PW_PASSWORD) {
873                                                 *q++ = '\t';
874                                                 i = vp_prints(q,freespace-2,tmp);
875                                                 q += i;
876                                                 freespace -= (i+2);
877                                                 *q++ = '\n';
878                                         }
879                                         tmp = tmp->next;
880                                 }
881                                 p++;
882                                 break;
883                         default:
884                                 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
885                                 if (freespace > 2) {
886                                         *q++ = '%';
887                                         *q++ = *p++;
888                                 }
889                                 break;
890                 }
891         }
892         *q = '\0';
893
894         DEBUG2("radius_xlat:  '%s'", out);
895
896         return strlen(out);
897 }