Enable building WITHOUT_ACCOUNTING
[freeradius.git] / src / modules / rlm_radutmp / rlm_radutmp.c
index f985fbf..65fe1c0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * rlm_radutmp.c       
+ * rlm_radutmp.c
  *
  * Version:    $Id$
  *
  *
  *   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 2000  The FreeRADIUS server project
+ * Copyright 2000,2006  The FreeRADIUS server project
  * FIXME add copyrights
  */
 
-#include       "autoconf.h"
+#include       <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include       <freeradius-devel/radiusd.h>
+#include       <freeradius-devel/radutmp.h>
+#include       <freeradius-devel/modules.h>
+#include       <freeradius-devel/rad_assert.h>
 
-#include       <sys/types.h>
-#include       <stdio.h>
-#include       <string.h>
-#include       <stdlib.h>
-#include       <unistd.h>
 #include       <fcntl.h>
-#include       <time.h>
-#include       <errno.h>
 #include        <limits.h>
 
 #include "config.h"
 
-#include       "radiusd.h"
-#include       "radutmp.h"
-#include       "modules.h"
-
 #define LOCK_LEN sizeof(struct radutmp)
 
 static const char porttypes[] = "ASITX";
@@ -49,39 +44,52 @@ static const char porttypes[] = "ASITX";
  */
 typedef struct nas_port {
        uint32_t                nasaddr;
-       int                     port;
+       unsigned int    port;
        off_t                   offset;
        struct nas_port         *next;
 } NAS_PORT;
 
