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