Require that the modules call talloc for their instance handle.
[freeradius.git] / src / modules / rlm_attr_rewrite / rlm_attr_rewrite.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_attr_rewrite.c
19  * @brief Rewrite attribute values.
20  * 
21  * @copyright 2001,2006 The FreeRADIUS server project
22  * @copyright 2002  Kostas Kalevras <kkalev@noc.ntua.gr>
23  */
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29
30 #ifdef HAVE_REGEX_H
31 #       include <regex.h>
32 #endif
33
34 #define RLM_REGEX_INPACKET 0
35 #define RLM_REGEX_INCONFIG 1
36 #define RLM_REGEX_INREPLY  2
37 #define RLM_REGEX_INPROXY 3
38 #define RLM_REGEX_INPROXYREPLY 4
39
40 typedef struct rlm_attr_rewrite {
41         char *attribute;        //!< The attribute to search for.
42         const DICT_ATTR *da;    //!< The attribute definition.
43         char *search;           //!< The pattern to search for.
44         int search_len;         //!< The length of the search pattern.
45         char *searchin_str;     //!< The VALUE_PAIR list to search in. 
46                                 //!< Can be either packet, reply, proxy, 
47                                 //!< proxy_reply or control (plus it's alias 
48                                 //!< 'config').
49         char searchin;          //!< The same as above just coded as a number 
50                                 //!< for speed.
51         char *replace;          //!< The replacement.
52         int replace_len;        //!< The length of the replacement string.
53         int  append;            //!< Switch to control append mode (1,0).
54         int  nocase;            //!< Ignore case.
55         int  new_attr;          //!< Boolean. Whether we need to create a new 
56                                 //!< attr.
57         int  num_matches;       //!< Maximum number of matches.
58         const char *name;       //!< The module name.
59 } rlm_attr_rewrite_t;
60
61 static const CONF_PARSER module_config[] = {
62   { "attribute", PW_TYPE_STRING_PTR,
63     offsetof(rlm_attr_rewrite_t,attribute), NULL, NULL },
64   { "searchfor", PW_TYPE_STRING_PTR,
65     offsetof(rlm_attr_rewrite_t,search), NULL, NULL },
66   { "searchin",  PW_TYPE_STRING_PTR,
67     offsetof(rlm_attr_rewrite_t,searchin_str), NULL, "packet" },
68   { "replacewith", PW_TYPE_STRING_PTR,
69     offsetof(rlm_attr_rewrite_t,replace), NULL, NULL },
70   { "append", PW_TYPE_BOOLEAN,
71     offsetof(rlm_attr_rewrite_t,append),NULL, "no" },
72   { "ignore_case", PW_TYPE_BOOLEAN,
73     offsetof(rlm_attr_rewrite_t,nocase), NULL, "yes" },
74   { "new_attribute", PW_TYPE_BOOLEAN,
75     offsetof(rlm_attr_rewrite_t,new_attr), NULL, "no" },
76   { "max_matches", PW_TYPE_INTEGER,
77     offsetof(rlm_attr_rewrite_t,num_matches), NULL, "10" },
78   { NULL, -1, 0, NULL, NULL }
79 };
80
81 static int attr_rewrite_instantiate(CONF_SECTION *conf, void **instance)
82 {
83         rlm_attr_rewrite_t *inst;
84         const DICT_ATTR *dattr;
85
86         /*
87          *      Set up a storage area for instance data
88          */
89         *instance = inst = talloc_zero(conf, rlm_attr_rewrite_t);
90         if (!inst) {
91                 return -1;
92         }
93
94         /*
95          *      If the configuration parameters can't be parsed, then
96          *      fail.
97          */
98         if (cf_section_parse(conf, inst, module_config) < 0) {
99                 return -1;
100         }
101
102         /*
103          *      Discover the attribute number of the key.
104          */
105         if (inst->attribute == NULL) {
106                 radlog(L_ERR, "rlm_attr_rewrite: 'attribute' must be set.");
107                 return -1;
108         }
109         if (inst->search == NULL || inst->replace == NULL) {
110                 radlog(L_ERR, "rlm_attr_rewrite: search/replace strings must be set.");
111                 return -1;
112         }
113         inst->search_len = strlen(inst->search);
114         inst->replace_len = strlen(inst->replace);
115
116         if (inst->replace_len == 0 && inst->new_attr){
117                 radlog(L_ERR, "rlm_attr_rewrite: replace string must not be zero length in order to create new attribute.");
118                 return -1;
119         }
120
121         if (inst->num_matches < 1 || inst->num_matches > MAX_STRING_LEN) {
122                 radlog(L_ERR, "rlm_attr_rewrite: Illegal range for match number.");
123                 return -1;
124         }
125         if (inst->searchin_str == NULL) {
126                 radlog(L_ERR, "rlm_attr_rewrite: Illegal searchin directive given. Assuming packet.");
127                 inst->searchin = RLM_REGEX_INPACKET;
128         }
129         else{
130                 if (strcmp(inst->searchin_str, "packet") == 0)
131                         inst->searchin = RLM_REGEX_INPACKET;
132                 else if (strcmp(inst->searchin_str, "config") == 0)
133                         inst->searchin = RLM_REGEX_INCONFIG;
134                 else if (strcmp(inst->searchin_str, "control") == 0)
135                         inst->searchin = RLM_REGEX_INCONFIG;
136                 else if (strcmp(inst->searchin_str, "reply") == 0)
137                         inst->searchin = RLM_REGEX_INREPLY;
138 #ifdef WITH_PROXY
139                 else if (strcmp(inst->searchin_str, "proxy") == 0)
140                         inst->searchin = RLM_REGEX_INPROXY;
141                 else if (strcmp(inst->searchin_str, "proxy_reply") == 0)
142                         inst->searchin = RLM_REGEX_INPROXYREPLY;
143 #endif
144                 else {
145                         radlog(L_ERR, "rlm_attr_rewrite: Illegal searchin directive given. Assuming packet.");
146                         inst->searchin = RLM_REGEX_INPACKET;
147                 }
148         }
149         dattr = dict_attrbyname(inst->attribute);
150         if (dattr == NULL) {
151                 radlog(L_ERR, "rlm_attr_rewrite: No such attribute %s",
152                                 inst->attribute);
153                 return -1;
154         }
155         inst->da = dattr;
156         /* Add the module instance name */
157         inst->name = cf_section_name2(conf); /* may be NULL */
158
159         return 0;
160 }
161
162 static rlm_rcode_t do_attr_rewrite(void *instance, REQUEST *request)
163 {
164         rlm_attr_rewrite_t *inst = (rlm_attr_rewrite_t *) instance;
165         rlm_rcode_t rcode = RLM_MODULE_NOOP;
166         VALUE_PAIR *attr_vp = NULL;
167         VALUE_PAIR *tmp = NULL;
168         regex_t preg;
169         regmatch_t pmatch[9];
170         int cflags = 0;
171         int err = 0;
172         char done_xlat = 0;
173         unsigned int len = 0;
174         char err_msg[MAX_STRING_LEN];
175         unsigned int i = 0;
176         unsigned int j = 0;
177         unsigned int counter = 0;
178         char new_str[MAX_STRING_LEN];
179         char *ptr, *ptr2;
180         char search_STR[MAX_STRING_LEN];
181         char replace_STR[MAX_STRING_LEN];
182
183         if ((attr_vp = pairfind(request->config_items, PW_REWRITE_RULE, 0, TAG_ANY)) != NULL){
184                 if (inst->name == NULL || strcmp(inst->name,attr_vp->vp_strvalue))
185                         return RLM_MODULE_NOOP;
186         }
187
188         if (inst->new_attr){
189                 /* new_attribute = yes */
190                 if (!radius_xlat(replace_STR, sizeof(replace_STR), inst->replace, request, NULL, NULL)) {
191                         DEBUG2("%s: xlat on replace string failed.", inst->name);
192                         return rcode;
193                 }
194                 attr_vp = pairmake(inst->attribute,replace_STR,0);
195                 if (attr_vp == NULL){
196                         DEBUG2("%s: Could not add new attribute %s with value '%s'", inst->name,
197                                 inst->attribute,replace_STR);
198                         return rcode;
199                 }
200                 switch(inst->searchin){
201                         case RLM_REGEX_INPACKET:
202                                 pairadd(&request->packet->vps,attr_vp);
203                                 break;
204                         case RLM_REGEX_INCONFIG:
205                                 pairadd(&request->config_items,attr_vp);
206                                 break;
207                         case RLM_REGEX_INREPLY:
208                                 pairadd(&request->reply->vps,attr_vp);
209                                 break;
210 #ifdef WITH_PROXY
211                         case RLM_REGEX_INPROXY:
212                                 if (!request->proxy) {
213                                         pairbasicfree(attr_vp);
214                                         return RLM_MODULE_NOOP;
215                                 }
216                                 pairadd(&request->proxy->vps, attr_vp);
217                                 break;
218                         case RLM_REGEX_INPROXYREPLY:
219                                 if (!request->proxy_reply) {
220                                         pairbasicfree(attr_vp);
221                                         return RLM_MODULE_NOOP;
222                                 }
223                                 pairadd(&request->proxy_reply->vps, attr_vp);
224                                 break;
225 #endif
226                         default:
227                                 radlog(L_ERR, "%s: Illegal value for searchin. Changing to packet.", inst->name);
228                                 inst->searchin = RLM_REGEX_INPACKET;
229                                 pairadd(&request->packet->vps,attr_vp);
230                                 break;
231                 }
232                 DEBUG2("%s: Added attribute %s with value '%s'", inst->name,inst->attribute,replace_STR);
233                 rcode = RLM_MODULE_OK;
234         } else {
235                 int replace_len = 0;
236
237                 /* new_attribute = no */
238                 switch (inst->searchin) {
239                         case RLM_REGEX_INPACKET:
240                                 if (!inst->da->vendor && (inst->da->attr == PW_USER_NAME))
241                                         attr_vp = request->username;
242                                 else if (!inst->da->vendor && (inst->da->attr == PW_USER_PASSWORD))
243                                         attr_vp = request->password;
244                                 else
245                                         tmp = request->packet->vps;
246                                 break;
247                         case RLM_REGEX_INCONFIG:
248                                 tmp = request->config_items;
249                                 break;
250                         case RLM_REGEX_INREPLY:
251                                 tmp = request->reply->vps;
252                                 break;
253 #ifdef WITH_PROXY
254                         case RLM_REGEX_INPROXYREPLY:
255                                 if (!request->proxy_reply)
256                                         return RLM_MODULE_NOOP;
257                                 tmp = request->proxy_reply->vps;
258                                 break;
259                         case RLM_REGEX_INPROXY:
260                                 if (!request->proxy)
261                                         return RLM_MODULE_NOOP;
262                                 tmp = request->proxy->vps;
263                                 break;
264 #endif
265                         default:
266                                 radlog(L_ERR, "%s: Illegal value for searchin. Changing to packet.", inst->name);
267                                 inst->searchin = RLM_REGEX_INPACKET;
268                                 attr_vp = pairfind(request->packet->vps, inst->da->attr, inst->da->vendor, TAG_ANY);
269                                 break;
270                 }
271 do_again:
272                 if (tmp != NULL)
273                         attr_vp = pairfind(tmp, inst->da->attr, inst->da->vendor, TAG_ANY);
274                 if (attr_vp == NULL) {
275                         DEBUG2("%s: Could not find value pair for attribute %s", inst->name,inst->attribute);
276                         return rcode;
277                 }
278                 if (attr_vp->length == 0){
279                         DEBUG2("%s: Attribute %s string value NULL or of zero length", inst->name,inst->attribute);
280                         return rcode;
281                 }
282                 cflags |= REG_EXTENDED;
283                 if (inst->nocase)
284                         cflags |= REG_ICASE;
285
286                 if (!radius_xlat(search_STR, sizeof(search_STR), inst->search, request, NULL, NULL) && inst->search_len != 0) {
287                         DEBUG2("%s: xlat on search string failed.", inst->name);
288                         return rcode;
289                 }
290
291                 if ((err = regcomp(&preg,search_STR,cflags))) {
292                         regerror(err, &preg, err_msg, MAX_STRING_LEN);
293                         DEBUG2("%s: regcomp() returned error: %s", inst->name,err_msg);
294                         return rcode;
295                 }
296
297                 if ((attr_vp->da->type == PW_TYPE_IPADDR) &&
298                     (attr_vp->vp_strvalue[0] == '\0')) {
299                         inet_ntop(AF_INET, &(attr_vp->vp_ipaddr),
300                                   attr_vp->vp_strvalue,
301                                   sizeof(attr_vp->vp_strvalue));
302                 }
303
304                 ptr = new_str;
305                 ptr2 = attr_vp->vp_strvalue;
306                 counter = 0;
307
308                 for ( i = 0 ;i < (unsigned)inst->num_matches; i++) {
309                         err = regexec(&preg, ptr2, REQUEST_MAX_REGEX, pmatch, 0);
310                         if (err == REG_NOMATCH) {
311                                 if (i == 0) {
312                                         DEBUG2("%s: Does not match: %s = %s", inst->name,
313                                                         inst->attribute, attr_vp->vp_strvalue);
314                                         regfree(&preg);
315                                         goto to_do_again;
316                                 } else
317                                         break;
318                         }
319                         if (err != 0) {
320                                 regfree(&preg);
321                                 radlog(L_ERR, "%s: match failure for attribute %s with value '%s'", inst->name,
322                                                 inst->attribute, attr_vp->vp_strvalue);
323                                 return rcode;
324                         }
325                         if (pmatch[0].rm_so == -1)
326                                 break;
327                         len = pmatch[0].rm_so;
328                         if (inst->append) {
329                                 len = len + (pmatch[0].rm_eo - pmatch[0].rm_so);
330                         }
331                         counter += len;
332                         if (counter >= MAX_STRING_LEN) {
333                                 regfree(&preg);
334                                 DEBUG2("%s: Replacement out of limits for attribute %s with value '%s'", inst->name,
335                                                 inst->attribute, attr_vp->vp_strvalue);
336                                 return rcode;
337                         }
338
339                         memcpy(ptr, ptr2,len);
340                         ptr += len;
341                         *ptr = '\0';
342                         ptr2 += pmatch[0].rm_eo;
343
344                         if (i == 0){
345                                 /*
346                                  * We only run on the first match, sorry
347                                  */
348                                 for(j = 0; j <= REQUEST_MAX_REGEX; j++){
349                                         char *p;
350                                         char buffer[sizeof(attr_vp->vp_strvalue)];
351
352                                         /*
353                                          * Stolen from src/main/valuepair.c, paircompare()
354                                          */
355
356                                         /*
357                                          * Delete old matches if the corresponding match does not
358                                          * exist in the current regex
359                                          */
360                                         if (pmatch[j].rm_so == -1){
361                                                 p = request_data_get(request,request,REQUEST_DATA_REGEX | j);
362                                                 if (p){
363                                                         free(p);
364                                                         continue;
365                                                 }
366                                                 break;
367                                         }
368                                         memcpy(buffer,
369                                                attr_vp->vp_strvalue + pmatch[j].rm_so,
370                                                pmatch[j].rm_eo - pmatch[j].rm_so);
371                                         buffer[pmatch[j].rm_eo - pmatch[j].rm_so] = '\0';
372                                         p = strdup(buffer);
373                                         request_data_add(request,request,REQUEST_DATA_REGEX | j,p,free);
374                                 }
375                         }
376
377                         if (!done_xlat){
378                                 if (inst->replace_len != 0 &&
379                                 radius_xlat(replace_STR, sizeof(replace_STR), inst->replace, request, NULL, NULL) == 0) {
380                                         DEBUG2("%s: xlat on replace string failed.", inst->name);
381                                         return rcode;
382                                 }
383                                 replace_len = (inst->replace_len != 0) ? strlen(replace_STR) : 0;
384                                 done_xlat = 1;
385                         }
386
387                         counter += replace_len;
388                         if (counter >= MAX_STRING_LEN) {
389                                 regfree(&preg);
390                                 DEBUG2("%s: Replacement out of limits for attribute %s with value '%s'", inst->name,
391                                                 inst->attribute, attr_vp->vp_strvalue);
392                                 return rcode;
393                         }
394                         if (replace_len){
395                                 memcpy(ptr, replace_STR, replace_len);
396                                 ptr += replace_len;
397                                 *ptr = '\0';
398                         }
399                 }
400                 regfree(&preg);
401                 len = strlen(ptr2) + 1;         /* We add the ending NULL */
402                 counter += len;
403                 if (counter >= MAX_STRING_LEN){
404                         DEBUG2("%s: Replacement out of limits for attribute %s with value '%s'", inst->name,
405                                         inst->attribute, attr_vp->vp_strvalue);
406                         return rcode;
407                 }
408                 memcpy(ptr, ptr2, len);
409                 ptr[len] = '\0';
410
411                 DEBUG2("%s: Changed value for attribute %s from '%s' to '%s'", inst->name,
412                                 inst->attribute, attr_vp->vp_strvalue, new_str);
413                 if (!pairparsevalue(attr_vp, new_str)) {
414                         DEBUG2("%s: Could not write value '%s' into attribute %s: %s", inst->name, new_str, inst->attribute, fr_strerror());
415                         return rcode;
416                 }
417
418 to_do_again:
419                 rcode = RLM_MODULE_OK;
420
421                 if (tmp != NULL){
422                         tmp = attr_vp->next;
423                         if (tmp != NULL)
424                                 goto do_again;
425                 }
426         }
427
428         return rcode;
429 }
430
431 static rlm_rcode_t attr_rewrite_accounting(void *instance, REQUEST *request)
432 {
433         return do_attr_rewrite(instance, request);
434 }
435
436 static rlm_rcode_t attr_rewrite_authorize(void *instance, REQUEST *request)
437 {
438         return do_attr_rewrite(instance, request);
439 }
440
441 static rlm_rcode_t attr_rewrite_authenticate(void *instance, REQUEST *request)
442 {
443         return do_attr_rewrite(instance, request);
444 }
445
446 static rlm_rcode_t attr_rewrite_preacct(void *instance, REQUEST *request)
447 {
448         return do_attr_rewrite(instance, request);
449 }
450
451 static rlm_rcode_t attr_rewrite_checksimul(void *instance, REQUEST *request)
452 {
453         return do_attr_rewrite(instance, request);
454 }
455
456 #ifdef WITH_PROXY
457 static rlm_rcode_t attr_rewrite_preproxy(void *instance, REQUEST *request)
458 {
459         return do_attr_rewrite(instance, request);
460 }
461
462 static rlm_rcode_t attr_rewrite_postproxy(void *instance, REQUEST *request)
463 {
464         return do_attr_rewrite(instance, request);
465 }
466 #endif
467
468 static rlm_rcode_t attr_rewrite_postauth(void *instance, REQUEST *request)
469 {
470         return do_attr_rewrite(instance, request);
471 }
472
473 /*
474  *      The module name should be the only globally exported symbol.
475  *      That is, everything else should be 'static'.
476  *
477  *      If the module needs to temporarily modify it's instantiation
478  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
479  *      The server will then take care of ensuring that the module
480  *      is single-threaded.
481  */
482 module_t rlm_attr_rewrite = {
483         RLM_MODULE_INIT,
484         "attr_rewrite",
485         RLM_TYPE_THREAD_UNSAFE,                 /* type */
486         attr_rewrite_instantiate,               /* instantiation */
487         NULL,                                   /* detach */
488         {
489                 attr_rewrite_authenticate,      /* authentication */
490                 attr_rewrite_authorize,         /* authorization */
491                 attr_rewrite_preacct,           /* preaccounting */
492                 attr_rewrite_accounting,        /* accounting */
493                 attr_rewrite_checksimul,        /* checksimul */
494 #ifdef WITH_PROXY
495                 attr_rewrite_preproxy,          /* pre-proxy */
496                 attr_rewrite_postproxy,         /* post-proxy */
497 #else
498                 NULL, NULL,
499 #endif
500                 attr_rewrite_postauth           /* post-auth */
501         },
502 };