If there are more than one attributes of the same name in the VALUE_PAIR list
[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 2001  The FreeRADIUS server project
21  * Copyright 2001  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  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
62 static CONF_PARSER module_config[] = {
63   { "attribute", PW_TYPE_STRING_PTR, offsetof(rlm_attr_rewrite_t,attribute), NULL, NULL },
64   { "searchfor", PW_TYPE_STRING_PTR, offsetof(rlm_attr_rewrite_t,search), NULL, NULL },
65   { "searchin",  PW_TYPE_STRING_PTR, offsetof(rlm_attr_rewrite_t,searchin_str), NULL, "packet" },
66   { "replacewith", PW_TYPE_STRING_PTR, offsetof(rlm_attr_rewrite_t,replace), NULL, NULL },
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
74 static int attr_rewrite_instantiate(CONF_SECTION *conf, void **instance)
75 {
76         rlm_attr_rewrite_t *data;
77         DICT_ATTR *dattr;
78         char *instance_name = NULL;
79         
80         /*
81          *      Set up a storage area for instance data
82          */
83         data = rad_malloc(sizeof(*data));
84
85         /*
86          *      If the configuration parameters can't be parsed, then
87          *      fail.
88          */
89         if (cf_section_parse(conf, data, module_config) < 0) {
90                 free(data);
91                 return -1;
92         }
93
94         /*
95          *      Discover the attribute number of the key. 
96          */
97         if (data->attribute == NULL) {
98                 radlog(L_ERR, "rlm_attr_rewrite: 'attribute' must be set.");
99                 return -1;
100         }
101         if (data->search == NULL || data->replace == NULL) {
102                 radlog(L_ERR, "rlm_attr_rewrite: search/replace strings must be set.");
103                 return -1;
104         }
105         data->search_len = strlen(data->search);
106         data->replace_len = strlen(data->replace);
107
108         if (data->num_matches < 1 || data->num_matches > MAX_STRING_LEN) {
109                 radlog(L_ERR, "rlm_attr_rewrite: Illegal range for match number.");
110                 return -1;
111         }
112         if (data->searchin_str == NULL) {
113                 radlog(L_ERR, "rlm_attr_rewrite: Illegal searchin directive given. Assuming packet.");
114                 data->searchin = RLM_REGEX_INPACKET;
115         }
116         else{
117                 if (strcmp(data->searchin_str, "packet") == 0)
118                         data->searchin = RLM_REGEX_INPACKET;
119                 else if (strcmp(data->searchin_str, "config") == 0)
120                         data->searchin = RLM_REGEX_INCONFIG;
121                 else if (strcmp(data->searchin_str, "reply") == 0)
122                         data->searchin = RLM_REGEX_INREPLY;
123                 else {
124                         radlog(L_ERR, "rlm_attr_rewrite: Illegal searchin directive given. Assuming packet.");
125                         data->searchin = RLM_REGEX_INPACKET;
126                 }
127                 free((char *)data->searchin_str);
128         }
129         dattr = dict_attrbyname(data->attribute);
130         if (dattr == NULL) {
131                 radlog(L_ERR, "rlm_attr_rewrite: No such attribute %s",
132                                 data->attribute);
133                 return -1;
134         }
135         data->attr_num = dattr->attr;
136         /* Add the module instance name */
137         data->name = NULL;
138         instance_name = cf_section_name2(conf);
139         if (instance_name != NULL)
140                 data->name = strdup(instance_name);
141         
142         
143         *instance = data;
144         
145         return 0;
146 }
147
148 static int do_attr_rewrite(void *instance, REQUEST *request)
149 {
150         rlm_attr_rewrite_t *data = (rlm_attr_rewrite_t *) instance;
151         int ret = RLM_MODULE_NOOP;
152         VALUE_PAIR *attr_vp = NULL;
153         VALUE_PAIR *tmp = NULL;
154         regex_t preg;
155         regmatch_t pmatch;
156         int cflags = 0;
157         int err = 0;
158         unsigned int len = 0;
159         char err_msg[MAX_STRING_LEN];
160         unsigned int i = 0;
161         unsigned int counter = 0;
162         char new_str[MAX_STRING_LEN];
163         char *ptr, *ptr2;
164         char search_STR[MAX_STRING_LEN];
165         char replace_STR[MAX_STRING_LEN];
166         int replace_len = 0;
167
168         if ((attr_vp = pairfind(request->config_items, PW_REWRITE_RULE)) != NULL){
169                 if (data->name == NULL || strcmp(data->name,attr_vp->strvalue))
170                         return RLM_MODULE_NOOP;
171         }
172
173         if (!data->new_attr){
174                 switch (data->searchin) {
175                         case RLM_REGEX_INPACKET:
176                                 if (data->attr_num == PW_USER_NAME)
177                                         attr_vp = request->username;
178                                 else if (data->attr_num == PW_PASSWORD)
179                                         attr_vp = request->password;
180                                 else
181                                         tmp = request->packet->vps;
182                                 break;
183                         case RLM_REGEX_INCONFIG:
184                                 tmp = request->config_items;
185                                 break;
186                         case RLM_REGEX_INREPLY:
187                                 tmp = request->reply->vps;
188                                 break;
189                         default:
190                                 radlog(L_ERR, "rlm_attr_rewrite: Illegal value for searchin. Changing to packet.");
191                                 data->searchin = RLM_REGEX_INPACKET;
192                                 attr_vp = pairfind(request->packet->vps, data->attr_num);
193                                 break;
194                 }
195 do_again:
196                 if (tmp != NULL)
197                         attr_vp = pairfind(tmp, data->attr_num);
198                 if (attr_vp == NULL) {
199                         DEBUG2("rlm_attr_rewrite: Could not find value pair for attribute %s",data->attribute);
200                         return ret;
201                 }
202                 if (attr_vp->strvalue == NULL || attr_vp->length == 0){
203                         DEBUG2("rlm_attr_rewrite: Attribute %s string value NULL or of zero length",data->attribute);
204                         return ret;
205                 }
206                 cflags |= REG_EXTENDED;
207                 if (data->nocase)
208                         cflags |= REG_ICASE;
209
210                 if (!radius_xlat(search_STR, sizeof(search_STR), data->search, request, NULL) && data->search_len != 0) {
211                         DEBUG2("rlm_attr_rewrite: xlat on search string failed.");
212                         return ret;
213                 }
214         }
215         if (!radius_xlat(replace_STR, sizeof(replace_STR), data->replace, request, NULL) && data->replace_len != 0) {
216                 DEBUG2("rlm_attr_rewrite: xlat on replace string failed.");
217                 return ret;
218         }
219         replace_len = strlen(replace_STR);
220
221         if (!data->new_attr){
222                 if ((err = regcomp(&preg,search_STR,cflags))) {
223                         regerror(err, &preg, err_msg, MAX_STRING_LEN);
224                         DEBUG2("rlm_attr_rewrite: regcomp() returned error: %s",err_msg);
225                         return ret;
226                 }
227                 ptr = new_str;
228                 ptr2 = attr_vp->strvalue;
229                 counter = 0;
230
231                 for ( i = 0 ;i < data->num_matches; i++) {
232                         err = regexec(&preg, ptr2, 1, &pmatch, 0);
233                         if (err == REG_NOMATCH) {
234                                 if (i == 0) {
235                                         DEBUG2("rlm_attr_rewrite: No match found for attribute %s with value '%s'",
236                                                         data->attribute, attr_vp->strvalue);
237                                         regfree(&preg);
238                                         return RLM_MODULE_OK;
239                                 } else
240                                         break;
241                         }
242                         if (err != 0) {
243                                 regfree(&preg);
244                                 radlog(L_ERR, "rlm_attr_rewrite: match failure for attribute %s with value '%s'",
245                                                 data->attribute, attr_vp->strvalue);
246                                 return ret;
247                         }
248                         if (pmatch.rm_so == -1)
249                                 break;
250                         len = pmatch.rm_so;
251                         counter += len;
252                         if (counter >= MAX_STRING_LEN) {
253                                 regfree(&preg);
254                                 DEBUG2("rlm_attr_rewrite: Replacement out of limits for attribute %s with value '%s'",
255                                                 data->attribute, attr_vp->strvalue);    
256                                 return ret;
257                         }
258
259                         strncpy(ptr, ptr2,len);
260                         ptr += len;
261                         ptr2 += pmatch.rm_eo;
262
263                         counter += replace_len;
264                         if (counter >= MAX_STRING_LEN) {
265                                 regfree(&preg);
266                                 DEBUG2("rlm_attr_rewrite: Replacement out of limits for attribute %s with value '%s'",
267                                                 data->attribute, attr_vp->strvalue);    
268                                 return ret;
269                         }
270                         strncpy(ptr, replace_STR, replace_len);
271                         ptr += replace_len;     
272                 }
273                 regfree(&preg);
274                 len = strlen(ptr2) + 1;         /* We add the ending NULL */
275                 counter += len;
276                 if (counter >= MAX_STRING_LEN){
277                         DEBUG2("rlm_attr_rewrite: Replacement out of limits for attribute %s with value '%s'",
278                                         data->attribute, attr_vp->strvalue);    
279                         return ret;
280                 }
281                 strncpy(ptr, ptr2, len);
282
283                 DEBUG2("rlm_attr_rewrite: Changed value for attribute %s from '%s' to '%s'",
284                                 data->attribute, attr_vp->strvalue, new_str);
285                 attr_vp->length = strlen(new_str);
286                 strncpy(attr_vp->strvalue, new_str, (attr_vp->length + 1));
287
288                 ret = RLM_MODULE_OK;
289
290                 if (tmp != NULL){
291                         tmp = attr_vp->next;
292                         if (tmp != NULL)
293                                 goto do_again;
294                 }
295         }
296         else{
297                 attr_vp = pairmake(data->attribute,replace_STR,0);
298                 switch(data->searchin){
299                         case RLM_REGEX_INPACKET:
300                                 pairadd(&request->packet->vps,attr_vp);
301                                 break;
302                         case RLM_REGEX_INCONFIG:
303                                 pairadd(&request->config_items,attr_vp);
304                                 break;
305                         case RLM_REGEX_INREPLY:
306                                 pairadd(&request->reply->vps,attr_vp);
307                                 break;
308                         default:
309                                 radlog(L_ERR, "rlm_attr_rewrite: Illegal value for searchin. Changing to packet.");
310                                 data->searchin = RLM_REGEX_INPACKET;
311                                 pairadd(&request->packet->vps,attr_vp);
312                                 break;
313                 }
314                 DEBUG2("rlm_attr_rewrite: Added attribute %s with value '%s'",data->attribute,attr_vp->strvalue);
315                 ret = RLM_MODULE_OK;
316         }
317                                 
318
319         return ret;
320 }
321
322
323 static int attr_rewrite_accounting(void *instance, REQUEST *request)
324 {
325         return do_attr_rewrite(instance, request);
326 }
327
328 static int attr_rewrite_authorize(void *instance, REQUEST *request)
329 {
330         return do_attr_rewrite(instance, request);
331 }
332 static int attr_rewrite_authenticate(void *instance, REQUEST *request)
333 {
334         return do_attr_rewrite(instance, request);
335 }
336 static int attr_rewrite_preacct(void *instance, REQUEST *request)
337 {
338         return do_attr_rewrite(instance, request);
339 }
340 static int attr_rewrite_ismul(void *instance, REQUEST *request)
341 {
342         return do_attr_rewrite(instance, request);
343 }
344
345 static int attr_rewrite_preproxy(void *instance, REQUEST *request)
346 {
347         return do_attr_rewrite(instance, request);
348 }
349
350 static int attr_rewrite_postproxy(void *instance, REQUEST *request)
351 {
352         return do_attr_rewrite(instance, request);
353 }
354
355 static int attr_rewrite_postauth(void *instance, REQUEST *request)
356 {
357         return do_attr_rewrite(instance, request);
358 }
359
360 static int attr_rewrite_detach(void *instance)
361 {
362         rlm_attr_rewrite_t *data = (rlm_attr_rewrite_t *) instance;
363
364         if (data->attribute)
365                 free(data->attribute);
366         if (data->search)
367                 free(data->search);
368         if (data->replace)      
369                 free(data->replace);
370         if (data->name)
371                 free(data->name);
372
373         free(instance);
374         return 0;
375 }
376
377 /*
378  *      The module name should be the only globally exported symbol.
379  *      That is, everything else should be 'static'.
380  *
381  *      If the module needs to temporarily modify it's instantiation
382  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
383  *      The server will then take care of ensuring that the module
384  *      is single-threaded.
385  */
386 module_t rlm_attr_rewrite = {
387         "attr_rewrite", 
388         RLM_TYPE_THREAD_UNSAFE,         /* type */
389         NULL,                           /* initialization */
390         attr_rewrite_instantiate,               /* instantiation */
391         {
392                 attr_rewrite_authenticate,      /* authentication */
393                 attr_rewrite_authorize,         /* authorization */
394                 attr_rewrite_preacct,           /* preaccounting */
395                 attr_rewrite_accounting,        /* accounting */
396                 attr_rewrite_ismul,             /* checksimul */
397                 attr_rewrite_preproxy,          /* pre-proxy */
398                 attr_rewrite_postproxy,         /* post-proxy */
399                 attr_rewrite_postauth           /* post-auth */
400         },
401         attr_rewrite_detach,                    /* detach */
402         NULL,                           /* destroy */
403 };