4d1572a3d59e3e7873cff3260c34161672bb22a0
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  *
21  * Copyright 2000,2006  The FreeRADIUS server project
22  * Copyright 2000  Alan DeKok <aland@ox.org>
23  */
24
25 #include <freeradius-devel/ident.h>
26 RCSID("$Id$")
27
28 #include        <freeradius-devel/radiusd.h>
29 #include        <freeradius-devel/md5.h>
30 #include        <freeradius-devel/rad_assert.h>
31
32 #include        <ctype.h>
33
34 typedef struct xlat_t {
35         char            module[MAX_STRING_LEN];
36         int             length;
37         void            *instance;
38         RAD_XLAT_FUNC   do_xlat;
39         int             internal;       /* not allowed to re-define these */
40 } xlat_t;
41
42 static rbtree_t *xlat_root = NULL;
43
44 /*
45  *      Define all xlat's in the structure.
46  */
47 static const char * const internal_xlat[] = {"check",
48                                              "request",
49                                              "reply",
50                                              "proxy-request",
51                                              "proxy-reply",
52                                              "outer.request",
53                                              "outer.reply",
54                                              NULL};
55
56 #if REQUEST_MAX_REGEX > 8
57 #error Please fix the following line
58 #endif
59 static const 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, -1);
72                 return func(out, outlen, buffer);
73         }
74
75         switch (type) {
76         case PW_TYPE_STRING :
77                 strlcpy(out,"_",outlen);
78                 break;
79         case PW_TYPE_INTEGER :
80                 strlcpy(out,"0",outlen);
81                 break;
82         case PW_TYPE_IPADDR :
83                 strlcpy(out,"?.?.?.?",outlen);
84                 break;
85         case PW_TYPE_IPV6ADDR :
86                 strlcpy(out,":?:",outlen);
87                 break;
88         case PW_TYPE_DATE :
89                 strlcpy(out,"0",outlen);
90                 break;
91         default :
92                 strlcpy(out,"unknown_type",outlen);
93         }
94         return strlen(out);
95 }
96
97
98 /*
99  *      Dynamically translate for check:, request:, reply:, etc.
100  */
101 static size_t xlat_packet(void *instance, REQUEST *request,
102                           char *fmt, char *out, size_t outlen,
103                           RADIUS_ESCAPE_STRING func)
104 {
105         DICT_ATTR       *da;
106         VALUE_PAIR      *vp;
107         VALUE_PAIR      *vps = NULL;
108         RADIUS_PACKET   *packet = NULL;
109
110         switch (*(int*) instance) {
111         case 0:
112                 vps = request->config_items;
113                 break;
114
115         case 1:
116                 vps = request->packet->vps;
117                 packet = request->packet;
118                 break;
119
120         case 2:
121                 vps = request->reply->vps;
122                 packet = request->reply;
123                 break;
124
125         case 3:
126 #ifdef WITH_PROXY
127                 if (request->proxy) vps = request->proxy->vps;
128                 packet = request->proxy;
129 #endif
130                 break;
131
132         case 4:
133 #ifdef WITH_PROXY
134                 if (request->proxy_reply) vps = request->proxy_reply->vps;
135                 packet = request->proxy_reply;
136 #endif
137                 break;
138
139         case 5:
140                 if (request->parent) {
141                         vps = request->parent->packet->vps;
142                         packet = request->parent->packet;
143                 }
144                 break;
145                         
146         case 6:
147                 if (request->parent && request->parent->reply) {
148                         vps = request->parent->reply->vps;
149                         packet = request->parent->reply;
150                 }
151                 break;
152                         
153         default:                /* WTF? */
154                 return 0;
155         }
156
157         /*
158          *      The "format" string is the attribute name.
159          */
160         da = dict_attrbyname(fmt);
161         if (!da) {
162                 int do_number = FALSE;
163                 size_t count;
164                 const char *p;
165                 char buffer[256];
166
167                 if (strlen(fmt) > sizeof(buffer)) return 0;
168
169                 DEBUG("GOT SHIT %s", fmt);
170
171                 p = strchr(fmt, '[');
172                 if (!p) {
173                         p = strchr(fmt, '#');
174                         if (!p) return 0;
175                         do_number = TRUE;
176                 }
177
178                 strlcpy(buffer, fmt, p - fmt + 1);
179
180                 da = dict_attrbyname(buffer);
181                 if (!da) return 0;
182
183                 if (do_number) {
184                         vp = pairfind(vps, da->attr);
185                         if (!vp) return 0;
186
187                         switch (da->type) {
188                         default:
189                                 break;
190
191                         case PW_TYPE_INTEGER:
192                         case PW_TYPE_DATE:
193                         case PW_TYPE_SHORT:
194                         case PW_TYPE_BYTE:
195                                 snprintf(out, outlen, "%u", vp->lvalue);
196                                 return strlen(out);
197                         }
198
199                         goto just_print;
200                 }
201
202                 /*
203                  *      %{Attribute-Name[#]} returns the count of
204                  *      attributes of that name in the list.
205                  */
206                 if ((p[1] == '#') && (p[2] == ']')) {
207                         count = 0;
208
209                         for (vp = pairfind(vps, da->attr);
210                              vp != NULL;
211                              vp = pairfind(vp->next, da->attr)) {
212                                 count++;
213                         }
214                         snprintf(out, outlen, "%d", (int) count);
215                         return strlen(out);
216                 }
217
218                 /*
219                  *      %{Attribute-Name[*]} returns ALL of the
220                  *      the attributes, separated by a newline.
221                  */
222                 if ((p[1] == '*') && (p[2] == ']')) {
223                         int total = 0;
224
225                         for (vp = pairfind(vps, da->attr);
226                              vp != NULL;
227                              vp = pairfind(vp->next, da->attr)) {
228                                 count = valuepair2str(out, outlen - 1, vp, da->type, func);
229                                 rad_assert(count <= outlen);
230                                 total += count + 1;
231                                 outlen -= (count + 1);
232                                 out += count;
233
234                                 *(out++) = '\n';
235
236                                 if (outlen == 0) break;
237                         }
238
239                         return total;
240                 }
241
242                 count = atoi(p + 1);
243
244                 /*
245                  *      Skip the numbers.
246                  */
247                 p += 1 + strspn(p + 1, "0123456789");
248                 if (*p != ']') {
249                         RDEBUG2("xlat: Invalid array reference in string at %s %s",
250                                fmt, p);
251                         return 0;
252                 }
253
254                 /*
255                  *      Find the N'th value.
256                  */
257                 for (vp = pairfind(vps, da->attr);
258                      vp != NULL;
259                      vp = pairfind(vp->next, da->attr)) {
260                         if (count == 0) break;
261                         count--;
262                 }
263
264                 /*
265                  *      Non-existent array reference.
266                  */
267                 if (!vp) return 0;
268         just_print:
269                 return valuepair2str(out, outlen, vp, da->type, func);
270         }
271
272         vp = pairfind(vps, da->attr);
273         if (!vp) {
274                 /*
275                  *      Some "magic" handlers, which are never in VP's, but
276                  *      which are in the packet.
277                  *
278                  *      FIXME: We should really do this in a more
279                  *      intelligent way...
280                  */
281                 if (packet) {
282                         VALUE_PAIR localvp;
283
284                         memset(&localvp, 0, sizeof(localvp));
285
286                         switch (da->attr) {
287                         case PW_PACKET_TYPE:
288                         {
289                                 DICT_VALUE *dval;
290
291                                 dval = dict_valbyattr(da->attr, packet->code);
292                                 if (dval) {
293                                         snprintf(out, outlen, "%s", dval->name);
294                                 } else {
295                                         snprintf(out, outlen, "%d", packet->code);
296                                 }
297                                 return strlen(out);
298                         }
299                         break;
300
301                         case PW_CLIENT_SHORTNAME:
302                                 if (request->client && request->client->shortname) {
303                                         strlcpy(out, request->client->shortname, outlen);
304                                 } else {
305                                         strlcpy(out, "<UNKNOWN-CLIENT>", outlen);
306                                 }
307                                 return strlen(out);
308
309                         case PW_CLIENT_IP_ADDRESS: /* the same as below */
310                         case PW_PACKET_SRC_IP_ADDRESS:
311                                 if (packet->src_ipaddr.af != AF_INET) {
312                                         return 0;
313                                 }
314                                 localvp.attribute = da->attr;
315                                 localvp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
316                                 break;
317
318                         case PW_PACKET_DST_IP_ADDRESS:
319                                 if (packet->dst_ipaddr.af != AF_INET) {
320                                         return 0;
321                                 }
322                                 localvp.attribute = da->attr;
323                                 localvp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
324                                 break;
325
326                         case PW_PACKET_SRC_PORT:
327                                 localvp.attribute = da->attr;
328                                 localvp.vp_integer = packet->src_port;
329                                 break;
330
331                         case PW_PACKET_DST_PORT:
332                                 localvp.attribute = da->attr;
333                                 localvp.vp_integer = packet->dst_port;
334                                 break;
335
336                         case PW_PACKET_AUTHENTICATION_VECTOR:
337                                 localvp.attribute = da->attr;
338                                 memcpy(localvp.vp_strvalue, packet->vector,
339                                        sizeof(packet->vector));
340                                 localvp.length = sizeof(packet->vector);
341                                 break;
342
343                                 /*
344                                  *      Authorization, accounting, etc.
345                                  */
346                         case PW_REQUEST_PROCESSING_STAGE:
347                                 if (request->component) {
348                                         strlcpy(out, request->component, outlen);
349                                 } else {
350                                         strlcpy(out, "server_core", outlen);
351                                 }
352                                 return strlen(out);
353
354                         case PW_PACKET_SRC_IPV6_ADDRESS:
355                                 if (packet->src_ipaddr.af != AF_INET6) {
356                                         return 0;
357                                 }
358                                 localvp.attribute = da->attr;
359                                 memcpy(localvp.vp_strvalue,
360                                        &packet->src_ipaddr.ipaddr.ip6addr,
361                                        sizeof(packet->src_ipaddr.ipaddr.ip6addr));
362                                 break;
363
364                         case PW_PACKET_DST_IPV6_ADDRESS:
365                                 if (packet->dst_ipaddr.af != AF_INET6) {
366                                         return 0;
367                                 }
368                                 localvp.attribute = da->attr;
369                                 memcpy(localvp.vp_strvalue,
370                                        &packet->dst_ipaddr.ipaddr.ip6addr,
371                                        sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
372                                 break;
373
374                         case PW_VIRTUAL_SERVER:
375                                 if (!request->server) return 0;
376
377                                 snprintf(out, outlen, "%s", request->server);
378                                 return strlen(out);
379                                 break;
380
381                         case PW_MODULE_RETURN_CODE:
382                                 localvp.attribute = da->attr;
383
384                                 /*
385                                  *      See modcall.c for a bit of a hack.
386                                  */
387                                 localvp.vp_integer = request->simul_max;
388                                 break;
389
390                         default:
391                                 return 0; /* not found */
392                                 break;
393                         }
394
395                         localvp.type = da->type;
396                         return valuepair2str(out, outlen, &localvp,
397                                              da->type, func);
398                 }
399
400                 /*
401                  *      Not found, die.
402                  */
403                 return 0;
404         }
405
406         if (!vps) return 0;     /* silently fail */
407
408         /*
409          *      Convert the VP to a string, and return it.
410          */
411         return valuepair2str(out, outlen, vp, da->type, func);
412 }
413
414 #ifdef HAVE_REGEX_H
415 /*
416  *      Pull %{0} to %{8} out of the packet.
417  */
418 static size_t xlat_regex(void *instance, REQUEST *request,
419                          char *fmt, char *out, size_t outlen,
420                          RADIUS_ESCAPE_STRING func)
421 {
422         char *regex;
423
424         /*
425          *      We cheat: fmt is "0" to "8", but those numbers
426          *      are already in the "instance".
427          */
428         fmt = fmt;              /* -Wunused */
429         func = func;            /* -Wunused FIXME: do escaping? */
430
431         regex = request_data_reference(request, request,
432                                  REQUEST_DATA_REGEX | *(int *)instance);
433         if (!regex) return 0;
434
435         /*
436          *      Copy UP TO "freespace" bytes, including
437          *      a zero byte.
438          */
439         strlcpy(out, regex, outlen);
440         return strlen(out);
441 }
442 #endif                          /* HAVE_REGEX_H */
443
444
445 /*
446  *      Change the debugging level.
447  */
448 static size_t xlat_debug(UNUSED void *instance, REQUEST *request,
449                           char *fmt, char *out, size_t outlen,
450                           UNUSED RADIUS_ESCAPE_STRING func)
451 {
452         int level = 0;
453
454         if (*fmt) level = atoi(fmt);
455
456         if (level == 0) {
457                 request->options = RAD_REQUEST_OPTION_NONE;
458                 request->radlog = NULL;
459         } else {
460                 if (level > 4) level = 4;
461
462                 request->options = level;
463                 request->radlog = radlog_request;
464         }
465
466         snprintf(out, outlen, "%d", level);
467         return strlen(out);
468 }
469
470
471 /*
472  *      Calculate the MD5 hash of a string.
473  */
474 static size_t xlat_md5(UNUSED void *instance, REQUEST *request,
475                        char *fmt, char *out, size_t outlen,
476                        UNUSED RADIUS_ESCAPE_STRING func)
477 {
478         int i;
479         uint8_t digest[16];
480         FR_MD5_CTX ctx;
481         char buffer[1024];
482
483         if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
484                 *out = '\0';
485                 return 0;
486         }
487
488         fr_MD5Init(&ctx);
489         fr_MD5Update(&ctx, (void *) buffer, strlen(buffer));
490         fr_MD5Final(digest, &ctx);
491
492         if (outlen < 33) {
493                 snprintf(out, outlen, "md5_overflow");
494                 return strlen(out);
495         }
496
497         for (i = 0; i < 16; i++) {
498                 snprintf(out + i * 2, 3, "%02x", digest[i]);
499         }
500
501         return strlen(out);
502 }
503
504 /*
505  *      Compare two xlat_t structs, based ONLY on the module name.
506  */
507 static int xlat_cmp(const void *a, const void *b)
508 {
509         if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
510                 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
511         }
512
513         return memcmp(((const xlat_t *)a)->module,
514                       ((const xlat_t *)b)->module,
515                       ((const xlat_t *)a)->length);
516 }
517
518
519 /*
520  *      find the appropriate registered xlat function.
521  */
522 static xlat_t *xlat_find(const char *module)
523 {
524         xlat_t my_xlat;
525
526         /*
527          *      Look for dictionary attributes first.
528          */
529         if ((dict_attrbyname(module) != NULL) ||
530             (strchr(module, '[') != NULL) ||
531             (strchr(module, '#') != NULL)) {
532                 module = "request";
533         }
534
535         strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
536         my_xlat.length = strlen(my_xlat.module);
537
538         return rbtree_finddata(xlat_root, &my_xlat);
539 }
540
541
542 /*
543  *      Register an xlat function.
544  */
545 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
546 {
547         xlat_t  *c;
548         xlat_t  my_xlat;
549
550         if ((module == NULL) || (strlen(module) == 0)) {
551                 DEBUG("xlat_register: Invalid module name");
552                 return -1;
553         }
554
555         /*
556          *      First time around, build up the tree...
557          *
558          *      FIXME: This code should be hoisted out of this function,
559          *      and into a global "initialization".  But it isn't critical...
560          */
561         if (!xlat_root) {
562                 int i;
563 #ifdef HAVE_REGEX_H
564                 char buffer[2];
565 #endif
566
567                 xlat_root = rbtree_create(xlat_cmp, free, 0);
568                 if (!xlat_root) {
569                         DEBUG("xlat_register: Failed to create tree.");
570                         return -1;
571                 }
572
573                 /*
574                  *      Register the internal packet xlat's.
575                  */
576                 for (i = 0; internal_xlat[i] != NULL; i++) {
577                         xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
578                         c = xlat_find(internal_xlat[i]);
579                         rad_assert(c != NULL);
580                         c->internal = TRUE;
581                 }
582
583                 /*
584                  *      New name: "control"
585                  */
586                 xlat_register("control", xlat_packet, &xlat_inst[0]);
587                 c = xlat_find("control");
588                 rad_assert(c != NULL);
589                 c->internal = TRUE;
590
591 #ifdef HAVE_REGEX_H
592                 /*
593                  *      Register xlat's for regexes.
594                  */
595                 buffer[1] = '\0';
596                 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
597                         buffer[0] = '0' + i;
598                         xlat_register(buffer, xlat_regex, &xlat_inst[i]);
599                         c = xlat_find(buffer);
600                         rad_assert(c != NULL);
601                         c->internal = TRUE;
602                 }
603 #endif /* HAVE_REGEX_H */
604
605
606                 xlat_register("debug", xlat_debug, &xlat_inst[0]);
607                 c = xlat_find("debug");
608                 rad_assert(c != NULL);
609                 c->internal = TRUE;
610
611                 xlat_register("md5", xlat_md5, &xlat_inst[0]);
612                 c = xlat_find("md5");
613                 rad_assert(c != NULL);
614                 c->internal = TRUE;
615         }
616
617         /*
618          *      If it already exists, replace the instance.
619          */
620         strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
621         my_xlat.length = strlen(my_xlat.module);
622         c = rbtree_finddata(xlat_root, &my_xlat);
623         if (c) {
624                 if (c->internal) {
625                         DEBUG("xlat_register: Cannot re-define internal xlat");
626                         return -1;
627                 }
628
629                 c->do_xlat = func;
630                 c->instance = instance;
631                 return 0;
632         }
633
634         /*
635          *      Doesn't exist.  Create it.
636          */
637         c = rad_malloc(sizeof(*c));
638         memset(c, 0, sizeof(*c));
639
640         c->do_xlat = func;
641         strlcpy(c->module, module, sizeof(c->module));
642         c->length = strlen(c->module);
643         c->instance = instance;
644
645         rbtree_insert(xlat_root, c);
646
647         return 0;
648 }
649
650 /*
651  *      Unregister an xlat function.
652  *
653  *      We can only have one function to call per name, so the
654  *      passing of "func" here is extraneous.
655  */
656 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
657 {
658         rbnode_t        *node;
659         xlat_t          my_xlat;
660
661         func = func;            /* -Wunused */
662
663         if (!module) return;
664
665         strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
666         my_xlat.length = strlen(my_xlat.module);
667
668         node = rbtree_find(xlat_root, &my_xlat);
669         if (!node) return;
670
671         rbtree_delete(xlat_root, node);
672 }
673
674 /*
675  *      De-register all xlat functions,
676  *      used mainly for debugging.
677  */
678 void xlat_free(void)
679 {
680         rbtree_free(xlat_root);
681 }
682
683
684 /*
685  *      Decode an attribute name into a string.
686  */
687 static int decode_attribute(const char **from, char **to, int freespace,
688                              REQUEST *request,
689                              RADIUS_ESCAPE_STRING func)
690 {
691         int     do_length = 0;
692         char    *xlat_name, *xlat_string;
693         char *p, *q, *l, *next = NULL;
694         int retlen=0;
695         const xlat_t *c;
696         int varlen;
697         char buffer[8192];
698
699         q = *to;
700
701         *q = '\0';
702
703         /*
704          *      Copy the input string to an intermediate buffer where
705          *      we can mangle it.
706          */
707         varlen = rad_copy_variable(buffer, *from);
708         if (varlen < 0) {
709                 RDEBUG2("Badly formatted variable: %s", *from);
710                 return -1;
711         }
712         *from += varlen;
713
714         /*
715          *      Kill the %{} around the data we are looking for.
716          */
717         p = buffer;
718         p[varlen - 1] = '\0';   /*  */
719         p += 2;
720         if (*p == '#') {
721                 p++;
722                 do_length = 1;
723         }
724
725         /*
726          *      Handle %{%{foo}:-%{bar}}, which is useful, too.
727          *
728          *      Did I mention that this parser is garbage?
729          */
730         if ((p[0] == '%') && (p[1] == '{')) {
731                 int len1, len2;
732                 int expand2 = FALSE;
733
734                 /*
735                  *      'p' is after the start of 'buffer', so we can
736                  *      safely do this.
737                  */
738                 len1 = rad_copy_variable(buffer, p);
739                 if (len1 < 0) {
740                         RDEBUG2("Badly formatted variable: %s", p);
741                         return -1;
742                 }
743
744                 /*
745                  *      They did %{%{foo}}, which is stupid, but allowed.
746                  */
747                 if (!p[len1]) {
748                         RDEBUG2("Improperly nested variable; %%{%s}", p);
749                         return -1;
750                 }
751
752                 /*
753                  *      It SHOULD be %{%{foo}:-%{bar}}.  If not, it's
754                  *      an error.
755                  */
756                 if ((p[len1] != ':') || (p[len1 + 1] != '-')) {
757                         RDEBUG2("No trailing :- after variable at %s", p);
758                         return -1;
759                 }
760
761                 /*
762                  *      Parse the second bit.  The second bit can be
763                  *      either %{foo}, or a string "foo", or a string
764                  *      'foo', or just a bare word: foo
765                  */
766                 p += len1 + 2;
767                 l = buffer + len1 + 1;
768
769                 if ((p[0] == '%') && (p[1] == '{')) {
770                         len2 = rad_copy_variable(l, p);
771
772                         if (len2 < 0) {
773                                 RDEBUG2("Invalid text after :- at %s", p);
774                                 return -1;
775                         }
776                         p += len2;
777                         expand2 = TRUE;
778
779                 } else if ((p[0] == '"') || p[0] == '\'') {
780                         getstring(&p, l, strlen(l));
781
782                 } else {
783                         l = p;
784                 }
785
786                 /*
787                  *      Expand the first one.  If we did, exit the
788                  *      conditional.
789                  */
790                 retlen = radius_xlat(q, freespace, buffer, request, func);
791                 if (retlen) {
792                         q += retlen;
793                         goto done;
794                 }
795
796                 RDEBUG2("\t... expanding second conditional");
797                 /*
798                  *      Expand / copy the second string if required.
799                  */
800                 if (expand2) {
801                         retlen = radius_xlat(q, freespace, l,
802                                             request, func);
803                         if (retlen) {
804                                 q += retlen;
805                         }
806                 } else {
807                         strlcpy(q, l, freespace);
808                         q += strlen(q);
809                 }
810
811                 /*
812                  *      Else the output is an empty string.
813                  */
814                 goto done;
815         }
816
817         /*
818          *      See if we're supposed to expand a module name.
819          */
820         xlat_name = NULL;
821         for (l = p; *l != '\0'; l++) {
822                 if (*l == '\\') {
823                         l++;
824                         continue;
825                 }
826
827                 if (*l == ':') {
828                         xlat_name = p; /* start of name */
829                         *l = '\0';
830                         p = l + 1;
831                         break;
832                 }
833
834                 /*
835                  *      Module names can't have spaces.
836                  */
837                 if ((*l == ' ') || (*l == '\t')) break;
838         }
839
840         /*
841          *      %{name} is a simple attribute reference,
842          *      or regex reference.
843          */
844         if (!xlat_name) {
845                 xlat_name = xlat_string = p;
846                 goto do_xlat;
847         }
848
849         /*
850          *      Maybe it's the old-style %{foo:-bar}
851          */
852         if (*p == '-') {
853                 RDEBUG2("WARNING: Deprecated conditional expansion \":-\".  See \"man unlang\" for details");
854                 p++;
855
856                 xlat_string = xlat_name;
857                 next = p;
858                 goto do_xlat;
859         }
860
861         /*
862          *      FIXME: For backwards "WTF" compatibility, check for
863          *      {...}, (after the :), and copy that, too.
864          */
865
866         /* module name, followed by (possibly) per-module string */
867         xlat_string = p;
868         
869 do_xlat:
870         if ((c = xlat_find(xlat_name)) != NULL) {
871                 if (!c->internal) RDEBUG3("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
872                                           c->module, xlat_string);
873                 retlen = c->do_xlat(c->instance, request, xlat_string,
874                                     q, freespace, func);
875                 if (retlen > 0) {
876                         if (do_length) {
877                                 snprintf(q, freespace, "%d", retlen);
878                                 retlen = strlen(q);
879                         }
880
881                 } else if (next) {
882                         /*
883                          *      Expand the second bit.
884                          */
885                         RDEBUG2("\t... expanding second conditional");
886                         retlen = radius_xlat(q, freespace, next, request, func);
887                 }
888                 q += retlen;
889
890         } else {
891                 /*
892                  *      No attribute by that name, return an error.
893                  */
894                 RDEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
895                 return -1;
896         }
897
898 done:
899         *to = q;
900         return 0;
901 }
902
903 /*
904  *  If the caller doesn't pass xlat an escape function, then
905  *  we use this one.  It simplifies the coding, as the check for
906  *  func == NULL only happens once.
907  */
908 static size_t xlat_copy(char *out, size_t outlen, const char *in)
909 {
910         int freespace = outlen;
911
912         rad_assert(outlen > 0);
913
914         while ((*in) && (freespace > 1)) {
915                 /*
916                  *  Copy data.
917                  *
918                  *  FIXME: Do escaping of bad stuff!
919                  */
920                 *(out++) = *(in++);
921
922                 freespace--;
923         }
924         *out = '\0';
925
926         return (outlen - freespace); /* count does not include NUL */
927 }
928
929 /*
930  *      Replace %<whatever> in a string.
931  *
932  *      See 'doc/variables.txt' for more information.
933  */
934 int radius_xlat(char *out, int outlen, const char *fmt,
935                 REQUEST *request, RADIUS_ESCAPE_STRING func)
936 {
937         int c, len, freespace;
938         const char *p;
939         char *q;
940         char *nl;
941         VALUE_PAIR *tmp;
942         struct tm *TM, s_TM;
943         char tmpdt[40]; /* For temporary storing of dates */
944         int openbraces=0;
945
946         /*
947          *      Catch bad modules.
948          */
949         if (!fmt || !out || !request) return 0;
950
951         /*
952          *  Ensure that we always have an escaping function.
953          */
954         if (func == NULL) {
955                 func = xlat_copy;
956         }
957
958         q = out;
959         p = fmt;
960         while (*p) {
961                 /* Calculate freespace in output */
962                 freespace = outlen - (q - out);
963                 if (freespace <= 1)
964                         break;
965                 c = *p;
966
967                 if ((c != '%') && (c != '$') && (c != '\\')) {
968                         /*
969                          * We check if we're inside an open brace.  If we are
970                          * then we assume this brace is NOT literal, but is
971                          * a closing brace and apply it
972                          */
973                         if ((c == '}') && openbraces) {
974                                 openbraces--;
975                                 p++; /* skip it */
976                                 continue;
977                         }
978                         *q++ = *p++;
979                         continue;
980                 }
981
982                 /*
983                  *      There's nothing after this character, copy
984                  *      the last '%' or "$' or '\\' over to the output
985                  *      buffer, and exit.
986                  */
987                 if (*++p == '\0') {
988                         *q++ = c;
989                         break;
990                 }
991
992                 if (c == '\\') {
993                         switch(*p) {
994                         case '\\':
995                                 *q++ = *p;
996                                 break;
997                         case 't':
998                                 *q++ = '\t';
999                                 break;
1000                         case 'n':
1001                                 *q++ = '\n';
1002                                 break;
1003                         default:
1004                                 *q++ = c;
1005                                 *q++ = *p;
1006                                 break;
1007                         }
1008                         p++;
1009
1010                 } else if (c == '%') switch(*p) {
1011                         case '{':
1012                                 p--;
1013                                 if (decode_attribute(&p, &q, freespace, request, func) < 0) return 0;
1014                                 break;
1015
1016                         case '%':
1017                                 *q++ = *p++;
1018                                 break;
1019                         case 'a': /* Protocol: */
1020                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
1021                                 p++;
1022                                 break;
1023                         case 'c': /* Callback-Number */
1024                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
1025                                 p++;
1026                                 break;
1027                         case 'd': /* request day */
1028                                 TM = localtime_r(&request->timestamp, &s_TM);
1029                                 len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
1030                                 if (len > 0) {
1031                                         strlcpy(q, tmpdt, freespace);
1032                                         q += strlen(q);
1033                                 }
1034                                 p++;
1035                                 break;
1036                         case 'f': /* Framed IP address */
1037                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
1038                                 p++;
1039                                 break;
1040                         case 'i': /* Calling station ID */
1041                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
1042                                 p++;
1043                                 break;
1044                         case 'l': /* request timestamp */
1045                                 snprintf(tmpdt, sizeof(tmpdt), "%lu",
1046                                          (unsigned long) request->timestamp);
1047                                 strlcpy(q,tmpdt,freespace);
1048                                 q += strlen(q);
1049                                 p++;
1050                                 break;
1051                         case 'm': /* request month */
1052                                 TM = localtime_r(&request->timestamp, &s_TM);
1053                                 len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
1054                                 if (len > 0) {
1055                                         strlcpy(q, tmpdt, freespace);
1056                                         q += strlen(q);
1057                                 }
1058                                 p++;
1059                                 break;
1060                         case 'n': /* NAS IP address */
1061                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
1062                                 p++;
1063                                 break;
1064                         case 'p': /* Port number */
1065                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
1066                                 p++;
1067                                 break;
1068                         case 's': /* Speed */
1069                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
1070                                 p++;
1071                                 break;
1072                         case 't': /* request timestamp */
1073                                 CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
1074                                 nl = strchr(tmpdt, '\n');
1075                                 if (nl) *nl = '\0';
1076                                 strlcpy(q, tmpdt, freespace);
1077                                 q += strlen(q);
1078                                 p++;
1079                                 break;
1080                         case 'u': /* User name */
1081                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
1082                                 p++;
1083                                 break;
1084                         case 'A': /* radacct_dir */
1085                                 strlcpy(q,radacct_dir,freespace);
1086                                 q += strlen(q);
1087                                 p++;
1088                                 break;
1089                         case 'C': /* ClientName */
1090                                 strlcpy(q,request->client->shortname,freespace);
1091                                 q += strlen(q);
1092                                 p++;
1093                                 break;
1094                         case 'D': /* request date */
1095                                 TM = localtime_r(&request->timestamp, &s_TM);
1096                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
1097                                 if (len > 0) {
1098                                         strlcpy(q, tmpdt, freespace);
1099                                         q += strlen(q);
1100                                 }
1101                                 p++;
1102                                 break;
1103                         case 'H': /* request hour */
1104                                 TM = localtime_r(&request->timestamp, &s_TM);
1105                                 len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
1106                                 if (len > 0) {
1107                                         strlcpy(q, tmpdt, freespace);
1108                                         q += strlen(q);
1109                                 }
1110                                 p++;
1111                                 break;
1112                         case 'L': /* radlog_dir */
1113                                 strlcpy(q,radlog_dir,freespace);
1114                                 q += strlen(q);
1115                                 p++;
1116                                 break;
1117                         case 'M': /* MTU */
1118                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
1119                                 p++;
1120                                 break;
1121                         case 'R': /* radius_dir */
1122                                 strlcpy(q,radius_dir,freespace);
1123                                 q += strlen(q);
1124                                 p++;
1125                                 break;
1126                         case 'S': /* request timestamp in SQL format*/
1127                                 TM = localtime_r(&request->timestamp, &s_TM);
1128                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
1129                                 if (len > 0) {
1130                                         strlcpy(q, tmpdt, freespace);
1131                                         q += strlen(q);
1132                                 }
1133                                 p++;
1134                                 break;
1135                         case 'T': /* request timestamp */
1136                                 TM = localtime_r(&request->timestamp, &s_TM);
1137                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
1138                                 if (len > 0) {
1139                                         strlcpy(q, tmpdt, freespace);
1140                                         q += strlen(q);
1141                                 }
1142                                 p++;
1143                                 break;
1144                         case 'U': /* Stripped User name */
1145                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
1146                                 p++;
1147                                 break;
1148                         case 'V': /* Request-Authenticator */
1149                                 strlcpy(q,"Verified",freespace);
1150                                 q += strlen(q);
1151                                 p++;
1152                                 break;
1153                         case 'Y': /* request year */
1154                                 TM = localtime_r(&request->timestamp, &s_TM);
1155                                 len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
1156                                 if (len > 0) {
1157                                         strlcpy(q, tmpdt, freespace);
1158                                         q += strlen(q);
1159                                 }
1160                                 p++;
1161                                 break;
1162                         case 'Z': /* Full request pairs except password */
1163                                 tmp = request->packet->vps;
1164                                 while (tmp && (freespace > 3)) {
1165                                         if (tmp->attribute != PW_USER_PASSWORD) {
1166                                                 *q++ = '\t';
1167                                                 len = vp_prints(q, freespace - 2, tmp);
1168                                                 q += len;
1169                                                 freespace -= (len + 2);
1170                                                 *q++ = '\n';
1171                                         }
1172                                         tmp = tmp->next;
1173                                 }
1174                                 p++;
1175                                 break;
1176                         default:
1177                                 RDEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
1178                                 if (freespace > 2) {
1179                                         *q++ = '%';
1180                                         *q++ = *p++;
1181                                 }
1182                                 break;
1183                 }
1184         }
1185         *q = '\0';
1186
1187         RDEBUG2("\texpand: %s -> %s", fmt, out);
1188
1189         return strlen(out);
1190 }