merge of alpha code from Alan Curry. It doesn't build as part of
authoraland <aland>
Tue, 7 Dec 1999 20:42:35 +0000 (20:42 +0000)
committeraland <aland>
Tue, 7 Dec 1999 20:42:35 +0000 (20:42 +0000)
the system, but at least the code is in CVS, so the diff's are
smaller.

src/modules/rlm_radutmp/Makefile [new file with mode: 0644]
src/modules/rlm_radutmp/rlm_radutmp.c [new file with mode: 0644]

diff --git a/src/modules/rlm_radutmp/Makefile b/src/modules/rlm_radutmp/Makefile
new file mode 100644 (file)
index 0000000..d601363
--- /dev/null
@@ -0,0 +1,19 @@
+include config.mak
+
+#
+#  This module is NOT built by default, as it's in a REALLY alpha
+#  development stage, and doesn't integrate well with the rest of the
+#  system.
+#
+#TARGET        = rlm_radutmp
+#SRCS  = rlm_radutmp.c
+
+include ../rules.mak
+
+rlm_radutmp.c: config.h
+
+config: config.mak
+
+# Just a dummy entry for now, but someday it'll use mmap
+config.mak config.h:
+       @../checkconfig -I HAVE_SYS_MMAN_H sys/mman.h
diff --git a/src/modules/rlm_radutmp/rlm_radutmp.c b/src/modules/rlm_radutmp/rlm_radutmp.c
new file mode 100644 (file)
index 0000000..afcb75f
--- /dev/null
@@ -0,0 +1,629 @@
+#include       "autoconf.h"
+
+#include       <sys/types.h>
+#include       <stdio.h>
+#include       <string.h>
+#include       <unistd.h>
+#include       <fcntl.h>
+#include       <time.h>
+#include       <errno.h>
+#include        <limits.h>
+
+#include "config.h"
+
+#if HAVE_MALLOC_H
+#  include <malloc.h>
+#endif
+
+#include       "radiusd.h"
+#include       "radutmp.h"
+#include       "modules.h"
+
+#define LOCK_LEN sizeof(struct radutmp)
+
+static char porttypes[] = "ASITX";
+
+/*
+ *     used for caching radutmp lookups in the accounting component. The
+ *     session (checksimul) component doesn't use it, but probably should.
+ */
+typedef struct nas_port {
+       UINT4                   nasaddr;
+       int                     port;
+       off_t                   offset;
+       struct nas_port         *next;
+} NAS_PORT;
+
+struct acct_instance {
+  NAS_PORT *nas_port_list;
+  char radutmp_fn[PATH_MAX];
+  int permission;
+  int callerid_ok:1;
+};
+
+struct sess_instance {
+  char radutmp_fn[PATH_MAX];
+};
+
+
+/* RADDB/modules syntax for radutmp accounting:
+ *  accounting rlm_radutmp.so [filename] [permission] [option...]
+ * filename defaults to the old location (usually /var/log/radutmp)
+ * permission defaults to 644, but the umask radiusd was started with applies
+ * current options are "callerid" and "nocallerid". default is nocallerid
+ * unless a restrictive permission is specified.
+ *
+ * For session component:
+ *  session rlm_radutmp.so [filename] */
+static int radutmp_instantiate(int component, int argc, char **argv,
+                              void **instance)
+{
+       switch(component) {
+               case RLM_COMPONENT_ACCT:
+               {
+                       const char *fn=RADUTMP;
+                       unsigned int perm=0644;
+                       int callerid=-1;
+
+                       if(argc) {
+                               fn=argv[0];
+                               --argc,++argv;
+                       }
+                       if(argc) {
+                               if(sscanf(argv[0], "%o", &perm)!=1) {
+                                       log(L_ERR, "Invalid permission %s",
+                                           argv[0]);
+                               }
+                               --argc,++argv;
+                       }
+                       while(argc) {
+                               if(!strcmp(argv[0], "callerid")) {
+                                       callerid=1;
+                               } else if(!strcmp(argv[0], "nocallerid")) {
+                                       callerid=0;
+                               } else {
+                                       log(L_ERR, "Unknown option %s",
+                                           argv[0]);
+                                       return -1;
+                               }
+                               --argc,++argv;
+                       }
+
+                       if(callerid==-1)
+                               callerid = !(perm & 004);
+
+                       *instance = malloc(sizeof(struct acct_instance));
+                       if (!*instance) {
+                               log(L_ERR|L_CONS, "Out of memory\n");
+                               return -1;
+                       }
+#define ru_instance ((struct acct_instance *)(*instance))
+                       ru_instance->nas_port_list=0;
+                       if(*fn=='/')
+                               strNcpy(ru_instance->radutmp_fn, fn, PATH_MAX);
+                       else
+                               snprintf(ru_instance->radutmp_fn, PATH_MAX,
+                                        "%s/%s", LOGDIR, fn);
+                       ru_instance->permission=perm;
+                       ru_instance->callerid_ok=callerid;
+#undef ru_instance
+               }
+                       break;
+               case RLM_COMPONENT_SESS:
+               {
+                       const char *fn=RADUTMP;
+
+                       if(argc) {
+                               fn=argv[0];
+                               --argc,++argv;
+                       }
+                       if(argc) {
+                               log(L_ERR,
+                                   "Too many args in radutmp session config\n");
+                               return -1;
+                       }
+
+                       *instance = malloc(sizeof(struct sess_instance));
+                       if (!*instance) {
+                               log(L_ERR|L_CONS, "Out of memory\n");
+                               return -1;
+                       }
+#define ru_instance ((struct sess_instance *)(*instance))
+                       if(*fn=='/')
+                               strNcpy(ru_instance->radutmp_fn, fn, PATH_MAX);
+                       else
+                               snprintf(ru_instance->radutmp_fn, PATH_MAX,
+                                        "%s/%s", LOGDIR, fn);
+#undef ru_instance
+               }
+                       break;
+               default:
+                       *instance=0;
+                       break;
+       }
+       return 0;
+}
+
+
+/*
+ *     Detach.
+ */
+static int radutmp_detach(int component, void *instance)
+{
+       switch(component) {
+               case RLM_COMPONENT_ACCT:
+               {
+                       NAS_PORT *p, *next;
+                       struct acct_instance *inst = instance;
+
+                       for(p=inst->nas_port_list ; p ; p=next) {
+                               next=p->next;
+                               free(p);
+                       }
+                       free(instance);
+               }
+                       break;
+               case RLM_COMPONENT_SESS:
+                       free(instance);
+                       break;
+       }
+       return 0;
+}
+
+/*
+ *     Zap all users on a NAS from the radutmp file.
+ */
+static int radutmp_zap(struct acct_instance *inst, UINT4 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().
+                */
+#if defined(F_LOCK) && !defined(BSD)
+               (void)lockf(fd, F_LOCK, LOCK_LEN);
+#else
+               (void)flock(fd, LOCK_EX);
+#endif
+               /*
+                *      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) {
+                               log(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);
+       }
+
+       return 0;
+}
+
+/*
+ *     Lookup a NAS_PORT in the nas_port_list
+ */
+static NAS_PORT *nas_port_find(NAS_PORT *nas_port_list, UINT4 nasaddr, int port)
+{
+       NAS_PORT        *cl;
+
+       for(cl = nas_port_list; cl; cl = cl->next)
+               if (nasaddr == cl->nasaddr &&
+                       port == cl->port)
+                       break;
+       return cl;
+}
+
+
+/*
+ *     Store logins in the RADIUS utmp file.
+ */
+static int radutmp_accounting(void *instance, REQUEST *request)
+{
+       struct radutmp  ut, u;
+       VALUE_PAIR      *vp;
+       int             rb_record = 0;
+       int             status = -1;
+       int             nas_address = 0;
+       int             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 acct_instance *inst = instance;
+
+       /*
+        *      Which type is this.
+        */
+       if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) == NULL) {
+               log(L_ERR, "Accounting: no Accounting-Status-Type record.");
+               return RLM_ACCT_FAIL;
+       }
+       status = vp->lvalue;
+       if (status == PW_STATUS_ACCOUNTING_ON ||
+           status == PW_STATUS_ACCOUNTING_OFF) rb_record = 1;
+
+       if (!rb_record &&
+           (vp = pairfind(request->packet->vps, PW_USER_NAME)) == NULL) 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)
+                       check1 = 1;
+               if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID))
+                    != NULL && vp->length == 8 &&
+                    memcmp(vp->strvalue, "00000000", 8) == 0)
+                       check2 = 1;
+               if (check1 == 0 || check2 == 0) {
+#if 0 /* Cisco sometimes sends START records without username. */
+                       log(L_ERR, "Accounting: no username in record");
+                       return RLM_ACCT_FAIL;
+#else
+                       break;
+#endif
+               }
+               log(L_INFO, "Accounting: 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';
+
+       /*
+        *      First, find the interesting attributes.
+        */
+       for (vp = request->packet->vps; vp; vp = vp->next) {
+               switch (vp->attribute) {
+                       case PW_USER_NAME:
+                               strncpy(ut.login, vp->strvalue, RUT_NAMESIZE);
+                               break;
+                       case PW_LOGIN_IP_HOST:
+                       case PW_FRAMED_IP_ADDRESS:
+                               framed_address = vp->lvalue;
+                               ut.framed_address = vp->lvalue;
+                               break;
+                       case PW_FRAMED_PROTOCOL:
+                               protocol = vp->lvalue;
+                               break;
+                       case PW_NAS_IP_ADDRESS:
+                               nas_address = vp->lvalue;
+                               ut.nas_address = vp->lvalue;
+                               break;
+                       case PW_NAS_PORT_ID:
+                               ut.nas_port = vp->lvalue;
+                               port_seen = 1;
+                               break;
+                       case PW_ACCT_DELAY_TIME:
+                               ut.delay = vp->lvalue;
+                               break;
+                       case PW_ACCT_SESSION_ID:
+                               /*
+                                *      If length > 8, only store the
+                                *      last 8 bytes.
+                                */
+                               off = vp->length - sizeof(ut.session_id);
+                               if (off < 0) off = 0;
+                               memcpy(ut.session_id, 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;
+                               break;
+                       case PW_CALLING_STATION_ID:
+                               if(inst->callerid_ok)
+                                       strNcpy(ut.caller_id, vp->strvalue,
+                                               sizeof(ut.caller_id));
+                               break;
+               }
+       }
+
+       /*
+        *      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 (protocol == PW_PPP)
+               ut.proto = 'P';
+       else if (protocol == PW_SLIP)
+               ut.proto = 'S';
+       else
+               ut.proto = 'T';
+       ut.time = t - ut.delay;
+
+       /*
+        *      See if this was a portmaster reboot.
+        */
+       if (status == PW_STATUS_ACCOUNTING_ON && nas_address) {
+               log(L_INFO, "NAS %s restarted (Accounting-On packet seen)",
+                       nas_name(nas_address));
+               radutmp_zap(inst, nas_address, ut.time);
+               return RLM_ACCT_OK;
+       }
+       if (status == PW_STATUS_ACCOUNTING_OFF && nas_address) {
+               log(L_INFO, "NAS %s rebooted (Accounting-Off packet seen)",
+                       nas_name(nas_address));
+               radutmp_zap(inst, nas_address, ut.time);
+               return RLM_ACCT_OK;
+       }
+
+       /*
+        *      If we don't know this type of entry pretend we succeeded.
+        */
+       if (status != PW_STATUS_START &&
+           status != PW_STATUS_STOP &&
+           status != PW_STATUS_ALIVE) {
+               log(L_ERR, "NAS %s port %d unknown packet type %d)",
+                       nas_name(nas_address), ut.nas_port, status);
+               return RLM_ACCT_FAIL_SOFT;
+       }
+
+       /*
+        *      Perhaps we don't want to store this record into
+        *      radutmp. We skip records:
+        *
+        *      - without a NAS-Port-Id (telnet / tcp access)
+        *      - with the username "!root" (console admin login)
+        */
+       if (!port_seen || strncmp(ut.login, "!root", RUT_NAMESIZE) == 0)
+               return RLM_ACCT_OK;
+
+       /*
+        *      Enter into the radutmp file.
+        */
+       fd = open(inst->radutmp_fn, O_RDWR|O_CREAT, inst->permission);
+       if (fd >= 0) {
+               NAS_PORT *cache;
+               int r;
+
+               /*
+                *      Lock the utmp file, prefer lockf() over flock().
+                */
+#if defined(F_LOCK) && !defined(BSD)
+               (void)lockf(fd, F_LOCK, LOCK_LEN);
+#else
+               (void)flock(fd, LOCK_EX);
+#endif
+               /*
+                *      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;
+
+                       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)
+                                       log(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_START &&
+                           strncmp(ut.session_id, u.session_id,
+                            sizeof(u.session_id)) == 0  &&
+                           u.time >= ut.time) {
+                               if (u.type == P_LOGIN) {
+                                       log(L_INFO,
+               "Accounting: login: entry for NAS %s port %d duplicate",
+                                       nas_name(nas_address), u.nas_port);
+                                       r = -1;
+                                       break;
+                               }
+                               log(L_ERR,
+               "Accounting: login: entry for NAS %s port %d wrong order",
+                               nas_name(nas_address), 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) {
+                               /*
+                                *      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) {
+                               log(L_ERR, "Accounting: negative lseek!\n");
+                               lseek(fd, (off_t)0, SEEK_SET);
+                               off = 0;
+                       } else
+                               off -= sizeof(u);
+                       r = 1;
+                       break;
+               }
+
+               if (r >= 0 &&  (status == PW_STATUS_START ||
+                               status == PW_STATUS_ALIVE)) {
+                       if (cache == NULL) {
+                          if ((cache = malloc(sizeof(NAS_PORT))) != NULL) {
+                                  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 (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) {
+                               log(L_ERR,
+               "Accounting: logout: login entry for NAS %s port %d not found",
+                               nas_name(nas_address), ut.nas_port);
+                               r = -1;
+                       }
+               }
+               close(fd);
+       } else {
+               log(L_ERR, "Accounting: %s: %s", inst->radutmp_fn, strerror(errno));
+               return RLM_ACCT_FAIL;
+       }
+
+       return RLM_ACCT_OK;
+}
+
+/*
+ *     See if a user is already logged in. Sets *count to the current
+ *     session count for this user and sets *mpp to 2 if it looks like a
+ *     multilink attempt based on the requested ipno, otherwise leaves it
+ *     alone.
+ */
+static int radutmp_checksimul(void *instance, const char *name, int *count,
+                             int doradcheck, UINT4 ipno, int *mpp)
+{
+       struct radutmp  u;
+       int             fd;
+       struct sess_instance *inst = instance;
+
+       if ((fd = open(inst->radutmp_fn, O_RDWR)) < 0) {
+               if(errno!=ENOENT)
+                       return RLM_CSIM_FAIL;
+               *count=0;
+               return RLM_CSIM_OK;
+       }
+
+       if(!doradcheck) {
+               *count = 0;
+               while(read(fd, &u, sizeof(u)) == sizeof(u))
+                       if (strncmp(name, u.login, RUT_NAMESIZE) == 0
+                           && u.type == P_LOGIN)
+                               ++*count;
+               close(fd);
+               return RLM_CSIM_OK;
+       }
+
+       /*
+        *      lockf() the file while reading/writing.
+        */
+#if defined(F_LOCK) && !defined(BSD)
+               (void)lockf(fd, F_LOCK, LOCK_LEN);
+#else
+               (void)flock(fd, LOCK_EX);
+#endif
+
+       /*
+        *      Check all registered logins by querying the
+        *      terminal server directly.
+        *      FIXME: rad_check_ts() runs with locked radutmp file!
+        */
+       *count = 0;
+       while (read(fd, &u, sizeof(u)) == sizeof(u)) {
+               if (strncmp(name, u.login, RUT_NAMESIZE) == 0
+                   && u.type == P_LOGIN) {
+                       char login[sizeof u.login+1];
+                       char session_id[sizeof u.session_id+1];
+                       strNcpy(login, u.login, sizeof login);
+                       strNcpy(session_id, u.session_id, sizeof session_id);
+                       if (rad_check_ts(u.nas_address, u.nas_port, login, 
+                                        session_id) == 1) {
+                               ++*count;
+                               /*
+                                *      Does it look like a MPP attempt?
+                                */
+                               if (strchr("SCPA", u.proto) &&
+                                   ipno && u.framed_address == ipno)
+                                       *mpp = 2;
+                       }
+                       else {
+                               /*
+                                *      False record - zap it.
+                                */
+
+                               session_zap(u.nas_address, u.nas_port, login,
+                                           session_id, u.framed_address,
+                                           u.proto, 0);
+                       }
+               }
+       }
+       close(fd);
+
+       return RLM_CSIM_OK;
+}
+
+/* globally exported name */
+module_t rlm_radutmp = {
+  "radutmp",
+  0,                            /* type: reserved */
+  NULL,                        /* initialization */
+  radutmp_instantiate,          /* initialization */
+  NULL,                         /* authorization */
+  NULL,                         /* authentication */
+  radutmp_accounting,           /* accounting */
+  radutmp_checksimul,          /* checksimul */
+  radutmp_detach,               /* detach */
+  NULL,                        /* destroy */
+};
+