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