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