Don't use strncpy
[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;
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                 q += c->do_xlat(c->instance, request, attrname+(c->length+1), q, freespace, func);
345                 found = 1;
346
347                 /*
348                  *      Nothing else, it MUST be a bare attribute name.
349                  */
350         } else if (decode_attr_packet(attrname, &q, freespace, request->packet, func)) {
351                 found = 1;
352
353         } else if (dict_attrbyname(attrname) == NULL) {
354                 /*
355                  *      No attribute by that name, return an error.
356                  */
357                 DEBUG2("WARNING: Attempt to use unknown xlat function, or non-existent attribute in string %%{%s}", attrname);
358
359         } /* else the attribute is known, but not in the request */
360
361         /*
362          * Skip to last '}' if attr is found
363          * The rest of the stuff within the braces is
364          * useless if we found what we need
365          */
366         if (found) {
367                 while((*p != '\0') && (openbraces > 0)) {
368                         switch (*p) {
369                         default:
370                                 break;
371
372                                 /*
373                                  *  Ensure that escaped braces are allowed.
374                                  */
375                         case '\\':
376                                 p++; /* skip the escaped character */
377                                 break;
378
379                                 /*
380                                  *  Bare brace
381                                  */
382                         case '{':
383                                 openbraces++;
384                                 break;
385
386                         case '}':
387                                 openbraces--;
388                                 break;
389                         }
390                         p++;    /* skip the character */
391                 }
392         }
393
394         *open = openbraces;
395         *from = p;
396         *to = q;
397 }
398
399 /*
400  *  If the caller doesn't pass xlat an escape function, then
401  *  we use this one.  It simplifies the coding, as the check for
402  *  func == NULL only happens once.
403  */
404 static int xlat_copy(char *out, int outlen, const char *in)
405 {
406         int len = 0;
407
408         while (*in) {
409                 /*
410                  *  Truncate, if too much.
411                  */
412                 if (len >= outlen) {
413                         break;
414                 }
415
416                 /*
417                  *  Copy data.
418                  *
419                  *  FIXME: Do escaping of bad stuff!
420                  */
421                 *out = *in;
422
423                 out++;
424                 in++;
425                 len++;
426         }
427
428         *out = '\0';
429         return len;
430 }
431
432 /*
433  *      Replace %<whatever> in a string.
434  *
435  *      See 'doc/variables.txt' for more information.
436  */
437 int radius_xlat(char *out, int outlen, const char *fmt,
438                 REQUEST *request, RADIUS_ESCAPE_STRING func)
439 {
440         int i, c,freespace;
441         const char *p;
442         char *q;
443         VALUE_PAIR *tmp;
444         struct tm *TM, s_TM;
445         char tmpdt[40]; /* For temporary storing of dates */
446         int openbraces=0;
447
448         /*
449          *  Ensure that we always have an escaping function.
450          */
451         if (func == NULL) {
452                 func = xlat_copy;
453         }
454
455         q = out;
456         p = fmt;
457         while (*p) {
458                 /* Calculate freespace in output */
459                 freespace = outlen - (q - out);
460                 if (freespace <= 1)
461                         break;
462                 c = *p;
463
464                 if ((c != '%') && (c != '$') && (c != '\\')) {
465                         /*
466                          * We check if we're inside an open brace.  If we are
467                          * then we assume this brace is NOT literal, but is
468                          * a closing brace and apply it 
469                          */
470                         if ((c == '}') && openbraces) {
471                                 openbraces--;
472                                 p++; /* skip it */
473                                 continue;
474                         }
475                         *q++ = *p++;
476                         continue;
477                 }
478
479                 /*
480                  *      There's nothing after this character, copy
481                  *      the last '%' or "$' or '\\' over to the output
482                  *      buffer, and exit.
483                  */
484                 if (*++p == '\0') {
485                         *q++ = c;
486                         break;
487                 }
488
489                 if (c == '\\') {
490                         switch(*p) {
491                         case '\\':
492                                 *q++ = *p;
493                                 break;
494                         case 't':
495                                 *q++ = '\t';
496                                 break;
497                         case 'n':
498                                 *q++ = '\n';
499                                 break;
500                         default:
501                                 *q++ = c;
502                                 *q++ = *p;
503                                 break;
504                         }
505                         p++;
506                 } else if (c == '$') switch(*p) {
507                         case '{': /* Attribute by Name */
508                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
509                                 break;
510                         default:
511                                 *q++ = c;
512                                 *q++ = *p++;
513                                 break;
514
515                 } else if (c == '%') switch(*p) {
516                         case '{':
517                                 decode_attribute(&p, &q, freespace, &openbraces, request, func);
518                                 break;
519
520                         case '%':
521                                 *q++ = *p++;
522                                 break;
523                         case 'a': /* Protocol: */
524                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
525                                 p++;
526                                 break;
527                         case 'c': /* Callback-Number */
528                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
529                                 p++;
530                                 break;
531                         case 'd': /* request day */
532                                 TM = localtime_r(&request->timestamp, &s_TM);
533                                 strftime(tmpdt,sizeof(tmpdt),"%d",TM);
534                                 strNcpy(q,tmpdt,freespace);
535                                 q += strlen(q);
536                                 p++;
537                                 break;
538                         case 'f': /* Framed IP address */
539                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
540                                 p++;
541                                 break;
542                         case 'i': /* Calling station ID */
543                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
544                                 p++;
545                                 break;
546                         case 'l': /* request timestamp */
547                                 snprintf(tmpdt, sizeof(tmpdt), "%lu",
548                                          (unsigned long) request->timestamp);
549                                 strNcpy(q,tmpdt,freespace);
550                                 q += strlen(q);
551                                 p++;
552                                 break;
553                         case 'm': /* request month */
554                                 TM = localtime_r(&request->timestamp, &s_TM);
555                                 strftime(tmpdt,sizeof(tmpdt),"%m",TM);
556                                 strNcpy(q,tmpdt,freespace);
557                                 q += strlen(q);
558                                 p++;
559                                 break;
560                         case 'n': /* NAS IP address */
561                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
562                                 p++;
563                                 break;
564                         case 'p': /* Port number */
565                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
566                                 p++;
567                                 break;
568                         case 's': /* Speed */
569                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
570                                 p++;
571                                 break;
572                         case 't': /* request timestamp */
573                                 CTIME_R(&request->timestamp, q, freespace);
574                                 q += strlen(q);
575                                 p++;
576                                 break;
577                         case 'u': /* User name */
578                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
579                                 p++;
580                                 break;
581                         case 'A': /* radacct_dir */
582                                 strNcpy(q,radacct_dir,freespace-1);
583                                 q += strlen(q);
584                                 p++;
585                                 break;
586                         case 'C': /* ClientName */
587                                 strNcpy(q,client_name(request->packet->src_ipaddr),freespace-1);
588                                 q += strlen(q);
589                                 p++;
590                                 break;
591                         case 'D': /* request date */
592                                 TM = localtime_r(&request->timestamp, &s_TM);
593                                 strftime(tmpdt,sizeof(tmpdt),"%Y%m%d",TM);
594                                 strNcpy(q,tmpdt,freespace);
595                                 q += strlen(q);
596                                 p++;
597                                 break;
598                         case 'H': /* request hour */
599                                 TM = localtime_r(&request->timestamp, &s_TM);
600                                 strftime(tmpdt,sizeof(tmpdt),"%H",TM);
601                                 strNcpy(q,tmpdt,freespace);
602                                 q += strlen(q);
603                                 p++;
604                                 break;
605                         case 'L': /* radlog_dir */
606                                 strNcpy(q,radlog_dir,freespace-1);
607                                 q += strlen(q);
608                                 p++;
609                                 break;
610                         case 'M': /* MTU */
611                                 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
612                                 p++;
613                                 break;
614                         case 'R': /* radius_dir */
615                                 strNcpy(q,radius_dir,freespace-1);
616                                 q += strlen(q);
617                                 p++;
618                                 break;
619                         case 'S': /* request timestamp in SQL format*/
620                                 TM = localtime_r(&request->timestamp, &s_TM);
621                                 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d %H:%M:%S",TM);
622                                 strNcpy(q,tmpdt,freespace);
623                                 q += strlen(q);
624                                 p++;
625                                 break;
626                         case 'T': /* request timestamp */
627                                 TM = localtime_r(&request->timestamp, &s_TM);
628                                 strftime(tmpdt,sizeof(tmpdt),"%Y-%m-%d-%H.%M.%S.000000",TM);
629                                 strNcpy(q,tmpdt,freespace);
630                                 q += strlen(q);
631                                 p++;
632                                 break;
633                         case 'U': /* Stripped User name */
634                                 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
635                                 p++;
636                                 break;
637                         case 'V': /* Request-Authenticator */
638                                 if (request->packet->verified)
639                                         strNcpy(q,"Verified",freespace-1);
640                                 else
641                                         strNcpy(q,"None",freespace-1);
642                                 q += strlen(q);
643                                 p++;
644                                 break;
645                         case 'Y': /* request year */
646                                 TM = localtime_r(&request->timestamp, &s_TM);
647                                 strftime(tmpdt,sizeof(tmpdt),"%Y",TM);
648                                 strNcpy(q,tmpdt,freespace);
649                                 q += strlen(q);
650                                 p++;
651                                 break;
652                         case 'Z': /* Full request pairs except password */
653                                 tmp = request->packet->vps;
654                                 while (tmp && (freespace > 3)) {
655                                         if (tmp->attribute != PW_PASSWORD) {
656                                                 *q++ = '\t';
657                                                 i = vp_prints(q,freespace-2,tmp);
658                                                 q += i;
659                                                 freespace -= (i+2);
660                                                 *q++ = '\n';
661                                         }
662                                         tmp = tmp->next;
663                                 }
664                                 p++;
665                                 break;
666                         default:
667                                 DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
668                                 if (freespace > 2) {
669                                         *q++ = '%';
670                                         *q++ = *p++;
671                                 }
672                                 break;
673                 }
674         }
675         *q = '\0';
676
677         DEBUG2("radius_xlat:  '%s'", out);
678
679         return strlen(out);
680 }