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