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