-struct radutmp_instance {
-  NAS_PORT *nas_port_list;
-  char *radutmp_fn;
-  char *username;
-  int permission;
-  int callerid_ok;
-};
-
-static CONF_PARSER module_config[] = {
-  { "filename", PW_TYPE_STRING_PTR,
-    offsetof(struct radutmp_instance,radutmp_fn), NULL,  RADUTMP },
-  { "username", PW_TYPE_STRING_PTR,
-    offsetof(struct radutmp_instance,username), NULL,  "%{User-Name}"},
-  { "perm",     PW_TYPE_INTEGER,
-    offsetof(struct radutmp_instance,permission), NULL,  "0644" },
-  { "callerid", PW_TYPE_BOOLEAN,
-    offsetof(struct radutmp_instance,callerid_ok), NULL, "no" },
-  { NULL, -1, 0, NULL, NULL }          /* end the list */
+typedef struct rlm_radutmp_t {
+       NAS_PORT        *nas_port_list;
+       char            *filename;
+       char            *username;
+       int             case_sensitive;
+       int             check_nas;
+       int             permission;
+       int             callerid_ok;
+} rlm_radutmp_t;
+
+static const CONF_PARSER module_config[] = {
+       { "filename", PW_TYPE_STRING_PTR,
+         offsetof(rlm_radutmp_t,filename), NULL,  RADUTMP },
+       { "username", PW_TYPE_STRING_PTR,
+         offsetof(rlm_radutmp_t,username), NULL,  "%{User-Name}"},
+       { "case_sensitive", PW_TYPE_BOOLEAN,
+         offsetof(rlm_radutmp_t,case_sensitive), NULL,  "yes"},
+       { "check_with_nas", PW_TYPE_BOOLEAN,
+         offsetof(rlm_radutmp_t,check_nas), NULL,  "yes"},
+       { "perm",     PW_TYPE_INTEGER,
+         offsetof(rlm_radutmp_t,permission), NULL,  "0644" },
+       { "callerid", PW_TYPE_BOOLEAN,
+         offsetof(rlm_radutmp_t,callerid_ok), NULL, "no" },
+       { NULL, -1, 0, NULL, NULL }             /* end the list */
 };
 
 static int radutmp_instantiate(CONF_SECTION *conf, void **instance)
 {
-       struct radutmp_instance *inst;
+       rlm_radutmp_t *inst;
+
        inst = rad_malloc(sizeof(*inst));
+       if (!inst) {
+               return -1;
+       }
+       memset(inst, 0, sizeof(*inst));
+
        if (cf_section_parse(conf, inst, module_config)) {
                free(inst);
                return -1;
        }
+
        inst->nas_port_list = NULL;
        *instance = inst;
        return 0;
@@ -94,13 +102,12 @@ static int radutmp_instantiate(CONF_SECTION *conf, void **instance)
 static int radutmp_detach(void *instance)
 {
        NAS_PORT *p, *next;
-       struct radutmp_instance *inst = instance;
+       rlm_radutmp_t *inst = instance;
 
-       for(p=inst->nas_port_list ; p ; p=next) {
-               next=p->next;
+       for (p = inst->nas_port_list ; p ; p=next) {
+               next = p->next;
                free(p);
        }
-       free(inst->radutmp_fn);
        free(inst);
        return 0;
 }
@@ -108,43 +115,47 @@ static int radutmp_detach(void *instance)
 /*
  *     Zap all users on a NAS from the radutmp file.
  */
-static int radutmp_zap(struct radutmp_instance *inst, uint32_t nasaddr, time_t t)
+static int radutmp_zap(UNUSED rlm_radutmp_t *inst,
+                      const char *filename,
+                      uint32_t nasaddr,
+                      time_t t)
 {
        struct radutmp  u;
        int             fd;
 
        if (t == 0) time(&t);
 
-       fd = open(inst->radutmp_fn, O_RDWR);
-       if (fd >= 0) {
-               /*
-                *      Lock the utmp file, prefer lockf() over flock().
-                */
-               rad_lockfd(fd, LOCK_LEN);
+       fd = open(filename, O_RDWR);
+       if (fd < 0) {
+               radlog(L_ERR, "rlm_radutmp: Error accessing file %s: %s",
+                      filename, strerror(errno));
+               return RLM_MODULE_FAIL;
+       }
 
-               /*
-                *      Find the entry for this NAS / portno combination.
-                */
-               while (read(fd, &u, sizeof(u)) == sizeof(u)) {
-                       if ((nasaddr != 0 && nasaddr != u.nas_address) ||
-                             u.type != P_LOGIN)
-                               continue;
-                       /*
-                        *      Match. Zap it.
-                        */
-                       if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
-                               radlog(L_ERR, "Accounting: radutmp_zap: "
-                                             "negative lseek!\n");
-                               lseek(fd, (off_t)0, SEEK_SET);
-                       }
-                       u.type = P_IDLE;
-                       u.time = t;
-                       write(fd, &u, sizeof(u));
-               }
-               close(fd);      /* and implicitely release the locks */
-       } else {
-               radlog(L_ERR, "Accounting: %s: %m", inst->radutmp_fn);
+       /*
+        *      Lock the utmp file, prefer lockf() over flock().
+        */
+       rad_lockfd(fd, LOCK_LEN);
+
+       /*
+        *      Find the entry for this NAS / portno combination.
+        */
+       while (read(fd, &u, sizeof(u)) == sizeof(u)) {
+         if ((nasaddr != 0 && nasaddr != u.nas_address) ||
+             u.type != P_LOGIN)
+           continue;
+         /*
+          *    Match. Zap it.
+          */
+         if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
+           radlog(L_ERR, "rlm_radutmp: radutmp_zap: negative lseek!");
+           lseek(fd, (off_t)0, SEEK_SET);
+         }
+         u.type = P_IDLE;
+         u.time = t;
+         write(fd, &u, sizeof(u));
        }
+       close(fd);      /* and implicitely release the locks */
 
        return 0;
 }
@@ -152,7 +163,7 @@ static int radutmp_zap(struct radutmp_instance *inst, uint32_t nasaddr, time_t t
 /*
  *     Lookup a NAS_PORT in the nas_port_list
  */
-static NAS_PORT *nas_port_find(NAS_PORT *nas_port_list, uint32_t nasaddr, int port)
+static NAS_PORT *nas_port_find(NAS_PORT *nas_port_list, uint32_t nasaddr, unsigned int port)
 {
        NAS_PORT        *cl;
 
@@ -164,6 +175,7 @@ static NAS_PORT *nas_port_find(NAS_PORT *nas_port_list, uint32_t nasaddr, int po
 }
 
 
+#ifdef WITH_ACCOUNTING
 /*
  *     Store logins in the RADIUS utmp file.
  */
@@ -171,84 +183,77 @@ static int radutmp_accounting(void *instance, REQUEST *request)
 {
        struct radutmp  ut, u;
        VALUE_PAIR      *vp;
-       int             rb_record = 0;
        int             status = -1;
-       uint32_t        nas_address = 0;
-       uint32_t        framed_address = 0;
        int             protocol = -1;
        time_t          t;
        int             fd;
-       int             just_an_update = 0;
        int             port_seen = 0;
-       int             nas_port_type = 0;
        int             off;
-       struct radutmp_instance *inst = instance;
+       rlm_radutmp_t   *inst = instance;
        char            buffer[256];
+       char            filename[1024];
+       char            ip_name[32]; /* 255.255.255.255 */
+       const char      *nas;
+       NAS_PORT        *cache;
+       int             r;
+
+       if (request->packet->src_ipaddr.af != AF_INET) {
+               DEBUG("rlm_radutmp: IPv6 not supported!");
+               return RLM_MODULE_NOOP;
+       }
 
        /*
         *      Which type is this.
         */
-       if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) == NULL) {
-               radlog(L_ERR, "Accounting: no Accounting-Status-Type record.");
+       if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0)) == NULL) {
+               radlog(L_ERR, "rlm_radutmp: No Accounting-Status-Type record.");
                return RLM_MODULE_NOOP;
        }
-       status = vp->lvalue;
-       if (status == PW_STATUS_ACCOUNTING_ON ||
-           status == PW_STATUS_ACCOUNTING_OFF) rb_record = 1;
+       status = vp->vp_integer;
 
        /*
-        *      Translate the User-Name attribute, or whatever else
-        *      they told us to use.
+        *      Look for weird reboot packets.
+        *
+        *      ComOS (up to and including 3.5.1b20) does not send
+        *      standard PW_STATUS_ACCOUNTING_XXX messages.
+        *
+        *      Check for:  o no Acct-Session-Time, or time of 0
+        *                  o Acct-Session-Id of "00000000".
+        *
+        *      We could also check for NAS-Port, that attribute
+        *      should NOT be present (but we don't right now).
         */
-       *buffer = '\0';
-       radius_xlat(buffer, sizeof(buffer), inst->username, request, NULL);
-
-       if (!rb_record &&
-           (*buffer != '\0')) do {
+       if ((status != PW_STATUS_ACCOUNTING_ON) &&
+           (status != PW_STATUS_ACCOUNTING_OFF)) do {
                int check1 = 0;
                int check2 = 0;
 
-               /*
-                *      ComOS (up to and including 3.5.1b20) does not send
-                *      standard PW_STATUS_ACCOUNTING_XXX messages.
-                *
-                *      Check for:  o no Acct-Session-Time, or time of 0
-                *                  o Acct-Session-Id of "00000000".
-                *
-                *      We could also check for NAS-Port, that attribute
-                *      should NOT be present (but we don't right now).
-                */
-               if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME))
-                    == NULL || vp->lvalue == 0)
+               if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME, 0))
+                    == NULL || vp->vp_date == 0)
                        check1 = 1;
