Require that the modules call talloc for their instance handle.
[freeradius.git] / src / modules / rlm_expr / rlm_expr.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15  
16 /**
17  * $Id$
18  * @file rlm_expr.c
19  * @brief Register many xlat expansions including the expr expansion.
20  *
21  * @copyright 2001,2006  The FreeRADIUS server project
22  * @copyright 2002  Alan DeKok <aland@ox.org>
23  */
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/md5.h>
29 #include <freeradius-devel/base64.h>
30 #include <freeradius-devel/modules.h>
31
32 #include <ctype.h>
33
34 #include "rlm_expr.h"
35
36 /*
37  *      Define a structure for our module configuration.
38  */
39 typedef struct rlm_expr_t {
40         char *xlat_name;
41         char *allowed_chars;
42 } rlm_expr_t;
43
44 static const CONF_PARSER module_config[] = {
45         {"safe-characters", PW_TYPE_STRING_PTR,
46          offsetof(rlm_expr_t, allowed_chars), NULL,
47         "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
48         {NULL, -1, 0, NULL, NULL}
49 };
50
51 typedef enum expr_token_t {
52   TOKEN_NONE = 0,
53   TOKEN_INTEGER,
54   TOKEN_ADD,
55   TOKEN_SUBTRACT,
56   TOKEN_DIVIDE,
57   TOKEN_REMAINDER,
58   TOKEN_MULTIPLY,
59   TOKEN_AND,
60   TOKEN_OR,
61   TOKEN_LAST
62 } expr_token_t;
63
64 typedef struct expr_map_t {
65         char op;
66         expr_token_t token;
67 } expr_map_t;
68
69 static expr_map_t map[] =
70 {
71         {'+',   TOKEN_ADD },
72         {'-',   TOKEN_SUBTRACT },
73         {'/',   TOKEN_DIVIDE },
74         {'*',   TOKEN_MULTIPLY },
75         {'%',   TOKEN_REMAINDER },
76         {'&',   TOKEN_AND },
77         {'|',   TOKEN_OR },
78         {0,     TOKEN_LAST}
79 };
80
81 /*
82  *      Lookup tables for randstr char classes
83  */
84 static char randstr_punc[] = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
85 static char randstr_salt[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz/.";
86
87 static int get_number(REQUEST *request, const char **string, int64_t *answer)
88 {
89         int             i, found;
90         int64_t         result;
91         int64_t         x;
92         const char      *p;
93         expr_token_t    this;
94
95         /*
96          *  Loop over the input.
97          */
98         result = 0;
99         this = TOKEN_NONE;
100
101         for (p = *string; *p != '\0'; /* nothing */) {
102                 if ((*p == ' ') ||
103                     (*p == '\t')) {
104                         p++;
105                         continue;
106                 }
107
108                 /*
109                  *  Discover which token it is.
110                  */
111                 found = FALSE;
112                 for (i = 0; map[i].token != TOKEN_LAST; i++) {
113                         if (*p == map[i].op) {
114                                 if (this != TOKEN_NONE) {
115                                         RDEBUG2("Invalid operator at \"%s\"", p);
116                                         return -1;
117                                 }
118                                 this = map[i].token;
119                                 p++;
120                                 found = TRUE;
121                                 break;
122                         }
123                 }
124
125                 /*
126                  *  Found the algebraic operator.  Get the next number.
127                  */
128                 if (found) {
129                         continue;
130                 }
131
132                 /*
133                  *  End of a group.  Stop.
134                  */
135                 if (*p == ')') {
136                         if (this != TOKEN_NONE) {
137                                 RDEBUG2("Trailing operator before end sub-expression at \"%s\"", p);
138                                 return -1;
139                         }
140                         p++;
141                         break;
142                 }
143
144                 /*
145                  *  Start of a group.  Call ourselves recursively.
146                  */
147                 if (*p == '(') {
148                         p++;
149
150                         found = get_number(request, &p, &x);
151                         if (found < 0) {
152                                 return -1;
153                         }
154                 } else {
155                         /*
156                          *  No algrebraic operator found, the next thing
157                          *  MUST be a number.
158                          *
159                          *  If it isn't, then we die.
160                          */
161                         if ((*p < '0') || (*p > '9')) {
162                                 RDEBUG2("Not a number at \"%s\"", p);
163                                 return -1;
164                         }
165
166                         /*
167                          *  This is doing it the hard way, but it also allows
168                          *  us to increment 'p'.
169                          */
170                         x = 0;
171                         while ((*p >= '0') && (*p <= '9')) {
172                                 x *= 10;
173                                 x += (*p - '0');
174                                 p++;
175                         }
176                 }
177
178                 switch (this) {
179                 default:
180                 case TOKEN_NONE:
181                         result = x;
182                         break;
183
184                 case TOKEN_ADD:
185                         result += x;
186                         break;
187
188                 case TOKEN_SUBTRACT:
189                         result -= x;
190                         break;
191
192                 case TOKEN_DIVIDE:
193                         if (x == 0) {
194                                 result = 0; /* we don't have NaN for integers */
195                                 break;
196                         }
197                         result /= x;
198                         break;
199
200                 case TOKEN_REMAINDER:
201                         if (x == 0) {
202                                 result = 0; /* we don't have NaN for integers */
203                                 break;
204                         }
205                         result %= x;
206                         break;
207
208                 case TOKEN_MULTIPLY:
209                         result *= x;
210                         break;
211
212                 case TOKEN_AND:
213                         result &= x;
214                         break;
215
216                 case TOKEN_OR:
217                         result |= x;
218                         break;
219                 }
220
221                 /*
222                  *  We've used this token.
223                  */
224                 this = TOKEN_NONE;
225         }
226
227         /*
228          *  And return the answer to the caller.
229          */
230         *string = p;
231         *answer = result;
232         return 0;
233 }
234
235 /*
236  *  Do xlat of strings!
237  */
238 static size_t expr_xlat(void *instance, REQUEST *request, const char *fmt,
239                         char *out, size_t outlen)
240 {
241         int             rcode;
242         int64_t         result;
243         rlm_expr_t      *inst = instance;
244         const           char *p;
245         char            buffer[256];
246
247         inst = inst;            /* -Wunused */
248
249         /*
250          * Do an xlat on the provided string (nice recursive operation).
251          */
252         if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
253                 radlog(L_ERR, "rlm_expr: xlat failed.");
254                 *out = '\0';
255                 return 0;
256         }
257
258         p = buffer;
259         rcode = get_number(request, &p, &result);
260         if (rcode < 0) {
261                 return 0;
262         }
263
264         /*
265          *  We MUST have eaten the entire input string.
266          */
267         if (*p != '\0') {
268                 RDEBUG2("Failed at %s", p);
269                 return 0;
270         }
271
272         snprintf(out, outlen, "%ld", (long int) result);
273         return strlen(out);
274 }
275
276 /**
277  *  @brief Generate a random integer value
278  *
279  */
280 static size_t rand_xlat(UNUSED void *instance, REQUEST *request, const char *fmt,
281                         char *out, size_t outlen)
282 {
283         int64_t         result;
284         char            buffer[256];
285
286         /*
287          * Do an xlat on the provided string (nice recursive operation).
288          */
289         if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
290                 radlog(L_ERR, "rlm_expr: xlat failed.");
291                 *out = '\0';
292                 return 0;
293         }
294
295         result = atoi(buffer);
296
297         /*
298          *      Too small or too big.
299          */
300         if (result <= 0) return 0;
301         if (result >= (1 << 30)) result = (1 << 30);
302
303         result *= fr_rand();    /* 0..2^32-1 */
304         result >>= 32;
305
306         snprintf(out, outlen, "%ld", (long int) result);
307         return strlen(out);
308 }
309
310 /**
311  *  @brief Generate a string of random chars
312  *
313  *  Build strings of random chars, useful for generating tokens and passcodes
314  *  Format similar to String::Random.
315  */
316 static size_t randstr_xlat(UNUSED void *instance, REQUEST *request,
317                            const char *fmt, char *out, size_t outlen)
318 {
319         char            *p;
320         char            buffer[1024];
321         unsigned int    result;
322         size_t          freespace = outlen;
323         size_t          len;
324         
325         if (outlen <= 1) return 0;
326
327         /*
328          * Do an xlat on the provided string (nice recursive operation).
329          */
330         len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
331         if (!len) {
332                 radlog(L_ERR, "rlm_expr: xlat failed.");
333                 *out = '\0';
334                 return 0;
335         }
336         
337         p = buffer;
338         while ((len-- > 0) && (--freespace > 0)) {
339                 result = fr_rand();
340                 switch (*p) {
341                         /*
342                          *  Lowercase letters
343                          */
344                         case 'c':
345                                 *out++ = 'a' + (result % 26);
346                         break;
347                         
348                         /*
349                          *  Uppercase letters
350                          */
351                         case 'C':
352                                 *out++ = 'A' + (result % 26);
353                         break;
354                         
355                         /*
356                          *  Numbers
357                          */
358                         case 'n':
359                                 *out++ = '0' + (result % 10);
360                         break;
361                         
362                         /*
363                          *  Alpha numeric
364                          */
365                         case 'a':
366                                 *out++ = randstr_salt[result % (sizeof(randstr_salt) - 3)];
367                         break;
368                         
369                         /*
370                          *  Punctuation
371                          */
372                         case '!':
373                                 *out++ = randstr_punc[result % (sizeof(randstr_punc) - 1)];
374                         break;
375                         
376                         /*
377                          *  Alpa numeric + punctuation
378                          */
379                         case '.':
380                                 *out++ = '!' + (result % 95);
381                         break;
382                         
383                         /*
384                          *  Alpha numeric + salt chars './'
385                          */     
386                         case 's':
387                                 *out++ = randstr_salt[result % (sizeof(randstr_salt) - 1)];
388                         break;
389                         
390                         /*
391                          *  Binary data as hexits (we don't really support 
392                          *  non printable chars).
393                          */
394                         case 'h':
395                                 if (freespace < 2)
396                                         break;
397                                 
398                                 snprintf(out, 3, "%02x", result % 256);
399                                 
400                                 /* Already decremented */
401                                 freespace -= 1;
402                                 out += 2;
403                         break;
404                         
405                         default:
406                                 radlog(L_ERR,
407                                        "rlm_expr: invalid character class '%c'",
408                                        *p);
409                                        
410                                 return 0;
411                         break;
412                 }
413         
414                 p++;
415         }
416         
417         *out++ = '\0';
418         
419         return outlen - freespace;
420 }
421
422 /**
423  * @brief URLencode special characters 
424  *
425  * Example: "%{urlquote:http://example.org/}" == "http%3A%47%47example.org%47"
426  */
427 static size_t urlquote_xlat(UNUSED void *instance, REQUEST *request,
428                             const char *fmt, char *out, size_t outlen)
429 {
430         char    *p;
431         char    buffer[1024];
432         size_t  freespace = outlen;
433         size_t  len;
434         
435         if (outlen <= 1) return 0;
436
437         len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
438         if (!len) {
439                 radlog(L_ERR, "rlm_expr: xlat failed.");
440                 *out = '\0';
441                 return 0;
442         }
443
444         p = buffer;
445         while ((len-- > 0) && (--freespace > 0)) {
446                 if (isalnum(*p)) {
447                         *out++ = *p++;
448                         continue;
449                 }
450
451                 switch (*p) {
452                         case '-':
453                         case '_':
454                         case '.':
455                         case '~':
456                                 *out++ = *p++;
457                                 break;
458                         default:
459                                 if (freespace < 3)
460                                         break;
461                                 
462                                 snprintf(out, 4, "%%%02x", *p++); /* %xx */
463                                 
464                                 /* Already decremented */
465                                 freespace -= 2;
466                                 out += 3;
467                 }
468         }
469
470         *out = '\0';
471
472         return outlen - freespace;
473 }
474
475 /**
476  * @brief Equivalent to the old safe-characters functionality in rlm_sql
477  *
478  * Example: "%{escape:<img>foo.jpg</img>}" == "=60img=62foo.jpg=60=/img=62"
479  */
480 static size_t escape_xlat(UNUSED void *instance, REQUEST *request,
481                           const char *fmt, char *out, size_t outlen)
482 {
483         rlm_expr_t *inst = instance;
484         char    *p;
485         char    buffer[1024];
486         size_t  freespace = outlen;
487         size_t  len;
488         
489         if (outlen <= 1) return 0;
490
491         len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
492         if (!len) {
493                 radlog(L_ERR, "rlm_expr: xlat failed.");
494                 *out = '\0';
495                 return 0;
496         }
497
498         p = buffer;
499         while ((len-- > 0) && (--freespace > 0)) {
500                 /*
501                  *      Non-printable characters get replaced with their
502                  *      mime-encoded equivalents.
503                  */
504                 if ((*p > 31) && strchr(inst->allowed_chars, *p)) {
505                         *out++ = *p++;
506                         continue;
507                 }
508                 
509                 if (freespace < 3)
510                         break;
511
512                 snprintf(out, 4, "=%02X", *p++);
513                 
514                 /* Already decremented */
515                 freespace -= 2;
516                 out += 3;
517         }
518         
519         *out = '\0';
520         
521         return outlen - freespace;
522 }
523
524 /**
525  * @brief Convert a string to lowercase
526  *
527  * Example "%{lc:Bar}" == "bar"
528  *
529  * Probably only works for ASCII
530  */
531 static size_t lc_xlat(UNUSED void *instance, REQUEST *request,
532                       const char *fmt, char *out, size_t outlen)
533 {
534         char *p, *q;
535         char buffer[1024];
536
537         if (outlen <= 1) return 0;
538
539         if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
540                 *out = '\0';
541                 return 0;
542         }
543
544         for (p = buffer, q = out; *p != '\0'; p++, outlen--) {
545                 if (outlen <= 1) break;
546
547                 *(q++) = tolower((int) *p);
548         }
549
550         *q = '\0';
551
552         return strlen(out);
553 }
554
555 /**
556  * @brief Convert a string to uppercase
557  *
558  * Example: "%{uc:Foo}" == "FOO"
559  *
560  * Probably only works for ASCII
561  */
562 static size_t uc_xlat(UNUSED void *instance, REQUEST *request,
563                       const char *fmt, char *out, size_t outlen)
564 {
565         char *p, *q;
566         char buffer[1024];
567
568         if (outlen <= 1) return 0;
569
570         if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
571                 *out = '\0';
572                 return 0;
573         }
574
575         for (p = buffer, q = out; *p != '\0'; p++, outlen--) {
576                 if (outlen <= 1) break;
577
578                 *(q++) = toupper((int) *p);
579         }
580
581         *q = '\0';
582
583         return strlen(out);
584 }
585
586 /**
587  * @brief Calculate the MD5 hash of a string.
588  *
589  * Example: "%{md5:foo}" == "acbd18db4cc2f85cedef654fccc4a4d8"
590  */
591 static size_t md5_xlat(UNUSED void *instance, REQUEST *request,
592                        const char *fmt, char *out, size_t outlen)
593 {
594         char buffer[1024];
595         uint8_t digest[16];
596         int i;
597         FR_MD5_CTX ctx;
598
599         if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
600                 *out = '\0';
601                 return 0;
602         }
603
604         fr_MD5Init(&ctx);
605         fr_MD5Update(&ctx, (void *) buffer, strlen(buffer));
606         fr_MD5Final(digest, &ctx);
607
608         if (outlen < 33) {
609                 snprintf(out, outlen, "md5_overflow");
610                 return strlen(out);
611         }
612
613         for (i = 0; i < 16; i++) {
614                 snprintf(out + i * 2, 3, "%02x", digest[i]);
615         }
616
617         return strlen(out);
618 }
619
620 /**
621  * @brief Encode string as base64
622  *
623  * Example: "%{tobase64:foo}" == "Zm9v"
624  */
625 static size_t base64_xlat(UNUSED void *instance, REQUEST *request,
626                           const char *fmt, char *out, size_t outlen)
627 {
628         size_t len;
629         char buffer[1024];
630
631         len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
632         
633         /* 
634          *  We can accurately calculate the length of the output string
635          *  if it's larger than outlen, the output would be useless so abort.
636          */
637         if (!len || ((FR_BASE64_ENC_LENGTH(len) + 1) > outlen)) {
638                 radlog(L_ERR, "rlm_expr: xlat failed.");
639                 *out = '\0';
640                 return 0;
641         }
642         
643         fr_base64_encode((uint8_t *) buffer, len, out, outlen);
644
645         return strlen(out);
646 }
647
648 /**
649  * @brief Convert base64 to hex
650  *
651  * Example: "%{base64tohex:Zm9v}" == "666f6f"
652  */
653 static size_t base64_to_hex_xlat(UNUSED void *instance, REQUEST *request,
654                                  const char *fmt, char *out, size_t outlen)
655 {       
656         char *p;
657         
658         char buffer[1024];
659         char decbuf[1024];
660         
661         size_t declen = sizeof(decbuf);
662         size_t freespace = outlen;
663         size_t len;
664
665         len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
666         
667         if (!len) {
668                 radlog(L_ERR, "rlm_expr: xlat failed.");
669                 *out = '\0';
670                 return 0;
671         }
672         
673         if (!fr_base64_decode(buffer, len, decbuf, &declen)) {
674                 radlog(L_ERR, "rlm_expr: base64 string invalid");
675                 *out = '\0';
676                 return 0;
677         }
678         
679         p = decbuf;
680         while ((declen-- > 0) && (--freespace > 0)) {
681                 if (freespace < 3)
682                         break;
683
684                 snprintf(out, 3, "%02x", *p++);
685                 
686                 /* Already decremented */
687                 freespace -= 1;
688                 out += 2;
689         }
690
691         return outlen - freespace;
692 }
693
694
695 /*
696  * Detach a instance free all ..
697  */
698 static int expr_detach(void *instance)
699 {
700         rlm_expr_t      *inst = instance;
701
702         xlat_unregister(inst->xlat_name, expr_xlat, instance);
703         pair_builtincompare_detach();
704         return 0;
705 }
706
707 /*
708  *      Do any per-module initialization that is separate to each
709  *      configured instance of the module.  e.g. set up connections
710  *      to external databases, read configuration files, set up
711  *      dictionary entries, etc.
712  *
713  *      If configuration information is given in the config section
714  *      that must be referenced in later calls, store a handle to it
715  *      in *instance otherwise put a null pointer there.
716  */
717 static int expr_instantiate(CONF_SECTION *conf, void **instance)
718 {
719         rlm_expr_t      *inst;
720
721         /*
722          *      Set up a storage area for instance data
723          */
724         *instance = inst = talloc_zero(conf, rlm_expr_t);
725         if (!inst) return -1;
726         
727         if (cf_section_parse(conf, inst, module_config) < 0) {
728                 expr_detach(inst);
729                 return -1;
730         }
731
732         inst->xlat_name = cf_section_name2(conf);
733         if (!inst->xlat_name) {
734                 inst->xlat_name = cf_section_name1(conf);
735         }
736         xlat_register(inst->xlat_name, expr_xlat, inst);
737
738         xlat_register("rand", rand_xlat, inst);
739         xlat_register("randstr", randstr_xlat, inst);
740         xlat_register("urlquote", urlquote_xlat, inst);
741         xlat_register("escape", escape_xlat, inst);
742         xlat_register("tolower", lc_xlat, inst);
743         xlat_register("toupper", uc_xlat, inst);
744         xlat_register("md5", md5_xlat, inst);
745         xlat_register("tobase64", base64_xlat, inst);
746         xlat_register("base64tohex", base64_to_hex_xlat, inst);
747
748         /*
749          *      Initialize various paircompare functions
750          */
751         pair_builtincompare_init();
752         return 0;
753 }
754
755 /*
756  *      The module name should be the only globally exported symbol.
757  *      That is, everything else should be 'static'.
758  *
759  *      If the module needs to temporarily modify it's instantiation
760  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
761  *      The server will then take care of ensuring that the module
762  *      is single-threaded.
763  */
764 module_t rlm_expr = {
765         RLM_MODULE_INIT,
766         "expr",                         /* Name */
767         RLM_TYPE_CHECK_CONFIG_SAFE,     /* type */
768         expr_instantiate,               /* instantiation */
769         expr_detach,                    /* detach */
770         {
771                 NULL,                   /* authentication */
772                 NULL,                   /* authorization */
773                 NULL,                   /* pre-accounting */
774                 NULL                    /* accounting */
775         },
776 };