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