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