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