Enable building #WITHOUT_PROXY
[freeradius.git] / src / modules / rlm_attr_filter / rlm_attr_filter.c
index 915ae25..cb8f61c 100644 (file)
  *   This program is is free software; you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License, version 2 if the
  *   License as published by the Free Software Foundation.
- * 
+ *
  *   This program is distributed in the hope that it will be useful,
  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *   GNU General Public License for more details.
- *  
+ *
  *   You should have received a copy of the GNU General Public License
  *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  *
- * Copyright (C) 2001 The FreeRADIUS server project
+ * Copyright (C) 2001,2006 The FreeRADIUS server project
  * Copyright (C) 2001 Chris Parker <cparker@starnetusa.net>
  */
 
-#include       "autoconf.h"
-#include       "libradius.h"
+#include       <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include       <freeradius-devel/radiusd.h>
+#include       <freeradius-devel/modules.h>
+#include       <freeradius-devel/rad_assert.h>
 
 #include       <sys/stat.h>
 
-#include       <stdlib.h>
-#include       <string.h>
-#include       <netdb.h>
 #include       <ctype.h>
 #include       <fcntl.h>
 #include        <limits.h>
 
-#ifdef HAVE_REGEX_H
-#  include      <regex.h>
-#endif
-
-#include       "radiusd.h"
-#include       "modules.h"
-
-static const char rcsid[] = "$Id$";
 
+/*
+ *     Define a structure with the module configuration, so it can
+ *     be used as the instance handle.
+ */
 struct attr_filter_instance {
-
-        /* autz */
         char *attrsfile;
+       char *key;
         PAIR_LIST *attrs;
+};
 
