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