-               if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID))
+               if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID, 0))
                     != NULL && vp->length == 8 &&
-                    memcmp(vp->strvalue, "00000000", 8) == 0)
+                    memcmp(vp->vp_strvalue, "00000000", 8) == 0)
                        check2 = 1;
                if (check1 == 0 || check2 == 0) {
 #if 0 /* Cisco sometimes sends START records without username. */
-                       radlog(L_ERR, "Accounting: no username in record");
+                       radlog(L_ERR, "rlm_radutmp: no username in record");
                        return RLM_MODULE_FAIL;
 #else
                        break;
 #endif
                }
-               radlog(L_INFO, "Accounting: converting reboot records.");
+               radlog(L_INFO, "rlm_radutmp: converting reboot records.");
                if (status == PW_STATUS_STOP)
                        status = PW_STATUS_ACCOUNTING_OFF;
                if (status == PW_STATUS_START)
                        status = PW_STATUS_ACCOUNTING_ON;
-               rb_record = 1;
        } while(0);
 
        time(&t);
        memset(&ut, 0, sizeof(ut));
        ut.porttype = 'A';
-
-       /*
-        *  Copy the previous translated user name.
-        */
-       strncpy(ut.login, buffer, RUT_NAMESIZE);
+       ut.nas_address = htonl(INADDR_NONE);
 
        /*
         *      First, find the interesting attributes.
@@ -257,22 +262,20 @@ static int radutmp_accounting(void *instance, REQUEST *request)
                switch (vp->attribute) {
                        case PW_LOGIN_IP_HOST:
                        case PW_FRAMED_IP_ADDRESS:
-                               framed_address = vp->lvalue;
-                               ut.framed_address = vp->lvalue;
+                               ut.framed_address = vp->vp_ipaddr;
                                break;
                        case PW_FRAMED_PROTOCOL:
-                               protocol = vp->lvalue;
+                               protocol = vp->vp_integer;
                                break;
                        case PW_NAS_IP_ADDRESS:
-                               nas_address = vp->lvalue;
-                               ut.nas_address = vp->lvalue;
+                               ut.nas_address = vp->vp_ipaddr;
                                break;
-                       case PW_NAS_PORT_ID:
-                               ut.nas_port = vp->lvalue;
+                       case PW_NAS_PORT:
+                               ut.nas_port = vp->vp_integer;
                                port_seen = 1;
                                break;
                        case PW_ACCT_DELAY_TIME:
-                               ut.delay = vp->lvalue;
+                               ut.delay = vp->vp_integer;
                                break;
                        case PW_ACCT_SESSION_ID:
                                /*
@@ -286,21 +289,20 @@ static int radutmp_accounting(void *instance, REQUEST *request)
                                 *      Compensate.
                                 */
                                if (vp->length > 0 &&
-                                   vp->strvalue[vp->length - 1] == 0)
+                                   vp->vp_strvalue[vp->length - 1] == 0)
                                        off--;
                                if (off < 0) off = 0;