+static const CONF_PARSER module_config[] = {
+       { "attrsfile",     PW_TYPE_FILENAME,
+         offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
+       { "key",     PW_TYPE_STRING_PTR,
+         offsetof(struct attr_filter_instance,key), NULL, "%{Realm}" },
+       { NULL, -1, 0, NULL, NULL }
 };
 
-/*
- *     Move only the first instance of an attribute from
- *      one list to another.
- */
-static void mypairmove(VALUE_PAIR **to, VALUE_PAIR **from, int attr)
+static void check_pair(VALUE_PAIR *check_item, VALUE_PAIR *reply_item,
+                      int *pass, int *fail)
 {
-       VALUE_PAIR *to_tail, *i, *next;
-       VALUE_PAIR *iprev = NULL;
-       int     moved = 0;
+       int compare;
 
-       /* DEBUG2("    attr_filter: moving attr: %d", attr); */
+       if (check_item->operator == T_OP_SET) return;
 
-       /*
-        *      Find the last pair in the "to" list and put it in "to_tail".
-        */
-       if (*to != NULL) {
-               to_tail = *to;
-               for(i = *to; i; i = i->next)
-                       to_tail = i;
-       } else
-               to_tail = NULL;
-
-       for(i = *from; i && !moved; i = next) {
-               next = i->next;
-
-               if (i->attribute != attr) {
-                       iprev = i;
-                       continue;
-               }
-
-               /*
-                *      Remove the attribute from the "from" list.
-                */
-               if (iprev)
-                       iprev->next = next;
-               else
-                       *from = next;
-
-               /*
-                *      Add the attribute to the "to" list.
-                */
-               if (to_tail)
-                       to_tail->next = i;
-               else
-                       *to = i;
-               to_tail = i;
-               i->next = NULL;
-               moved = 1;
+       compare = paircmp(check_item, reply_item);
+       if (compare == 1) {
+               ++*(pass);
+       } else {
+               ++*(fail);
        }
-}
-
-/*
- *     See if a VALUE_PAIR list contains Fall-Through = Yes
- *     
- *     FIXME: not functional at the moment
- */
-static int fallthrough(VALUE_PAIR *vp)
-{
-       VALUE_PAIR *tmp;
-
-       tmp = pairfind(vp, PW_FALL_THROUGH);
 
-       return tmp ? tmp->lvalue : 0;
+       return;
 }
 
 
-
-static CONF_PARSER module_config[] = {
-        { "attrsfile",     PW_TYPE_STRING_PTR,
-         offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
-       { NULL, -1, 0, NULL, NULL }
-};
-
 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
 {
-        int rcode;
-        PAIR_LIST *attrs = NULL;
+       int rcode;
+       PAIR_LIST *attrs = NULL;
        PAIR_LIST *entry;
        VALUE_PAIR *vp;
 
        rcode = pairlist_read(filename, &attrs, 1);
        if (rcode < 0) {
-           return -1;
+               return -1;
        }
-       
-        /*
-         *     Walk through the 'attrs' file list.
-         */
-       
+
+       /*
+        * Walk through the 'attrs' file list.
+        */
+
        entry = attrs;
        while (entry) {
-               
-           entry->check = entry->reply;
-           entry->reply = NULL;
-       
-           for (vp = entry->check; vp != NULL; vp = vp->next) {
-
-               /*
-                *      If it's NOT a vendor attribute,
-                *      and it's NOT a wire protocol
-                *      and we ignore Fall-Through,
-                *      then bitch about it, giving a
-                *      good warning message.
-                */
-               if (!(vp->attribute & ~0xffff) &&
-                   (vp->attribute > 0xff) &&
-                   (vp->attribute > 1000)) {
-                   log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
-                             "\tfound in filter list for realm \"%s\".\n",
-                             filename, entry->lineno, vp->name,
-                             entry->name);
+
+               entry->check = entry->reply;
+               entry->reply = NULL;
+
+               for (vp = entry->check; vp != NULL; vp = vp->next) {
+
+                   /*
+                    * If it's NOT a vendor attribute,
+                    * and it's NOT a wire protocol
+                    * and we ignore Fall-Through,
+                    * then bitch about it, giving a good warning message.
+                    */
+                    if ((vp->vendor == 0) &&
+                        (vp->attribute > 0xff) &&
+                        (vp->attribute > 1000)) {
+                       log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
+                                 "\tfound in filter list for realm \"%s\".\n",
+                                 filename, entry->lineno, vp->name,
+                                 entry->name);
+                   }
                }
-           }
-           
-           entry = entry->next;
+
+               entry = entry->next;
        }
 
        *pair_list = attrs;
-        return 0;
+       return 0;
 }
 
+
+/*
+ *     Clean up.
+ */
+static int attr_filter_detach(void *instance)
+{
+       struct attr_filter_instance *inst = instance;
+       pairlist_free(&inst->attrs);
+       free(inst);
+       return 0;
+}
+
+
 /*
  *     (Re-)read the "attrs" file into memory.
  */
 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
 {
-        struct attr_filter_instance *inst;
+       struct attr_filter_instance *inst;
        int rcode;
 
-        inst = rad_malloc(sizeof *inst);
+       inst = rad_malloc(sizeof *inst);
+       if (!inst) {
+               return -1;
+       }
+       memset(inst, 0, sizeof(*inst));
 
-        if (cf_section_parse(conf, inst, module_config) < 0) {
-                free(inst);
-                return -1;
-        }
+       if (cf_section_parse(conf, inst, module_config) < 0) {
+               attr_filter_detach(inst);
+               return -1;
+       }
 
        rcode = getattrsfile(inst->attrsfile, &inst->attrs);
         if (rcode != 0) {
-                radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
-                free(inst->attrsfile);
-                free(inst);
-                return -1;
-        }
-
-        *instance = inst;
-        return 0;
+               radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
+               attr_filter_detach(inst);
+               return -1;
+       }
+       *instance = inst;
+       return 0;
 }
 
+
 /*
- *     Find the named realm in the database.  Create the
- *     set of attribute-value pairs to check and reply with
- *     for this realm from the database.
+ *     Common attr_filter checks
  */
-static int attr_filter_authorize(void *instance, REQUEST *request)
+static int attr_filter_common(void *instance, REQUEST *request,
+                             RADIUS_PACKET *packet)
 {
        struct attr_filter_instance *inst = instance;
-       VALUE_PAIR      *request_pairs;
-       VALUE_PAIR      **reply_items;
-       VALUE_PAIR      *reply_item;
-       VALUE_PAIR      *reply_tmp = NULL;
-       VALUE_PAIR      *check_items;
-       VALUE_PAIR      *check_item;
-       VALUE_PAIR      *tmp;
+       VALUE_PAIR      *vp;
+       VALUE_PAIR      *output;
+       VALUE_PAIR      **output_tail;
+       VALUE_PAIR      *check_item;
        PAIR_LIST       *pl;
-       int             usedefault = 1;
-       int             found = 0;
-       int             compare;
-#ifdef HAVE_REGEX_H
-       regex_t         reg;
-#endif
-       VALUE_PAIR      *realmpair;
-        REALM           *realm;
-        char            *realmname;
+       int             found = 0;
+       int             pass, fail = 0;
+       char            *keyname = NULL;
+       VALUE_PAIR      **input;
+       char            buffer[256];
 
-       /*
-        *      It's not a proxy reply, so return NOOP
-        */
+       if (!packet) return RLM_MODULE_NOOP;
 
-       if( request->proxy == NULL ) {
-               return( RLM_MODULE_NOOP );
-       }
+       input = &(packet->vps);
 
-       request_pairs = request->packet->vps;
-       reply_items = &request->reply->vps;
+       if (!inst->key) {
+               VALUE_PAIR      *namepair;
 
-       /*
-        *      Get the realm.  Can't use request->config_items as
-        *      that gets freed by rad_authenticate....  use the one
-        *      set in the original request vps
-        */
-       realmpair = pairfind(request_pairs, PW_REALM);
-       if(!realmpair) {
-               /*    Can't find a realm, so no filtering of attributes 
-                *    or should we use a DEFAULT entry?
-                *    For now, just return NOTFOUND. (maybe NOOP?)
-                */ 
-               return RLM_MODULE_NOTFOUND;
+               namepair = pairfind(request->packet->vps, PW_REALM, 0);
+               if (!namepair) {
+                       return (RLM_MODULE_NOOP);
+               }
+               keyname = namepair->vp_strvalue;
+       } else {
+               int len;
+
+               len = radius_xlat(buffer, sizeof(buffer), inst->key,
+                                 request, NULL);
+               if (!len) {
+                       return RLM_MODULE_NOOP;
+               }
+               keyname = buffer;
        }
 
-       realmname = (char *) realmpair->strvalue;
-        realm = realm_find(realmname);
+       output = NULL;
+       output_tail = &output;
 
        /*
-        *      Find the attr_filter profile entry for the realm.
+        *      Find the attr_filter profile entry for the entry.
         */
-       for(pl = inst->attrs; pl; pl = pl->next) {
-
-           /*
-            *  If the current entry is NOT a default,
-            *  AND the realm does NOT match the current entry,
-            *  then skip to the next entry.
-            */
-           if ( ((strcmp(pl->name, "DEFAULT") != 0) && !(usedefault))
-                && (strcmp(realmname, pl->name) != 0) )  {
-                       continue;
-               }
+       for (pl = inst->attrs; pl; pl = pl->next) {
+               int fall_through = 0;
 
-               /*      THIS SECTION NEEDS LOTS OF WORK TO GET THE ATTRIBUTE 
-                *      FILTERING LOGIC WORKING PROPERLY.  RIGHT NOW IT DOES
-                *      THINGS MOSLTY RIGHT.  IT HAS SOME ISSUES WHEN YOU HAVE
-                 *      MULTIPLE A/V PAIRS FROM THE SAME ATTRIBUTE ( IE, VSA'S ).
-                *      THAT NEEDS A BIT OF WORK STILL....  -cparker@starnetusa.net
+               /*
+                *  If the current entry is NOT a default,
+                *  AND the realm does NOT match the current entry,
+                *  then skip to the next entry.
                 */
-               
-               DEBUG2("  attr_filter: Matched entry %s at line %d", pl->name, pl->lineno);
+               if ((strcmp(pl->name, "DEFAULT") != 0) &&
+                   (strcmp(keyname, pl->name) != 0))  {
+                   continue;
+               }
 
+               DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
+                      pl->lineno);
                found = 1;
-               usedefault = fallthrough(pl->check);
-               
-               check_items = pl->check;
 
-               for( check_item = check_items; check_item != NULL ; 
-                    check_item = check_item->next ) {
-
-                   /*
-                    *      If it is a SET operator, add the attribute to
-                    *      the reply list without checking reply_items.
-                    *
-                    */
-
-                   if( check_item->operator == T_OP_SET ) {
-                       tmp = paircreate(check_item->attribute, check_item->type);
-                       if( tmp == NULL ) {
-                           radlog(L_ERR|L_CONS, "no memory");
-                           exit(1);
+               for (check_item = pl->check;
+                    check_item != NULL;
+                    check_item = check_item->next) {
+                       if ((check_item->attribute == PW_FALL_THROUGH) &&
+                           (check_item->vp_integer == 1)) {
+                               fall_through = 1;
+                               continue;
                        }
-                       switch (tmp->type) {
-                           case PW_TYPE_INTEGER:
-                           case PW_TYPE_IPADDR:
-                           case PW_TYPE_DATE:
-                                tmp->lvalue = check_item->lvalue;
-                                break;
-                           default:
-                                strNcpy((char *)tmp->strvalue,
-                                        (char *)check_item->strvalue,
-                                        sizeof(tmp->strvalue));
-                                tmp->length = check_item->length;
-                                break;
-                       }
-                       /* DEBUG2("    attr_filter: creating vp %s - %d - %d",
-                              tmp->name, tmp->type, tmp->lvalue); */
-                       pairadd(&reply_tmp, tmp);
-                       continue;
-                   }
-
-                   reply_item = pairfind(*reply_items, check_item->attribute);
-
-                   /* DEBUG2("    attr_filter: checking for: %s", check_item->name); */
 
-                   if(reply_item != (VALUE_PAIR *)NULL) {
-
-                       compare = simplepaircmp(reply_item, check_item); 
-
-                       /* DEBUG2("    attr_filter: compare = %d", compare); */
-
-                       switch(check_item->operator) {
-
-                           case T_OP_EQ:
-                           default:
-                               radlog(L_ERR, "Invalid operator for item %s: "
-                                      "reverting to '=='", check_item->name);
-                               
-                           case T_OP_CMP_EQ:
-                               if (compare == 0) {
-                                   mypairmove( &reply_tmp, reply_items, 
-                                               check_item->attribute);
+                       /*
+                        *    If it is a SET operator, add the attribute to
+                        *    the output list without checking it.
+                        */
+                       if (check_item->operator == T_OP_SET ) {
+                               vp = paircopyvp(check_item);
+                               if (!vp) {
+                                       pairfree(&output);
+                                       return RLM_MODULE_FAIL;
                                }
-                               break;
-
-                           case T_OP_NE:
-                               if (compare != 0) {
-                                   mypairmove( &reply_tmp, reply_items, 
-                                               check_item->attribute);
-                               }
-                               break;
+                               *output_tail = vp;
+                               output_tail = &(vp->next);
+                       }
+               }
 
-                           case T_OP_LT:
-                               if (compare < 0) {
-                                   mypairmove( &reply_tmp, reply_items, 
-                                               check_item->attribute);
+               /*
+                *      Iterate through the input items, comparing
+                *      each item to every rule, then moving it to the
+                *      output list only if it matches all rules
+                *      for that attribute.  IE, Idle-Timeout is moved
+                *      only if it matches all rules that describe an
+                *      Idle-Timeout.
+                */
+               for (vp = *input; vp != NULL; vp = vp->next ) {
+                       /* reset the pass,fail vars for each reply item */
+                       pass = fail = 0;
+
+                       /*
+                        *      reset the check_item pointer to
+                        *      beginning of the list
+                        */
+                       for (check_item = pl->check;
+                            check_item != NULL;
+                            check_item = check_item->next) {
+                               /*
+                                *      Vendor-Specific is special, and
+                                *      matches any VSA if the comparison
+                                *      is always true.
+                                */
+                               if ((check_item->attribute == PW_VENDOR_SPECIFIC) &&
+                                   (vp->vendor != 0) &&
+                                   (check_item->operator == T_OP_CMP_TRUE)) {
+                                       pass++;
+                                       continue;
                                }
-                               break;
 
-                           case T_OP_GT:
-                               if (compare > 0) {
-                                   mypairmove( &reply_tmp, reply_items, 
-                                               check_item->attribute);
+                               if (vp->attribute == check_item->attribute) {
+                                       check_pair(check_item, vp,
+                                                  &pass, &fail);
                                }
-                               break;
-                               
-                           case T_OP_LE:
-                               if (compare <= 0) {
-                                   mypairmove( &reply_tmp, reply_items, 
-                                               check_item->attribute);
-                               }
-                               break;
+                       }
 
-                           case T_OP_GE:
-                               if (compare >= 0) {
-                                   mypairmove( &reply_tmp, reply_items, 
-                                               check_item->attribute);
+                       /* only move attribute if it passed all rules */
+                       if (fail == 0 && pass > 0) {
+                               *output_tail = paircopyvp(vp);
+                               if (!*output_tail) {
+                                       pairfree(&output);
+                                       return RLM_MODULE_FAIL;
                                }
-                               break;
-#ifdef HAVE_REGEX_H
-                           case T_OP_REG_EQ:
-                               regcomp(&reg, (char *)check_item->strvalue, 0);
-                               compare = regexec(&reg, (char *)reply_item->strvalue,
-                                                 0, NULL, 0);
-                               regfree(&reg);
-                               if (compare == 0) {
-                                   mypairmove( &reply_tmp, reply_items, 
-                                               check_item->attribute);
-                               }
-                               break;
-
-                           case T_OP_REG_NE:
-                               regcomp(&reg, (char *)check_item->strvalue, 0);
-                               compare = regexec(&reg, (char *)reply_item->strvalue,
-                                                 0, NULL, 0);
-                               regfree(&reg);
-                               if (compare != 0) {
-                                   mypairmove( &reply_tmp, reply_items, 
-                                               check_item->attribute);
-                               }
-                               break;
-#endif
+                               output_tail = &((*output_tail)->next);
                        }
-
-                   }
-
                }
-               
+
+               /* If we shouldn't fall through, break */
+               if (!fall_through)
+                       break;
        }
 
-       pairfree(&request->reply->vps);
-       request->reply->vps = reply_tmp;
-       
        /*
-        *      See if we succeeded.  If we didn't find the realm,
-        *      then exit from the module.
+        *      No entry matched.  We didn't do anything.
         */
-       if (!found)
-               return RLM_MODULE_OK;
+       if (!found) {
+               rad_assert(output == NULL);
+               return RLM_MODULE_NOOP;
+       }
 
-       /*
-        *      Remove server internal parameters.
-        */
-       pairdelete(reply_items, PW_FALL_THROUGH);
+       pairfree(input);
+       *input = output;
+
+       if (request->packet->code == PW_AUTHENTICATION_REQUEST) {
+               request->username = pairfind(request->packet->vps,
+                                            PW_STRIPPED_USER_NAME, 0);
+               if (!request->username) 
+                       request->username = pairfind(request->packet->vps,
+                                                    PW_USER_NAME, 0);
+               request->password = pairfind(request->packet->vps,
+                                            PW_USER_PASSWORD, 0);
+       }
 
        return RLM_MODULE_UPDATED;
 }
 
-/*
- *     Clean up.
- */
-static int attr_filter_detach(void *instance)
+static int attr_filter_preacct(void *instance, REQUEST *request)
 {
-        struct attr_filter_instance *inst = instance;
-        pairlist_free(&inst->attrs);
-        free(inst->attrsfile);
-        free(inst);
-       return 0;
+       return attr_filter_common(instance, request, request->packet);
+}
+
+static int attr_filter_accounting(void *instance, REQUEST *request)
+{
+       return attr_filter_common(instance, request, request->reply);
+}
+
+#ifdef WITH_PROXY
+static int attr_filter_preproxy(void *instance, REQUEST *request)
+{
+       return attr_filter_common(instance, request, request->proxy);
+}
+
+static int attr_filter_postproxy(void *instance, REQUEST *request)
+{
+       return attr_filter_common(instance, request, request->proxy_reply);
+}
+#endif
+
+static int attr_filter_postauth(void *instance, REQUEST *request)
+{
+       return attr_filter_common(instance, request, request->reply);
+}
+
+static int attr_filter_authorize(void *instance, REQUEST *request)
+{
+       return attr_filter_common(instance, request, request->packet);
 }
 
 
 /* globally exported name */
 module_t rlm_attr_filter = {
+       RLM_MODULE_INIT,
        "attr_filter",
-       0,                              /* type: reserved */
-       NULL,                           /* initialization */
+       RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,         /* type */
        attr_filter_instantiate,        /* instantiation */
+       attr_filter_detach,             /* detach */
        {
                NULL,                   /* authentication */
-               attr_filter_authorize,  /* authorization */
-               NULL,                   /* preaccounting */
-               NULL,                   /* accounting */
-               NULL                    /* checksimul */
+               attr_filter_authorize,  /* authorization */
+               attr_filter_preacct,    /* pre-acct */
+               attr_filter_accounting, /* accounting */
+               NULL,                   /* checksimul */
+#ifdef WITH_PROXY
+               attr_filter_preproxy,   /* pre-proxy */
+               attr_filter_postproxy,  /* post-proxy */
+#else
+               NULL, NULL,
+#endif
+               attr_filter_postauth    /* post-auth */
        },
-       attr_filter_detach,             /* detach */
-       NULL                            /* destroy */
 };