backport from HEAD
[freeradius.git] / src / modules / rlm_checkval / rlm_checkval.c
1 /*
2  * rlm_checkval.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 2003  The FreeRADIUS server project
21  * Copyright 2003  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
31 #include "radiusd.h"
32 #include "modules.h"
33 #include "conffile.h"
34 #ifdef HAVE_REGEX_H
35 #       include <regex.h>
36 #endif
37 #ifndef REG_EXTENDED
38 #define REG_EXTENDED (0)
39 #endif
40
41 #ifndef REG_NOSUB
42 #define REG_NOSUB (0)
43 #endif
44 /*
45  *      Define a structure for our module configuration.
46  *
47  *      These variables do not need to be in a structure, but it's
48  *      a lot cleaner to do so, and a pointer to the structure can
49  *      be used as the instance handle.
50  */
51 typedef struct rlm_checkval_t {
52         char    *item_name;     /* The attribute inside Access-Request ie Calling-Station-Id */
53         char    *check_name;    /* The attribute to check it with ie Allowed-Calling-Station-Id */
54         char    *data_type;     /* string,integer,ipaddr,date,abinary,octets */
55         int     dat_type;
56         int     item_attr;
57         int     chk_attr;
58         int     notfound_reject;        /* If we don't find the item_name in the request send back a reject */
59 } rlm_checkval_t;
60
61 /*
62  *      A mapping of configuration file names to internal variables.
63  *
64  *      Note that the string is dynamically allocated, so it MUST
65  *      be freed.  When the configuration file parse re-reads the string,
66  *      it free's the old one, and strdup's the new one, placing the pointer
67  *      to the strdup'd string into 'config.string'.  This gets around
68  *      buffer over-flows.
69  */
70 static CONF_PARSER module_config[] = {
71   { "item-name",  PW_TYPE_STRING_PTR, offsetof(rlm_checkval_t,item_name), NULL,  NULL},
72   { "check-name",  PW_TYPE_STRING_PTR, offsetof(rlm_checkval_t,check_name), NULL,  NULL},
73   { "data-type",    PW_TYPE_STRING_PTR, offsetof(rlm_checkval_t,data_type),NULL, "integer"},
74   { "notfound-reject", PW_TYPE_BOOLEAN, offsetof(rlm_checkval_t,notfound_reject),NULL, "no"},
75   { NULL, -1, 0, NULL, NULL }           /* end the list */
76 };
77
78
79 static int checkval_detach(void *instance)
80 {
81         rlm_checkval_t *data = (rlm_checkval_t *) instance;
82
83         if (data->item_name)
84                 free((char *)data->item_name);
85         if (data->check_name)
86                 free((char *)data->check_name);
87         if (data->data_type)
88                 free((char *)data->data_type);
89
90         free(instance);
91         return 0;
92 }
93
94 /*
95  *      Do any per-module initialization that is separate to each
96  *      configured instance of the module.  e.g. set up connections
97  *      to external databases, read configuration files, set up
98  *      dictionary entries, etc.
99  *
100  *      If configuration information is given in the config section
101  *      that must be referenced in later calls, store a handle to it
102  *      in *instance otherwise put a null pointer there.
103  */
104 static int checkval_instantiate(CONF_SECTION *conf, void **instance)
105 {
106         rlm_checkval_t *data;
107         DICT_ATTR *dattr;
108         ATTR_FLAGS flags;
109
110         static const LRAD_NAME_NUMBER names[] = {
111                 { "string", PW_TYPE_STRING },
112                 { "integer", PW_TYPE_INTEGER },
113                 { "ipaddr", PW_TYPE_IPADDR },
114                 { "date", PW_TYPE_DATE },
115                 { "abinary", PW_TYPE_OCTETS },
116                 { "octets", PW_TYPE_OCTETS },
117                 { "binary", PW_TYPE_OCTETS },
118                 { NULL, 0 }
119         };
120
121         /*
122          *      Set up a storage area for instance data
123          */
124         data = rad_malloc(sizeof(*data));
125         if (!data) {
126                 return -1;
127         }
128         memset(data, 0, sizeof(*data));
129
130         /*
131          *      If the configuration parameters can't be parsed, then
132          *      fail.
133          */
134         if (cf_section_parse(conf, data, module_config) < 0) {
135                 checkval_detach(data);
136                 return -1;
137         }
138
139         /*
140          * Check if data_type exists
141          */
142         if (!data->data_type || !strlen(data->data_type)){
143                 radlog(L_ERR, "rlm_checkval: Data type not defined");
144                 checkval_detach(data);
145                 return -1;
146         }
147         if (!data->item_name || !strlen(data->item_name)){
148                 radlog(L_ERR, "rlm_checkval: Item name not defined");
149                 checkval_detach(data);
150                 return -1;
151         }
152         if (!data->check_name || !strlen(data->check_name)){
153                 radlog(L_ERR, "rlm_checkval: Check item name not defined");
154                 checkval_detach(data);
155                 return -1;
156         }
157
158         /*
159          *      Discover the attribute number of the item name
160          */
161         dattr = dict_attrbyname(data->item_name);
162         if (!dattr) {
163                 radlog(L_ERR, "rlm_checkval: No such attribute %s",
164                        data->item_name);
165                 checkval_detach(data);
166                 return -1;
167         }
168         data->item_attr = dattr->attr;
169
170         /*
171          *      Add the check attribute name to the dictionary
172          *      if it does not already exists. dict_addattr() handles that
173          */
174
175         memset(&flags, 0, sizeof(flags));
176         dict_addattr(data->check_name, 0, PW_TYPE_STRING, -1,flags);
177         dattr = dict_attrbyname(data->check_name);
178         if (!dattr){
179                 radlog(L_ERR, "rlm_checkval: No such attribute %s",
180                        data->check_name);
181                 checkval_detach(data);
182                 return -1;
183         }
184         data->chk_attr = dattr->attr;
185         DEBUG2("rlm_checkval: Registered name %s for attribute %d",
186                 dattr->name,dattr->attr);
187
188         /*
189          *      Convert the string type to an integer type,
190          *      so we don't have to do string comparisons on each
191          *      packet.
192          */
193         data->dat_type = lrad_str2int(names, data->data_type, -1);
194         if (data->dat_type < 0) {
195                 radlog(L_ERR, "rlm_checkval: Data type %s in not known",data->data_type);
196                 checkval_detach(data);
197                 return -1;
198         }
199
200         *instance = data;
201
202         return 0;
203 }
204
205 static int do_checkval(void *instance, REQUEST *request)
206 {
207         rlm_checkval_t *data = (rlm_checkval_t *) instance;
208         int ret=RLM_MODULE_NOOP;
209         VALUE_PAIR *chk_vp, *item_vp;
210         VALUE_PAIR *tmp;
211         char found = 0;
212
213         /* quiet the compiler */
214         instance = instance;
215         request = request;
216
217
218         /*
219         *      Look for the check item
220         */
221
222         if (!(item_vp = pairfind(request->packet->vps, data->item_attr))){
223                 DEBUG2("rlm_checkval: Could not find item named %s in request", data->item_name);
224                 if (data->notfound_reject)
225                         ret = RLM_MODULE_REJECT;
226                 else
227                         ret = RLM_MODULE_NOTFOUND;
228         }
229         if (item_vp)
230                 DEBUG2("rlm_checkval: Item Name: %s, Value: %s",data->item_name, item_vp->strvalue);
231         tmp = request->config_items;
232         do{
233                 if (!(chk_vp = pairfind(tmp, data->chk_attr))){
234                         if (!found){
235                                 DEBUG2("rlm_checkval: Could not find attribute named %s in check pairs",data->check_name);
236                                 ret = RLM_MODULE_NOTFOUND;
237                         }
238                         break;
239                 }
240                 if (!item_vp)
241                         break;
242                 DEBUG2("rlm_checkval: Value Name: %s, Value: %s",data->check_name, chk_vp->strvalue);
243
244                 /*
245                 * Check if item != check
246                 */
247                 found = 1;
248                 if (data->dat_type == PW_TYPE_STRING ||
249                     data->dat_type == PW_TYPE_OCTETS) {
250                         if (item_vp->length != chk_vp->length)
251                                 ret = RLM_MODULE_REJECT;
252                         else{
253                                 if (!memcmp(item_vp->strvalue,
254                                             chk_vp->strvalue,
255                                             (size_t) chk_vp->length))
256                                         ret = RLM_MODULE_OK;
257                                 else
258                                         ret = RLM_MODULE_REJECT;
259                         }
260                 }
261                 else{   /* Integer or Date */
262
263                         if (item_vp->lvalue == chk_vp->lvalue)
264                                 ret = RLM_MODULE_OK;
265                         else
266                                 ret = RLM_MODULE_REJECT;
267                 }
268 #ifdef HAVE_REGEX_H
269                 if (ret == RLM_MODULE_REJECT &&
270                     chk_vp->operator == T_OP_REG_EQ) {
271                         regex_t reg;
272                         int err;
273                         char err_msg[MAX_STRING_LEN];
274
275                         DEBUG("rlm_checkval: Doing regex");
276                         err = regcomp(&reg, (char *)chk_vp->strvalue, REG_EXTENDED|REG_NOSUB);
277                         if (err){
278                                 regerror(err, &reg,err_msg, MAX_STRING_LEN);
279                                 DEBUG("rlm_checkval: regcomp() returned error: %s", err_msg);
280                                 return RLM_MODULE_FAIL;
281                         }
282                         if (regexec(&reg, (char *)item_vp->strvalue,0, NULL, 0) == 0)
283                                 ret = RLM_MODULE_OK;
284                         else
285                                 ret = RLM_MODULE_REJECT;
286                 }
287 #endif
288                 tmp = chk_vp->next;
289         } while (ret == RLM_MODULE_REJECT &&
290                  tmp != NULL);
291
292         if (ret == RLM_MODULE_REJECT) {
293                 if (!item_vp && data->notfound_reject){
294                         char module_fmsg[MAX_STRING_LEN];
295                         VALUE_PAIR *module_fmsg_vp;
296
297                         snprintf(module_fmsg,sizeof(module_fmsg),
298                                 "rlm_checkval: Could not find item named %s in request", data->item_name);
299                         module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
300                         pairadd(&request->packet->vps, module_fmsg_vp);
301                 }
302                 else{
303                         char module_fmsg[MAX_STRING_LEN];
304                         VALUE_PAIR *module_fmsg_vp;
305
306                         snprintf(module_fmsg,sizeof(module_fmsg),
307                                 "rlm_checkval: This %s is not allowed for the user", data->item_name);
308                         module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
309                         pairadd(&request->packet->vps, module_fmsg_vp);
310                 }
311         }
312
313
314         return ret;
315 }
316
317 /*
318  */
319 static int checkval_authorize(void *instance, REQUEST *request)
320 {
321         return do_checkval(instance,request);
322 }
323
324 static int checkval_accounting(void *instance, REQUEST *request)
325 {
326         return do_checkval(instance,request);
327 }
328
329 /*
330  *      The module name should be the only globally exported symbol.
331  *      That is, everything else should be 'static'.
332  *
333  *      If the module needs to temporarily modify it's instantiation
334  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
335  *      The server will then take care of ensuring that the module
336  *      is single-threaded.
337  */
338 module_t rlm_checkval = {
339         "checkval",
340         0,              /* type */
341         NULL,                           /* initialization */
342         checkval_instantiate,           /* instantiation */
343         {
344                 NULL,                   /* authentication */
345                 checkval_authorize,     /* authorization */
346                 NULL,                   /* preaccounting */
347                 checkval_accounting,    /* accounting */
348                 NULL,                   /* checksimul */
349                 NULL,                   /* pre-proxy */
350                 NULL,                   /* post-proxy */
351                 NULL                    /* post-auth */
352         },
353         checkval_detach,                /* detach */
354         NULL,                           /* destroy */
355 };