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