-                               memcpy(ut.session_id, vp->strvalue + off,
+                               memcpy(ut.session_id, vp->vp_strvalue + off,
                                        sizeof(ut.session_id));
                                break;
                        case PW_NAS_PORT_TYPE:
-                               if (vp->lvalue >= 0 && vp->lvalue <= 4)
-                                       ut.porttype = porttypes[vp->lvalue];
-                               nas_port_type = vp->lvalue;
+                               if (vp->vp_integer <= 4)
+                                       ut.porttype = porttypes[vp->vp_integer];
                                break;
                        case PW_CALLING_STATION_ID:
                                if(inst->callerid_ok)
-                                       strNcpy(ut.caller_id,
-                                               (char *)vp->strvalue,
+                                       strlcpy(ut.caller_id,
+                                               (char *)vp->vp_strvalue,
                                                sizeof(ut.caller_id));
                                break;
                }
@@ -310,11 +312,25 @@ static int radutmp_accounting(void *instance, REQUEST *request)
         *      If we didn't find out the NAS address, use the
         *      originator's IP address.
         */
-       if (nas_address == 0) {
-               nas_address = request->packet->src_ipaddr;
-               ut.nas_address = nas_address;
+       if (ut.nas_address == htonl(INADDR_NONE)) {
+               ut.nas_address = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
+               nas = request->client->shortname;
+
+       } else if (request->packet->src_ipaddr.ipaddr.ip4addr.s_addr == ut.nas_address) {               /* might be a client, might not be. */
+               nas = request->client->shortname;
+
+       } else {
+               /*
+                *      The NAS isn't a client, it's behind
+                *      a proxy server.  In that case, just
+                *      get the IP address.
+                */
+               nas = ip_ntoa(ip_name, ut.nas_address);
        }
 
+       /*
+        *      Set the protocol field.
+        */
        if (protocol == PW_PPP)
                ut.proto = 'P';
        else if (protocol == PW_SLIP)
@@ -324,18 +340,30 @@ static int radutmp_accounting(void *instance, REQUEST *request)
        ut.time = t - ut.delay;
 
        /*
-        *      See if this was a portmaster reboot.
+        *      Get the utmp filename, via xlat.
         */
-       if (status == PW_STATUS_ACCOUNTING_ON && nas_address) {
-               radlog(L_INFO, "NAS %s restarted (Accounting-On packet seen)",
-                       nas_name(nas_address));
-               radutmp_zap(inst, nas_address, ut.time);
+       radius_xlat(filename, sizeof(filename), inst->filename, request, NULL);
+
+       /*
+        *      See if this was a reboot.
+        *
+        *      Hmm... we may not want to zap all of the users when
+        *      the NAS comes up, because of issues with receiving
+        *      UDP packets out of order.
+        */
+       if (status == PW_STATUS_ACCOUNTING_ON &&
+           (ut.nas_address != htonl(INADDR_NONE))) {
+               radlog(L_INFO, "rlm_radutmp: NAS %s restarted (Accounting-On packet seen)",
+                      nas);
+               radutmp_zap(inst, filename, ut.nas_address, ut.time);
                return RLM_MODULE_OK;
        }
-       if (status == PW_STATUS_ACCOUNTING_OFF && nas_address) {
-               radlog(L_INFO, "NAS %s rebooted (Accounting-Off packet seen)",
-                       nas_name(nas_address));
-               radutmp_zap(inst, nas_address, ut.time);
+
+       if (status == PW_STATUS_ACCOUNTING_OFF &&
+           (ut.nas_address != htonl(INADDR_NONE))) {
+               radlog(L_INFO, "rlm_radutmp: NAS %s rebooted (Accounting-Off packet seen)",
+                      nas);
+               radutmp_zap(inst, filename, ut.nas_address, ut.time);
                return RLM_MODULE_OK;
        }
 
@@ -345,145 +373,182 @@ static int radutmp_accounting(void *instance, REQUEST *request)
        if (status != PW_STATUS_START &&
            status != PW_STATUS_STOP &&
            status != PW_STATUS_ALIVE) {
-               radlog(L_ERR, "NAS %s port %d unknown packet type %d)",
-                       nas_name(nas_address), ut.nas_port, status);
+               radlog(L_ERR, "rlm_radutmp: NAS %s port %u unknown packet type %d)",
+                      nas, ut.nas_port, status);
                return RLM_MODULE_NOOP;
        }
 
        /*
+        *      Translate the User-Name attribute, or whatever else
+        *      they told us to use.
+        */
+       *buffer = '\0';
+       radius_xlat(buffer, sizeof(buffer), inst->username, request, NULL);
+
+       /*
+        *  Copy the previous translated user name.
+        */
+       strlcpy(ut.login, buffer, RUT_NAMESIZE);
+
+       /*
         *      Perhaps we don't want to store this record into
         *      radutmp. We skip records:
         *
-        *      - without a NAS-Port-Id (telnet / tcp access)
+        *      - without a NAS-Port (telnet / tcp access)
         *      - with the username "!root" (console admin login)
         */
-       if (!port_seen || strncmp(ut.login, "!root", RUT_NAMESIZE) == 0)
+       if (!port_seen) {
+               DEBUG2("  rlm_radutmp: No NAS-Port seen.  Cannot do anything.");
+               DEBUG2("  rlm_radumtp: WARNING: checkrad will probably not work!");
                return RLM_MODULE_NOOP;
+       }
+
+       if (strncmp(ut.login, "!root", RUT_NAMESIZE) == 0) {
+               DEBUG2("  rlm_radutmp: Not recording administrative user");
+
+               return RLM_MODULE_NOOP;
+       }
 
        /*
         *      Enter into the radutmp file.
         */
-       fd = open(inst->radutmp_fn, O_RDWR|O_CREAT, inst->permission);
-       if (fd >= 0) {
-               NAS_PORT *cache;
-               int r;
+       fd = open(filename, O_RDWR|O_CREAT, inst->permission);
+       if (fd < 0) {
+               radlog(L_ERR, "rlm_radutmp: Error accessing file %s: %s",
+                      filename, strerror(errno));
+               return RLM_MODULE_FAIL;
+       }
 
-               /*
-                *      Lock the utmp file, prefer lockf() over flock().
-                */
-               rad_lockfd(fd, LOCK_LEN);
+       /*
+        *      Lock the utmp file, prefer lockf() over flock().
+        */
+       rad_lockfd(fd, LOCK_LEN);
+
+       /*
+        *      Find the entry for this NAS / portno combination.
+        */
+       if ((cache = nas_port_find(inst->nas_port_list, ut.nas_address,
+                                  ut.nas_port)) != NULL) {
+               lseek(fd, (off_t)cache->offset, SEEK_SET);
+       }
+
+       r = 0;
+       off = 0;
+       while (read(fd, &u, sizeof(u)) == sizeof(u)) {
+               off += sizeof(u);
+               if (u.nas_address != ut.nas_address ||
+                   u.nas_port    != ut.nas_port)
+                       continue;
 
                /*
-                *      Find the entry for this NAS / portno combination.
+                *      Don't compare stop records to unused entries.
                 */
-               if ((cache = nas_port_find(inst->nas_port_list, ut.nas_address,
-                                          ut.nas_port)) != NULL)
-                       lseek(fd, (off_t)cache->offset, SEEK_SET);
-
-               r = 0;
-               off = 0;
-               while (read(fd, &u, sizeof(u)) == sizeof(u)) {
-                       off += sizeof(u);
-                       if (u.nas_address != ut.nas_address ||
-                           u.nas_port    != ut.nas_port)
-                               continue;
-
-                       if (status == PW_STATUS_STOP &&
-                           strncmp(ut.session_id, u.session_id,
-                            sizeof(u.session_id)) != 0) {
-                               /*
-                                *      Don't complain if this is not a
-                                *      login record (some clients can
-                                *      send _only_ logout records).
-                                */
-                               if (u.type == P_LOGIN)
-                                       radlog(L_ERR,
-               "Accounting: logout: entry for NAS %s port %d has wrong ID",
-                                       nas_name(nas_address), u.nas_port);
-                               r = -1;
-                               break;
-                       }
+               if (status == PW_STATUS_STOP &&
+                   u.type == P_IDLE) {
+                       continue;
+               }
+
+               if (status == PW_STATUS_STOP &&
+                   strncmp(ut.session_id, u.session_id,
+                           sizeof(u.session_id)) != 0) {
+                       /*
+                        *      Don't complain if this is not a
+                        *      login record (some clients can
+                        *      send _only_ logout records).
+                        */
+                       if (u.type == P_LOGIN)
+                               radlog(L_ERR, "rlm_radutmp: Logout entry for NAS %s port %u has wrong ID",
+                                      nas, u.nas_port);
+                       r = -1;
+                       break;
+               }
 
-                       if (status == PW_STATUS_START &&
-                           strncmp(ut.session_id, u.session_id,
-                            sizeof(u.session_id)) == 0  &&
-                           u.time >= ut.time) {
-                               if (u.type == P_LOGIN) {
-                                       radlog(L_INFO,
-               "Accounting: login: entry for NAS %s port %d duplicate",
-                                       nas_name(nas_address), u.nas_port);
-                                       r = -1;
-                                       break;
-                               }
-                               radlog(L_ERR,
-               "Accounting: login: entry for NAS %s port %d wrong order",
-                               nas_name(nas_address), u.nas_port);
+               if (status == PW_STATUS_START &&
+                   strncmp(ut.session_id, u.session_id,
+                           sizeof(u.session_id)) == 0  &&
+                   u.time >= ut.time) {
+                       if (u.type == P_LOGIN) {
+                               radlog(L_INFO, "rlm_radutmp: Login entry for NAS %s port %u duplicate",
+                                      nas, u.nas_port);
                                r = -1;
                                break;
                        }
+                       radlog(L_ERR, "rlm_radutmp: Login entry for NAS %s port %u wrong order",
+                              nas, u.nas_port);
+                       r = -1;
+                       break;
+               }
 
+               /*
+                *      FIXME: the ALIVE record could need
+                *      some more checking, but anyway I'd
+                *      rather rewrite this mess -- miquels.
+                */
+               if (status == PW_STATUS_ALIVE &&
+                   strncmp(ut.session_id, u.session_id,
+                           sizeof(u.session_id)) == 0  &&
+                   u.type == P_LOGIN) {
                        /*
-                        *      FIXME: the ALIVE record could need
-                        *      some more checking, but anyway I'd
-                        *      rather rewrite this mess -- miquels.
+                        *      Keep the original login time.
                         */
-                       if (status == PW_STATUS_ALIVE &&
-                           strncmp(ut.session_id, u.session_id,
-                            sizeof(u.session_id)) == 0  &&
-                           u.type == P_LOGIN) {
-                               /*
-                                *      Keep the original login time.
-                                */
-                               ut.time = u.time;
-                               if (u.login[0] != 0)
-                                       just_an_update = 1;
-                       }
-
-                       if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
-                               radlog(L_ERR, "Accounting: negative lseek!\n");
-                               lseek(fd, (off_t)0, SEEK_SET);
-                               off = 0;
-                       } else
-                               off -= sizeof(u);
-                       r = 1;
-                       break;
+                       ut.time = u.time;
                }
 
-               if (r >= 0 &&  (status == PW_STATUS_START ||
-                               status == PW_STATUS_ALIVE)) {
-                       if (cache == NULL) {
-                          cache = rad_malloc(sizeof(NAS_PORT));
-                          cache->nasaddr = ut.nas_address;
-                          cache->port = ut.nas_port;
-                          cache->offset = off;
-                          cache->next = inst->nas_port_list;
-                          inst->nas_port_list = cache;
-                       }
-                       ut.type = P_LOGIN;
-                       write(fd, &ut, sizeof(u));
+               if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
+                       radlog(L_ERR, "rlm_radutmp: negative lseek!");
+                       lseek(fd, (off_t)0, SEEK_SET);
+                       off = 0;
+               } else
+                       off -= sizeof(u);
+               r = 1;
+               break;
+       } /* read the file until we find a match */
+
+       /*
+        *      Found the entry, do start/update it with
+        *      the information from the packet.
+        */
+       if (r >= 0 &&  (status == PW_STATUS_START ||
+                       status == PW_STATUS_ALIVE)) {
+               /*
+                *      Remember where the entry was, because it's
+                *      easier than searching through the entire file.
+                */
+               if (cache == NULL) {
+                       cache = rad_malloc(sizeof(NAS_PORT));
+                       cache->nasaddr = ut.nas_address;
+                       cache->port = ut.nas_port;
+                       cache->offset = off;
+                       cache->next = inst->nas_port_list;
+                       inst->nas_port_list = cache;
                }
-               if (status == PW_STATUS_STOP) {
-                       if (r > 0) {
-                               u.type = P_IDLE;
-                               u.time = ut.time;
-                               u.delay = ut.delay;
-                               write(fd, &u, sizeof(u));
-                       } else if (r == 0) {
-                               radlog(L_ERR,
-               "Accounting: logout: login entry for NAS %s port %d not found",
-                               nas_name(nas_address), ut.nas_port);
-                               r = -1;
-                       }
+
+               ut.type = P_LOGIN;
+               write(fd, &ut, sizeof(u));
+       }
+
+       /*
+        *      The user has logged off, delete the entry by
+        *      re-writing it in place.
+        */
+       if (status == PW_STATUS_STOP) {
+               if (r > 0) {
+                       u.type = P_IDLE;
+                       u.time = ut.time;
+                       u.delay = ut.delay;
+                       write(fd, &u, sizeof(u));
+               } else if (r == 0) {
+                       radlog(L_ERR, "rlm_radutmp: Logout for NAS %s port %u, but no Login record",
+                              nas, ut.nas_port);
                }
-               close(fd);      /* and implicitely release the locks */
-       } else {
-               radlog(L_ERR, "Accounting: %s: %m", inst->radutmp_fn);
-               return RLM_MODULE_FAIL;
        }
+       close(fd);      /* and implicitely release the locks */
 
        return RLM_MODULE_OK;
 }
+#endif
 
+#ifdef WITH_SESSION_MGMT
 /*
  *     See if a user is already logged in. Sets request->simul_count to the
  *     current session count for this user and sets request->simul_mpp to 2
@@ -502,29 +567,64 @@ static int radutmp_checksimul(void *instance, REQUEST *request)
        uint32_t        ipno = 0;
        char            *call_num = NULL;
        int             rcode;
-       struct radutmp_instance *inst = instance;
+       rlm_radutmp_t   *inst = instance;
        char            login[256];
+       char            filename[1024];
 
-       if ((fd = open(inst->radutmp_fn, O_RDWR)) < 0) {
-               if(errno!=ENOENT)
-                       return RLM_MODULE_FAIL;
-               request->simul_count=0;
-               return RLM_MODULE_OK;
+       /*
+        *      Get the filename, via xlat.
+        */
+       radius_xlat(filename, sizeof(filename), inst->filename, request, NULL);
+
+       if ((fd = open(filename, O_RDWR)) < 0) {
+               /*
+                *      If the file doesn't exist, then no users
+                *      are logged in.
+                */
+               if (errno == ENOENT) {
+                       request->simul_count=0;
+                       return RLM_MODULE_OK;
+               }
+
+               /*
+                *      Error accessing the file.
+                */
+               radlog(L_ERR, "rlm_radumtp: Error accessing file %s: %s",
+                      filename, strerror(errno));
+               return RLM_MODULE_FAIL;
        }
 
        *login = '\0';
        radius_xlat(login, sizeof(login), inst->username, request, NULL);
-       if(*login == '\0') 
-                       return RLM_MODULE_FAIL;
+       if (!*login) {
+               return RLM_MODULE_NOOP;
+       }
 
+       /*
+        *      WTF?  This is probably wrong... we probably want to
+        *      be able to check users across multiple session accounting
+        *      methods.
+        */
        request->simul_count = 0;
-       while(read(fd, &u, sizeof(u)) == sizeof(u)) {
-               if (strncmp(login, u.login, RUT_NAMESIZE) == 0
-                   && u.type == P_LOGIN)
+
+       /*
+        *      Loop over utmp, counting how many people MAY be logged in.
+        */
+       while (read(fd, &u, sizeof(u)) == sizeof(u)) {
+               if (((strncmp(login, u.login, RUT_NAMESIZE) == 0) ||
+                    (!inst->case_sensitive &&
+                     (strncasecmp(login, u.login, RUT_NAMESIZE) == 0))) &&
+                   (u.type == P_LOGIN)) {
                        ++request->simul_count;
+               }
        }
 
-       if(request->simul_count < request->simul_max) {
+       /*
+        *      The number of users logged in is OK,
+        *      OR, we've been told to not check the NAS.
+        */
+       if ((request->simul_count < request->simul_max) ||
+           !inst->check_nas) {
                close(fd);
                return RLM_MODULE_OK;
        }
@@ -533,44 +633,75 @@ static int radutmp_checksimul(void *instance, REQUEST *request)
        /*
         *      Setup some stuff, like for MPP detection.
         */
-       if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS)) != NULL)
-               ipno = vp->lvalue;
-       if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
-               call_num = vp->strvalue;        
+       if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0)) != NULL)
+               ipno = vp->vp_ipaddr;
+       if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0)) != NULL)
+               call_num = vp->vp_strvalue;
 
        /*
         *      lock the file while reading/writing.
         */
        rad_lockfd(fd, LOCK_LEN);
 
