Check return value from registered xlat functions. If return value is 0,
[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 struct xlat_cmp {
39         char module[MAX_STRING_LEN];
40         int length;
41         void *instance;
42         RAD_XLAT_FUNC do_xlat;
43         struct xlat_cmp *next;
44 };
45
46 static struct xlat_cmp *cmp = NULL;
47
48 /*
49  *      Register an xlat function.
50  */
51 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
52 {
53         struct xlat_cmp      *c;
54
55         if (module == NULL || strlen(module) == 0){
56                 DEBUG("xlat_register: Invalid module name");
57                 return -1;
58         }
59
60         xlat_unregister(module, func);
61
62         c = rad_malloc(sizeof(struct xlat_cmp));
63
64         c->do_xlat = func;
65         strNcpy(c->module, module, sizeof(c->module));
66         c->length = strlen(c->module);
67         c->instance = instance;
68         c->next = cmp;
69         cmp = c;
70
71         return 0;
72 }
73
74 /*
75  *      Unregister an xlat function.
76  */
77 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
78 {
79         struct xlat_cmp      *c, *last;
80
81         last = NULL;
82         for (c = cmp; c; c = c->next) {
83                 if (strncmp(c->module,module,c->length) == 0 && c->do_xlat == func)
84                         break;
85                 last = c;
86         }
87
88         if (c == NULL) return;
89
90         if (last != NULL)
91                 last->next = c->next;
92         else
93                 cmp = c->next;
94
95         free(c);
96 }
97
98 /*
99  * find the appropriate registered xlat function.
100  */
101 static struct xlat_cmp *find_xlat_func(const char *module)
102 {
103         struct xlat_cmp *c;
104
105         for (c = cmp; c; c = c->next){
106                 if (strncmp(c->module,module,c->length) == 0 && *(module+c->length) == ':')
107                         break;
108         }
109
110         return c;
111 }
112
113
114 /*
115    Convert the value on a VALUE_PAIR to string
116 */
117 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
118                          int type, RADIUS_ESCAPE_STRING func)
119 {
120         char buffer[MAX_STRING_LEN * 4];
121
122         if (pair != NULL) {
123                 vp_prints_value(buffer, sizeof(buffer), pair, 0);
124                 return func(out, outlen, buffer);
125         }
126
127         switch (type) {
128         case PW_TYPE_STRING :
129                 strNcpy(out,"_",outlen);
130                 break;
131         case PW_TYPE_INTEGER :
132                 strNcpy(out,"0",outlen);
133                 break;
134         case PW_TYPE_IPADDR :
135                 strNcpy(out,"?.?.?.?",outlen);
136                 break;
137         case PW_TYPE_DATE :
138                 strNcpy(out,"0",outlen);
139                 break;
140         default :
141                 strNcpy(out,"unknown_type",outlen);
142         }
143         return strlen(out);
144 }
145
146 /*
147  *      Decode an attribute name from a particular RADIUS_PACKET
148  *      into a string.
149  */
150 static int decode_attr_packet(const char *from, char **to, int freespace,
151                               RADIUS_PACKET *packet,
152                               RADIUS_ESCAPE_STRING func)
153
154 {
155         DICT_ATTR *tmpda;
156         VALUE_PAIR *vp;
157
158         tmpda = dict_attrbyname(from);
159         if (!tmpda) return 0;
160
161         /*
162          *      See if the VP is defined.
163          */
164         vp = pairfind(packet->vps, tmpda->attr);
165         if (vp) {
166                 *to += valuepair2str(*to, freespace, vp,
167                                      tmpda->type, func);
168                 return 1;
169         }
170
171         /*
172          *      Non-protocol attributes.
173          */
174         switch (tmpda->attr) {
175                 case PW_PACKET_TYPE:
176                 {
177                         DICT_VALUE *dval;
178
179                         dval = dict_valbyattr(tmpda->attr, packet->code);
180                         if (dval) {
181                                 snprintf(*to, freespace, "%s", dval->name);
182                         } else {
183                                 snprintf(*to, freespace, "%d", packet->code);
184                         }
185                         *to += strlen(*to);
186                         return 1;
187                 }
188                 break;
189
190                 default:
191                         break;
192         }
193
194         return 0;
195 }
196
197 /*
198  * Decode an attribute name from a particular VALUE_PAIR*
199  * into a string.
200  */
201 static int decode_attr_vps(const char *from, char **to, int freespace,
202                               VALUE_PAIR *vps,
203                               RADIUS_ESCAPE_STRING func)
204
205 {
206         DICT_ATTR *tmpda;
207         VALUE_PAIR *vp;
208
209         tmpda = dict_attrbyname(from);
210         if (!tmpda) return 0;
211
212         /*
213          *      See if the VP is defined.
214          */
215         vp = pairfind(vps, tmpda->attr);
216         if (vp) {
217                 *to += valuepair2str(*to, freespace, vp,
218                                      tmpda->type, func);
219                 return 1;
220         }
221
222         return 0;
223 }
224
225 /*
226  *  Decode an attribute name into a string.
227  */
228 static void decode_attribute(const char **from, char **to, int freespace,
229                              int *open, REQUEST *request,
230                              RADIUS_ESCAPE_STRING func)
231 {
232         char attrname[256];
233         const char *p;
234         char *q, *pa;
235         int stop=0, found=0, retlen=0;
236         int openbraces = *open;
237         struct xlat_cmp *c;
238
239         p = *from;
240         q = *to;
241         pa = &attrname[0];
242
243         *q = '\0';
244
245         /*
246          * Skip the '{' at the front of 'p'
247          * Increment open braces
248          */
249         p++;
250         openbraces++;
251
252         /*
253          *  Copy over the rest of the string.
254          */
255         while ((*p) && (!stop)) {
256                 switch(*p) {
257                         /*
258                          *  Allow braces inside things, too.
259                          */
260                         case '\\':
261                                 p++; /* skip it */
262                                 *pa++ = *p++;
263                                 break;
264
265                         case '{':
266                                 openbraces++;
267                                 *pa++ = *p++;
268                                 break;
269
270                         case '}':
271                                 openbraces--;
272                                 if (openbraces == *open) {
273                                         p++;
274                                         stop=1;
275                                 } else {
276                                         *pa++ = *p++;
277                                 }
278                                 break;
279
280                                 /*
281                                  *  Attr-Name1:-Attr-Name2
282                                  *
283                                  *  Use Attr-Name1, and if not found,
284                                  *  use Attr-Name2.
285                                  */
286                         case ':':
287                                 if (p[1] == '-') {
288                                         p += 2;
289                                         stop = 1;
290                                         break;
291                                 }
292                                 /* else FALL-THROUGH */
293
294                         default:
295                                 *pa++ = *p++;
296                                 break;
297                 }
298         }
299         *pa = '\0';
300
301         /*
302          *      Find an attribute from the reply.
303          */
304         if (strncasecmp(attrname,"reply:",6) == 0) {
305                 found = decode_attr_packet(&attrname[6], &q, freespace,
306                                            request->reply, func);
307
308                 /*
309                  *      Find an attribute from the request.
310                  */
311         } else if (strncasecmp(attrname,"request:",8) == 0) {
312                 found = decode_attr_packet(&attrname[8], &q, freespace,
313                                            request->packet, func);
314
315                 /*
316                  *      Find an attribute from the config items.
317                  */
318         } else if (strncasecmp(attrname,"check:",6) == 0) {
319                 found = decode_attr_vps(&attrname[6], &q, freespace,
320                                            request->config_items, func);
321
322                 /*
323                  *      Find an attribute from the proxy request.
324                  */
325         } else if ((strncasecmp(attrname,"proxy-request:",14) == 0) &&
326                    (request->proxy_reply != NULL)) {
327                 found = decode_attr_packet(&attrname[14], &q, freespace,
328                                            request->proxy, func);
329
330                 /*
331                  *      Find an attribute from the proxy reply.
332                  */
333         } else if ((strncasecmp(attrname,"proxy-reply:",12) == 0) &&
334                    (request->proxy_reply != NULL)) {
335                 found = decode_attr_packet(&attrname[12], &q, freespace,
336                                            request->proxy_reply, func);
337
338                 /*
339                  *      Find a string from a registered function.
340                  */
341         } else if ((c = find_xlat_func(attrname)) != NULL) {
342                 DEBUG("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
343                       c->module, attrname+ c->length + 1);
344                 retlen = c->do_xlat(c->instance, request, attrname+(c->length+1), q, freespace, func);
345                 /* If retlen is 0, treat it as not found */
346                 if (retlen == 0) {
347                         found = 0;
348                 } else {
349                         found = 1;
350                         q += retlen;
351                 }
352
353                 /*
354                  *      Nothing else, it MUST be a bare attribute name.
355                  */
356         } else if (decode_attr_packet(attrname, &q, freespace, request->packet, func)) {
357                 found = 1;
358
359         } else if (dict_attrbyname(attrname) == NULL) {
360                 /*
361                  *      No attribute by that name, return an error.
362                  */
363                 DEBUG2("WARNING: Attempt to use unknown xlat function, or non-existent attribute in string %%{%s}", attrname);
364
365         } /* else the attribute is known, but not in the request */
366
367         /*
368          * Skip to last '}' if attr is found
369          * The rest of the stuff within the braces is
370          * useless if we found what we need
371          */
372         if (found) {
373                 while((*p != '\0') && (openbraces > 0)) {
374                         switch (*p) {
375                         default:
376                                 break;
377
378                                 /*
379                                  *  Ensure that escaped braces are allowed.
380                                  */
381                         case '\\':
382                                 p++; /* skip the escaped character */
383                                 break;
384
385                                 /*
386                                  *  Bare brace
387                                  */
388                         case '{':
389                                 openbraces++;
390                                 break;
391
392                         case '}':
393                                 openbraces--;
394                                 break;
395                         }
396                         p++;    /* skip the character */
397                 }
398         }
399
400         *open = openbraces;
401         *from = p;
402         *to = q;
403 }
404
405 /*
406  *  If the caller doesn't pass xlat an escape function, then
407  *  we use this one.  It simplifies the coding, as the check for
408  *  func == NULL only happens once.
409  */
410 static int xlat_copy(char *out, int outlen, const char *in)
411 {
412         int len = 0;
413
414         while (*in) {
415                 /*
416                  *  Truncate, if too much.
417                  */
418                 if (len >= outlen) {
419                         break;
420                 }
421
422                 /*
423                  *  Copy data.
424                  *
425                  *  FIXME: Do escaping of bad stuff!
426                  */
427                 *out = *in;
428
429                 out++;
430                 in++;
431                 len++;
432         }
433
434         *out = '\0';
435         return len;
436 }
437
438 /*
439  *      Replace %<whatever> in a string.
440  *
441  *      See 'doc/variables.txt' for more information.
442  */
443 int radius_xlat(char *out, int outlen, const char *fmt,
444                 REQUEST *request, RADIUS_ESCAPE_STRING func)
445 {
446         int i, c,freespace;
447         const char *p;
448         char *q;
449         VALUE_PAIR *tmp;
450         struct tm *TM, s_TM;
451         char tmpdt[40]; /* For temporary storing of dates */
452         int openbraces=0;
453
454         /*
455          *  Ensure that we always have an escaping function.
456          */
457         if (func == NULL) {
458                 func = xlat_copy;
459         }
460
461         q = out;
462         p = fmt;
463         while (*p) {
464                 /* Calculate freespace in output */
465                 freespace = outlen - (q - out);
466                 if (freespace <= 1)
467                         break;
468                 c = *p;
469
470                 if ((c != '%') && (c != '$') && (c != '\\')) {
471                         /*
472                          * We check if we're inside an open brace.  If we are
473                          * then we assume this brace is NOT literal, but is
474                          * a closing brace and apply it
475                          */
476                         if ((c == '}') && openbraces) {
477                                 openbraces--;
478                                 p++; /* skip it */
479                                 continue;
480                         }
481                         *q++ = *p++;
482                         continue;
483                 }
484
485                 /*
486                  *      There's nothing after this character, copy
487                  *      the last '%' or "$' or '\\' over to the output
488                  *      buffer, and exit.
489                  */
490                 if (*++p == '\0') {
491                         *q++ = c;
492                         break;
493                 }
494
495                 if (c == '\\') {
496                         switch(*p) {
497                         case '\\':
498                                 *q++ = *p;
499                                 break;
500                         case 't':
501                                 *q++ = '\t';
502                                 break;
503                         case 'n':
504                                 *q++ = '\n';
505                                 break;
506                         default:
507                                 *q++ = c;
508                                 *q++ = *p;
509                                 break;
510                         }
511                         p++;
512                 } else if (c == '$') switch(*p) {
513                         case '{': /* Attribute by Name */
514                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
515                                 break;
516                         default:
517                                 *q++ = c;
518                                 *q++ = *p++;
519                                 break;
520
521                 } else if (c == '%') switch(*p) {
522                         case '{':
523                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
524                                 break;
525
526                         case '%':
527                                 *q++ = *p++;
528                                 break;
529                         case 'a': /* Protocol: */
530                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
531                                 p++;
532                                 break;
533                         case 'c': /* Callback-Number */
534                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
535                                 p++;
536                                 break;
537                         case 'd': /* request day */
538                                 TM = localtime_r(&request->timestamp, &s_TM);
539                                 strftime(tmpdt,sizeof(tmpdt),"%d",TM);
540                                 strNcpy(q,tmpdt,freespace);
541                                 q += strlen(q);
542                                 p++;
543                                 break;
544                         case 'f': /* Framed IP address */
545                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
546                                 p++;
547                                 break;
548                         case 'i': /* Calling station ID */
549                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
550                                 p++;
551                                 break;
552                         case 'l': /* request timestamp */
553                                 snprintf(tmpdt, sizeof(tmpdt), "%lu",
554                                          (unsigned long) request->timestamp);
555                                 strNcpy(q,tmpdt,freespace);
556                                 q += strlen(q);
557                                 p++;
558                                 break;
559                         case 'm': /* request month */
560                                 TM = localtime_r(&request->timestamp, &s_TM);
561                                 strftime(tmpdt,sizeof(tmpdt),"%m",TM);
562                                 strNcpy(q,tmpdt,freespace);
563                                 q += strlen(q);
564                                 p++;
565                                 break;
566                         case 'n': /* NAS IP address */
567                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
568                                 p++;
569                                 break;
570                         case 'p': /* Port number */
571                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
572                                 p++;
573                                 break;
574                         case 's': /* Speed */
575                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
576                                 p++;
577                                 break;
578                         case 't': /* request timestamp */
579                                 CTIME_R(&request->timestamp, q, freespace);
580                                 q += strlen(q);
581                                 p++;
582                                 break;
583                         case 'u': /* User name */
584                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
585                                 p++;
586                                 break;
587                         case 'A': /* radacct_dir */
588                                 strNcpy(q,radacct_dir,freespace-1);
589                                 q += strlen(q);
590                                 p++;
591                                 break;
592                         case 'C': /* ClientName */
593                                 strNcpy(q,client_name(request->packet->src_ipaddr),freespace-1);
594                                 q += strlen(q);
595                                 p++;
596                                 break;
597                         case 'D': /* request date */
598                                 TM = localtime_r(&request->timestamp, &s_TM);
599                                 strftime(tmpdt,sizeof(tmpdt),"%Y%m%d",TM);
600                                 strNcpy(q,tmpdt,freespace);
601                                 q += strlen(q);
602                                 p++;
603                                 break;
604                         case 'H': /* request hour */
605                                 TM = localtime_r(&request->timestamp, &s_TM);
606                                 strftime(tmpdt,sizeof(tmpdt),"%H",TM);
607                                 strNcpy(q,tmpdt,freespace);
608                                 q += strlen(q);
609                                 p++;
610                                 break;
611                         case 'L': /* radlog_dir */
612                                 strNcpy(q,radlog_dir,freespace-1);
613                                 q += strlen(q);
614                                 p++;
615                                 break;
616                         case 'M': /* MTU */
617                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
618                                 p++;
619                                 break;
620                         case 'R': /* radius_dir */
621                                 strNcpy(q,radius_dir,freespace-1);
622                                 q += strlen(q);
623                                 p++;
624                                 break;
625                         case 'S': /* request timestamp in SQL format*/
626                                 TM = localtime_r(&request->timestamp, &s_TM);
627                                 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d %H:%M:%S",TM);
628                                 strNcpy(q,tmpdt,freespace);
629                                 q += strlen(q);
630                                 p++;
631                                 break;
632                         case 'T': /* request timestamp */
633                                 TM = localtime_r(&request->timestamp, &s_TM);
634                                 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d-%H.%M.%S.000000",TM);
635                                 strNcpy(q,tmpdt,freespace);
636                                 q += strlen(q);
637                                 p++;
638                                 break;
639                         case 'U': /* Stripped User name */
640                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
641                                 p++;
642                                 break;
643                         case 'V': /* Request-Authenticator */
644                                 if (request->packet->verified)
645                                         strNcpy(q,"Verified",freespace-1);
646                                 else
647                                         strNcpy(q,"None",freespace-1);
648                                 q += strlen(q);
649                                 p++;
650                                 break;
651                         case 'Y': /* request year */
652                                 TM = localtime_r(&request->timestamp, &s_TM);
653                                 strftime(tmpdt,sizeof(tmpdt),"%Y",TM);
654                                 strNcpy(q,tmpdt,freespace);
655                                 q += strlen(q);
656                                 p++;
657                                 break;
658                         case 'Z': /* Full request pairs except password */
659                                 tmp = request->packet->vps;
660                                 while (tmp && (freespace > 3)) {
661                                         if (tmp->attribute != PW_PASSWORD) {
662                                                 *q++ = '\t';
663                                                 i = vp_prints(q,freespace-2,tmp);
664                                                 q += i;
665                                                 freespace -= (i+2);
666                                                 *q++ = '\n';
667                                         }
668                                         tmp = tmp->next;
669                                 }
670                                 p++;
671                                 break;
672                         default:
673                                 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
674                                 if (freespace > 2) {
675                                         *q++ = '%';
676                                         *q++ = *p++;
677                                 }
678                                 break;
679                 }
680         }
681         *q = '\0';
682
683         DEBUG2("radius_xlat:  '%s'", out);
684
685         return strlen(out);
686 }