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