Work even if we have multiple attributes
[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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Copyright 2002  The FreeRADIUS server project
21  * Copyright 2002  Kostas Kalevras <kkalev@noc.ntua.gr>
22  */
23
24 #include "config.h"
25 #include "autoconf.h"
26 #include "libradius.h"
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include "config.h"
32 #if HAVE_REGEX_H
33 #       include <regex.h>
34 #endif
35
36 #include "radiusd.h"
37 #include "modules.h"
38 #include "conffile.h"
39
40 #define RLM_REGEX_INPACKET 0
41 #define RLM_REGEX_INCONFIG 1
42 #define RLM_REGEX_INREPLY  2
43
44 static const char rcsid[] = "$Id$";
45
46 typedef struct rlm_attr_rewrite_t {
47         char *attribute;        /* The attribute to search for */
48         int  attr_num;          /* The attribute number */
49         char *search;           /* The pattern to search for */
50         int search_len;         /* The length of the search pattern */
51         char *searchin_str;     /* The VALUE_PAIR list to search in. Can be either packet,reply or config */
52         char searchin;          /* The same as above just coded as a number for speed */
53         char *replace;          /* The replacement */
54         int replace_len;        /* The length of the replacement string */
55         int  append;            /* Switch to control append mode (1,0) */ 
56         int  nocase;            /* Ignore case */
57         int  new_attr;          /* Boolean. Do we create a new attribute or not? */
58         int  num_matches;       /* Maximum number of matches */
59         char *name;             /* The module name */
60 } rlm_attr_rewrite_t;
61
62
63 static CONF_PARSER module_config[] = {
64   { "attribute", PW_TYPE_STRING_PTR, offsetof(rlm_attr_rewrite_t,attribute), NULL, NULL },
65   { "searchfor", PW_TYPE_STRING_PTR, offsetof(rlm_attr_rewrite_t,search), NULL, NULL },
66   { "searchin",  PW_TYPE_STRING_PTR, offsetof(rlm_attr_rewrite_t,searchin_str), NULL, "packet" },
67   { "replacewith", PW_TYPE_STRING_PTR, offsetof(rlm_attr_rewrite_t,replace), NULL, NULL },
68   { "append", PW_TYPE_BOOLEAN, offsetof(rlm_attr_rewrite_t,append),NULL, "no" },
69   { "ignore_case", PW_TYPE_BOOLEAN, offsetof(rlm_attr_rewrite_t,nocase), NULL, "yes" },
70   { "new_attribute", PW_TYPE_BOOLEAN, offsetof(rlm_attr_rewrite_t,new_attr), NULL, "no" },
71   { "max_matches", PW_TYPE_INTEGER, offsetof(rlm_attr_rewrite_t,num_matches), NULL, "10" },
72   { NULL, -1, 0, NULL, NULL }
73 };
74
75
76 static int attr_rewrite_instantiate(CONF_SECTION *conf, void **instance)
77 {
78         rlm_attr_rewrite_t *data;
79         DICT_ATTR *dattr;
80         char *instance_name = NULL;
81         
82         /*
83          *      Set up a storage area for instance data
84          */
85         data = rad_malloc(sizeof(*data));
86
87         /*
88          *      If the configuration parameters can't be parsed, then
89          *      fail.
90          */
91         if (cf_section_parse(conf, data, module_config) < 0) {
92                 free(data);
93                 return -1;
94         }
95
96         /*
97          *      Discover the attribute number of the key. 
98          */
99         if (data->attribute == NULL) {
100                 radlog(L_ERR, "rlm_attr_rewrite: 'attribute' must be set.");
101                 return -1;
102         }
103         if (data->search == NULL || data->replace == NULL) {
104                 radlog(L_ERR, "rlm_attr_rewrite: search/replace strings must be set.");
105                 return -1;
106         }
107         data->search_len = strlen(data->search);
108         data->replace_len = strlen(data->replace);
109
110         if (data->replace_len == 0 && data->new_attr){
111                 radlog(L_ERR, "rlm_attr_rewrite: replace string must not be zero length in order to create new attribute.");
112                 return -1;
113         }
114
115         if (data->num_matches < 1 || data->num_matches > MAX_STRING_LEN) {
116                 radlog(L_ERR, "rlm_attr_rewrite: Illegal range for match number.");
117                 return -1;
118         }
119         if (data->searchin_str == NULL) {
120                 radlog(L_ERR, "rlm_attr_rewrite: Illegal searchin directive given. Assuming packet.");
121                 data->searchin = RLM_REGEX_INPACKET;
122         }
123         else{
124                 if (strcmp(data->searchin_str, "packet") == 0)
125                         data->searchin = RLM_REGEX_INPACKET;
126                 else if (strcmp(data->searchin_str, "config") == 0)
127                         data->searchin = RLM_REGEX_INCONFIG;
128                 else if (strcmp(data->searchin_str, "reply") == 0)
129                         data->searchin = RLM_REGEX_INREPLY;
130                 else {
131                         radlog(L_ERR, "rlm_attr_rewrite: Illegal searchin directive given. Assuming packet.");
132                         data->searchin = RLM_REGEX_INPACKET;
133                 }
134                 free((char *)data->searchin_str);
135         }
136         dattr = dict_attrbyname(data->attribute);
137         if (dattr == NULL) {
138                 radlog(L_ERR, "rlm_attr_rewrite: No such attribute %s",
139                                 data->attribute);
140                 return -1;
141         }
142         data->attr_num = dattr->attr;
143         /* Add the module instance name */
144         data->name = NULL;
145         instance_name = cf_section_name2(conf);
146         if (instance_name != NULL)
147                 data->name = strdup(instance_name);
148         
149         
150         *instance = data;
151         
152         return 0;
153 }
154
155 static int do_attr_rewrite(void *instance, REQUEST *request)
156 {
157         rlm_attr_rewrite_t *data = (rlm_attr_rewrite_t *) instance;
158         int ret = RLM_MODULE_NOOP;
159         VALUE_PAIR *attr_vp = NULL;
160         VALUE_PAIR *tmp = NULL;
161         regex_t preg;
162         regmatch_t pmatch;
163         int cflags = 0;
164         int err = 0;
165         char done_xlat = 0;
166         unsigned int len = 0;
167         char err_msg[MAX_STRING_LEN];
168         unsigned int i = 0;
169         unsigned int counter = 0;
170         char new_str[MAX_STRING_LEN];
171         char *ptr, *ptr2;
172         char search_STR[MAX_STRING_LEN];
173         char replace_STR[MAX_STRING_LEN];
174         int replace_len = 0;
175
176         if ((attr_vp = pairfind(request->config_items, PW_REWRITE_RULE)) != NULL){
177                 if (data->name == NULL || strcmp(data->name,attr_vp->strvalue))
178                         return RLM_MODULE_NOOP;
179         }
180
181         if (!data->new_attr){
182                 switch (data->searchin) {
183                         case RLM_REGEX_INPACKET:
184                                 if (data->attr_num == PW_USER_NAME)
185                                         attr_vp = request->username;
186                                 else if (data->attr_num == PW_PASSWORD)
187                                         attr_vp = request->password;
188                                 else
189                                         tmp = request->packet->vps;
190                                 break;
191                         case RLM_REGEX_INCONFIG:
192                                 tmp = request->config_items;
193                                 break;
194                         case RLM_REGEX_INREPLY:
195                                 tmp = request->reply->vps;
196                                 break;
197                         default:
198                                 radlog(L_ERR, "rlm_attr_rewrite: Illegal value for searchin. Changing to packet.");
199                                 data->searchin = RLM_REGEX_INPACKET;
200                                 attr_vp = pairfind(request->packet->vps, data->attr_num);
201                                 break;
202                 }
203 do_again:
204                 if (tmp != NULL)
205                         attr_vp = pairfind(tmp, data->attr_num);
206                 if (attr_vp == NULL) {
207                         DEBUG2("rlm_attr_rewrite: Could not find value pair for attribute %s",data->attribute);
208                         return ret;
209                 }
210                 if (attr_vp->strvalue == NULL || attr_vp->length == 0){
211                         DEBUG2("rlm_attr_rewrite: Attribute %s string value NULL or of zero length",data->attribute);
212                         return ret;
213                 }
214                 cflags |= REG_EXTENDED;
215                 if (data->nocase)
216                         cflags |= REG_ICASE;
217
218                 if (!radius_xlat(search_STR, sizeof(search_STR), data->search, request, NULL) && data->search_len != 0) {
219                         DEBUG2("rlm_attr_rewrite: xlat on search string failed.");
220                         return ret;
221                 }
222         }
223         if (data->new_attr){
224                 if (!radius_xlat(replace_STR, sizeof(replace_STR), data->replace, request, NULL)) {
225                         DEBUG2("rlm_attr_rewrite: xlat on replace string failed.");
226                         return ret;
227                 }
228                 replace_len = strlen(replace_STR);
229         }
230
231         if (!data->new_attr){
232                 if ((err = regcomp(&preg,search_STR,cflags))) {
233                         regerror(err, &preg, err_msg, MAX_STRING_LEN);
234                         DEBUG2("rlm_attr_rewrite: regcomp() returned error: %s",err_msg);
235                         return ret;
236                 }
237                 ptr = new_str;
238                 ptr2 = attr_vp->strvalue;
239                 counter = 0;
240
241                 for ( i = 0 ;i < data->num_matches; i++) {
242                         err = regexec(&preg, ptr2, 1, &pmatch, 0);
243                         if (err == REG_NOMATCH) {
244                                 if (i == 0) {
245                                         DEBUG2("rlm_attr_rewrite: No match found for attribute %s with value '%s'",
246                                                         data->attribute, attr_vp->strvalue);
247                                         regfree(&preg);
248                                         goto to_do_again;
249                                 } else
250                                         break;
251                         }
252                         if (err != 0) {
253                                 regfree(&preg);
254                                 radlog(L_ERR, "rlm_attr_rewrite: match failure for attribute %s with value '%s'",
255                                                 data->attribute, attr_vp->strvalue);
256                                 return ret;
257                         }
258                         if (pmatch.rm_so == -1)
259                                 break;
260                         len = pmatch.rm_so;
261                         if (data->append) {
262                                 len = len + (pmatch.rm_eo - pmatch.rm_so);
263                         } 
264                         counter += len;
265                         if (counter >= MAX_STRING_LEN) {
266                                 regfree(&preg);
267                                 DEBUG2("rlm_attr_rewrite: Replacement out of limits for attribute %s with value '%s'",
268                                                 data->attribute, attr_vp->strvalue);    
269                                 return ret;
270                         }
271
272                         strncpy(ptr, ptr2,len);
273                         ptr += len;
274                         ptr2 += pmatch.rm_eo;
275
276                         if (!done_xlat){
277                                 if (data->replace_len != 0 &&
278                                 radius_xlat(replace_STR, sizeof(replace_STR), data->replace, request, NULL) == 0) {
279                                         DEBUG2("rlm_attr_rewrite: xlat on replace string failed.");
280                                         return ret;
281                                 }
282                                 replace_len = (data->replace_len != 0) ? strlen(replace_STR) : 0;
283                                 done_xlat = 1;
284                         }
285
286                         counter += replace_len;
287                         if (counter >= MAX_STRING_LEN) {
288                                 regfree(&preg);
289                                 DEBUG2("rlm_attr_rewrite: Replacement out of limits for attribute %s with value '%s'",
290                                                 data->attribute, attr_vp->strvalue);    
291                                 return ret;
292                         }
293                         if (replace_len){
294                                 strncpy(ptr, replace_STR, replace_len);
295                                 ptr += replace_len;     
296                         }
297                 }
298                 regfree(&preg);
299                 len = strlen(ptr2) + 1;         /* We add the ending NULL */
300                 counter += len;
301                 if (counter >= MAX_STRING_LEN){
302                         DEBUG2("rlm_attr_rewrite: Replacement out of limits for attribute %s with value '%s'",
303                                         data->attribute, attr_vp->strvalue);    
304                         return ret;
305                 }
306                 strncpy(ptr, ptr2, len);
307
308                 DEBUG2("rlm_attr_rewrite: Changed value for attribute %s from '%s' to '%s'",
309                                 data->attribute, attr_vp->strvalue, new_str);
310                 attr_vp->length = strlen(new_str);
311                 strncpy(attr_vp->strvalue, new_str, (attr_vp->length + 1));
312
313 to_do_again:
314                 ret = RLM_MODULE_OK;
315
316                 if (tmp != NULL){
317                         tmp = attr_vp->next;
318                         if (tmp != NULL)
319                                 goto do_again;
320                 }
321         }
322         else{
323                 attr_vp = pairmake(data->attribute,replace_STR,0);
324                 switch(data->searchin){
325                         case RLM_REGEX_INPACKET:
326                                 pairadd(&request->packet->vps,attr_vp);
327                                 break;
328                         case RLM_REGEX_INCONFIG:
329                                 pairadd(&request->config_items,attr_vp);
330                                 break;
331                         case RLM_REGEX_INREPLY:
332                                 pairadd(&request->reply->vps,attr_vp);
333                                 break;
334                         default:
335                                 radlog(L_ERR, "rlm_attr_rewrite: Illegal value for searchin. Changing to packet.");
336                                 data->searchin = RLM_REGEX_INPACKET;
337                                 pairadd(&request->packet->vps,attr_vp);
338                                 break;
339                 }
340                 DEBUG2("rlm_attr_rewrite: Added attribute %s with value '%s'",data->attribute,attr_vp->strvalue);
341                 ret = RLM_MODULE_OK;
342         }
343                                 
344
345         return ret;
346 }
347
348
349 static int attr_rewrite_accounting(void *instance, REQUEST *request)
350 {
351         return do_attr_rewrite(instance, request);
352 }
353
354 static int attr_rewrite_authorize(void *instance, REQUEST *request)
355 {
356         return do_attr_rewrite(instance, request);
357 }
358 static int attr_rewrite_authenticate(void *instance, REQUEST *request)
359 {
360         return do_attr_rewrite(instance, request);
361 }
362 static int attr_rewrite_preacct(void *instance, REQUEST *request)
363 {
364         return do_attr_rewrite(instance, request);
365 }
366 static int attr_rewrite_ismul(void *instance, REQUEST *request)
367 {
368         return do_attr_rewrite(instance, request);
369 }
370
371 static int attr_rewrite_preproxy(void *instance, REQUEST *request)
372 {
373         return do_attr_rewrite(instance, request);
374 }
375
376 static int attr_rewrite_postproxy(void *instance, REQUEST *request)
377 {
378         return do_attr_rewrite(instance, request);
379 }
380
381 static int attr_rewrite_postauth(void *instance, REQUEST *request)
382 {
383         return do_attr_rewrite(instance, request);
384 }
385
386 static int attr_rewrite_detach(void *instance)
387 {
388         rlm_attr_rewrite_t *data = (rlm_attr_rewrite_t *) instance;
389
390         if (data->attribute)
391                 free(data->attribute);
392         if (data->search)
393                 free(data->search);
394         if (data->replace)      
395                 free(data->replace);
396         if (data->name)
397                 free(data->name);
398
399         free(instance);
400         return 0;
401 }
402
403 /*
404  *      The module name should be the only globally exported symbol.
405  *      That is, everything else should be 'static'.
406  *
407  *      If the module needs to temporarily modify it's instantiation
408  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
409  *      The server will then take care of ensuring that the module
410  *      is single-threaded.
411  */
412 module_t rlm_attr_rewrite = {
413         "attr_rewrite", 
414         RLM_TYPE_THREAD_UNSAFE,         /* type */
415         NULL,                           /* initialization */
416         attr_rewrite_instantiate,               /* instantiation */
417         {
418                 attr_rewrite_authenticate,      /* authentication */
419                 attr_rewrite_authorize,         /* authorization */
420                 attr_rewrite_preacct,           /* preaccounting */
421                 attr_rewrite_accounting,        /* accounting */
422                 attr_rewrite_ismul,             /* checksimul */
423                 attr_rewrite_preproxy,          /* pre-proxy */
424                 attr_rewrite_postproxy,         /* post-proxy */
425                 attr_rewrite_postauth           /* post-auth */
426         },
427         attr_rewrite_detach,                    /* detach */
428         NULL,                           /* destroy */
429 };