+       /*
+        *      FIXME: If we get a 'Start' for a user/nas/port which is
+        *      listed, but for which we did NOT get a 'Stop', then
+        *      it's not a duplicate session.  This happens with
+        *      static IP's like DSL.
+        */
        request->simul_count = 0;
        while (read(fd, &u, sizeof(u)) == sizeof(u)) {
-               if (strncmp(login, u.login, RUT_NAMESIZE) == 0
-                   && u.type == P_LOGIN) {
-                       char session_id[sizeof u.session_id+1];
-                       strNcpy(session_id, u.session_id, sizeof session_id);
+               if (((strncmp(login, u.login, RUT_NAMESIZE) == 0) ||
+                    (!inst->case_sensitive &&
+                     (strncasecmp(login, u.login, RUT_NAMESIZE) == 0))) &&
+                   (u.type == P_LOGIN)) {
+                       char session_id[sizeof(u.session_id) + 1];
+                       char utmp_login[sizeof(u.login) + 1];
+
+                       strlcpy(session_id, u.session_id, sizeof(session_id));
 
                        /*
-                        *      rad_check_ts may take seconds to return,
-                        *      and we don't want to block everyone else
-                        *      while that's happening.
+                        *      The login name MAY fill the whole field,
+                        *      and thus won't be zero-filled.
+                        *
+                        *      Note that we take the user name from
+                        *      the utmp file, as that's the canonical
+                        *      form.  The 'login' variable may contain
+                        *      a string which is an upper/lowercase
+                        *      version of u.login.  When we call the
+                        *      routine to check the terminal server,
+                        *      the NAS may be case sensitive.
+                        *
+                        *      e.g. We ask if "bob" is using a port,
+                        *      and the NAS says "no", because "BOB"
+                        *      is using the port.
                         */
+                       strlcpy(utmp_login, u.login, sizeof(u.login));
+
+                       /*
+                        *      rad_check_ts may take seconds
+                        *      to return, and we don't want
+                        *      to block everyone else while
+                        *      that's happening.  */
                        rad_unlockfd(fd, LOCK_LEN);
-                       rcode = rad_check_ts(u.nas_address, u.nas_port, login, 
-                                            session_id);
+                       rcode = rad_check_ts(u.nas_address, u.nas_port,
+                                            utmp_login, session_id);
                        rad_lockfd(fd, LOCK_LEN);
 
-                       /*
-                        *      Failed to check the terminal server for
-                        *      duplicate logins: Return an error.
-                        */
-                       if (rcode < 0) {
-                               close(fd);
-                               return RLM_MODULE_FAIL;
+                       if (rcode == 0) {
+                               /*
+                                *      Stale record - zap it.
+                                */
+                               session_zap(request, u.nas_address,
+                                           u.nas_port, login, session_id,
+                                           u.framed_address, u.proto,0);
                        }
-
-                       if (rcode == 1) {
+                       else if (rcode == 1) {
+                               /*
+                                *      User is still logged in.
+                                */
                                ++request->simul_count;
+
                                /*
                                 *      Does it look like a MPP attempt?
                                 */
@@ -583,12 +714,13 @@ static int radutmp_checksimul(void *instance, REQUEST *request)
                        }
                        else {
                                /*
-                                *      False record - zap it.
+                                *      Failed to check the terminal
+                                *      server for duplicate logins:
+                                *      Return an error.
                                 */
-                               session_zap(request->packet->sockfd,
-                                           u.nas_address, u.nas_port, login,
-                                           session_id, u.framed_address,
-                                           u.proto, 0);
+                               close(fd);
+                               radlog(L_ERR, "rlm_radutmp: Failed to check the terminal server for user '%s'.", utmp_login);
+                               return RLM_MODULE_FAIL;
                        }
                }
        }
@@ -596,21 +728,32 @@ static int radutmp_checksimul(void *instance, REQUEST *request)
 
        return RLM_MODULE_OK;
 }
+#endif
 
 /* globally exported name */
 module_t rlm_radutmp = {
-  "radutmp",
-  0,                            /* type: reserved */
-  NULL,                        /* initialization */
-  radutmp_instantiate,          /* instantiation */
-  {
-         NULL,                 /* authentication */
-         NULL,                 /* authorization */
-         NULL,                 /* preaccounting */
-         radutmp_accounting,   /* accounting */
-         radutmp_checksimul    /* checksimul */
-  },
-  radutmp_detach,               /* detach */
-  NULL,                        /* destroy */
+       RLM_MODULE_INIT,
+       "radutmp",
+       RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,         /* type */
+       radutmp_instantiate,          /* instantiation */
+       radutmp_detach,               /* detach */
+       {
+               NULL,                 /* authentication */
+               NULL,                 /* authorization */
+               NULL,                 /* preaccounting */
+#ifdef WITH_ACCOUNTING
+               radutmp_accounting,   /* accounting */
+#else
+               NULL,
+#endif
+#ifdef WITH_SESSION_MGMT
+               radutmp_checksimul,     /* checksimul */
+#else
+               NULL,
+#endif
+               NULL,                   /* pre-proxy */
+               NULL,                   /* post-proxy */
+               NULL                    /* post-auth */
+       },
 };