summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_radutmp/rlm_radutmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_radutmp/rlm_radutmp.c')
-rw-r--r--src/modules/rlm_radutmp/rlm_radutmp.c763
1 files changed, 763 insertions, 0 deletions
diff --git a/src/modules/rlm_radutmp/rlm_radutmp.c b/src/modules/rlm_radutmp/rlm_radutmp.c
new file mode 100644
index 0000000..b3d0037
--- /dev/null
+++ b/src/modules/rlm_radutmp/rlm_radutmp.c
@@ -0,0 +1,763 @@
+/*
+ * This program is is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file rlm_radutmp.c
+ * @brief Tracks sessions.
+ *
+ * @copyright 2000-2013 The FreeRADIUS server project
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/radutmp.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include <fcntl.h>
+
+#include "config.h"
+
+#define LOCK_LEN sizeof(struct radutmp)
+
+static char const 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 {
+ uint32_t nasaddr;
+ uint16_t port;
+ off_t offset;
+ struct nas_port *next;
+} NAS_PORT;
+
+typedef struct rlm_radutmp_t {
+ NAS_PORT *nas_port_list;
+ char const *filename;
+ char const *username;
+ bool case_sensitive;
+ bool check_nas;
+ uint32_t permission;
+ bool caller_id_ok;
+} rlm_radutmp_t;
+
+static const CONF_PARSER module_config[] = {
+ { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_OUTPUT | PW_TYPE_REQUIRED, rlm_radutmp_t, filename), RADUTMP },
+ { "username", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED | PW_TYPE_XLAT, rlm_radutmp_t, username), "%{User-Name}" },
+ { "case_sensitive", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_radutmp_t, case_sensitive), "yes" },
+ { "check_with_nas", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_radutmp_t, check_nas), "yes" },
+ { "perm", FR_CONF_OFFSET(PW_TYPE_INTEGER | PW_TYPE_DEPRECATED, rlm_radutmp_t, permission), NULL },
+ { "permissions", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_radutmp_t, permission), "0644" },
+ { "callerid", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_radutmp_t, caller_id_ok), NULL },
+ { "caller_id", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_radutmp_t, caller_id_ok), "no" },
+ CONF_PARSER_TERMINATOR
+};
+
+
+#ifdef WITH_ACCOUNTING
+/*
+ * Zap all users on a NAS from the radutmp file.
+ */
+static rlm_rcode_t radutmp_zap(REQUEST *request, char const *filename, uint32_t nasaddr, time_t t)
+{
+ struct radutmp u;
+ int fd;
+
+ if (t == 0) time(&t);
+
+ fd = open(filename, O_RDWR);
+ if (fd < 0) {
+ REDEBUG("Error accessing file %s: %s", filename, fr_syserror(errno));
+ return RLM_MODULE_FAIL;
+ }
+
+ /*
+ * Lock the utmp file, prefer lockf() over flock().
+ */
+ if (rad_lockfd(fd, LOCK_LEN) < 0) {
+ REDEBUG("Failed to acquire lock on file %s: %s", filename, fr_syserror(errno));
+ close(fd);
+ 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) {
+ REDEBUG("radutmp_zap: negative lseek!");
+ lseek(fd, (off_t)0, SEEK_SET);
+ }
+ u.type = P_IDLE;
+ u.time = t;
+
+ if (write(fd, &u, sizeof(u)) < 0) {
+ REDEBUG("Failed writing: %s", fr_syserror(errno));
+
+ close(fd);
+ return RLM_MODULE_FAIL;
+ }
+ }
+ close(fd); /* and implicitely release the locks */
+
+ return RLM_MODULE_OK;
+}
+
+/*
+ * Lookup a NAS_PORT in the nas_port_list
+ */
+static NAS_PORT *nas_port_find(NAS_PORT *nas_port_list, uint32_t nasaddr, uint16_t 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 rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
+{
+ rlm_rcode_t rcode = RLM_MODULE_OK;
+ struct radutmp ut, u;
+ vp_cursor_t cursor;
+ VALUE_PAIR *vp;
+ int status = -1;
+ int protocol = -1;
+ time_t t;
+ int fd = -1;
+ bool port_seen = false;
+ int off;
+ rlm_radutmp_t *inst = instance;
+ char ip_name[32]; /* 255.255.255.255 */
+ char const *nas;
+ NAS_PORT *cache;
+ int r;
+
+ char *filename = NULL;
+ char *expanded = NULL;
+
+ if (request->packet->src_ipaddr.af != AF_INET) {
+ DEBUG("rlm_radutmp: IPv6 not supported!");
+ return RLM_MODULE_NOOP;
+ }
+
+ /*
+ * Which type is this.
+ */
+ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) {
+ RDEBUG("No Accounting-Status-Type record");
+ return RLM_MODULE_NOOP;
+ }
+ status = vp->vp_integer;
+
+ /*
+ * 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).
+ */
+ if ((status != PW_STATUS_ACCOUNTING_ON) &&
+ (status != PW_STATUS_ACCOUNTING_OFF)) do {
+ int check1 = 0;
+ int check2 = 0;
+
+ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_SESSION_TIME, 0, TAG_ANY))
+ == NULL || vp->vp_date == 0)
+ check1 = 1;
+ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_SESSION_ID, 0, TAG_ANY))
+ != NULL && vp->vp_length == 8 &&
+ memcmp(vp->vp_strvalue, "00000000", 8) == 0)
+ check2 = 1;
+ if (check1 == 0 || check2 == 0) {
+ break;
+ }
+ 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;
+ } while(0);
+
+ time(&t);
+ memset(&ut, 0, sizeof(ut));
+ ut.porttype = 'A';
+ ut.nas_address = htonl(INADDR_NONE);
+
+ /*
+ * First, find the interesting attributes.
+ */
+ for (vp = fr_cursor_init(&cursor, &request->packet->vps);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ if (!vp->da->vendor) switch (vp->da->attr) {
+ case PW_LOGIN_IP_HOST:
+ case PW_FRAMED_IP_ADDRESS:
+ ut.framed_address = vp->vp_ipaddr;
+ break;
+
+ case PW_FRAMED_PROTOCOL:
+ protocol = vp->vp_integer;
+ break;
+
+ case PW_NAS_IP_ADDRESS:
+ ut.nas_address = vp->vp_ipaddr;
+ break;
+
+ case PW_NAS_PORT:
+ ut.nas_port = vp->vp_integer;
+ port_seen = true;
+ break;
+
+ case PW_ACCT_DELAY_TIME:
+ ut.delay = vp->vp_integer;
+ break;
+
+ case PW_ACCT_SESSION_ID:
+ /*
+ * If length > 8, only store the
+ * last 8 bytes.
+ */
+ off = vp->vp_length - sizeof(ut.session_id);
+ /*
+ * Ascend is br0ken - it adds a \0
+ * to the end of any string.
+ * Compensate.
+ */
+ if (vp->vp_length > 0 &&
+ vp->vp_strvalue[vp->vp_length - 1] == 0)
+ off--;
+ if (off < 0) off = 0;
+ memcpy(ut.session_id, vp->vp_strvalue + off,
+ sizeof(ut.session_id));
+ break;
+
+ case PW_NAS_PORT_TYPE:
+ if (vp->vp_integer <= 4)
+ ut.porttype = porttypes[vp->vp_integer];
+ break;
+
+ case PW_CALLING_STATION_ID:
+ if (inst->caller_id_ok) strlcpy(ut.caller_id, vp->vp_strvalue, sizeof(ut.caller_id));
+ break;
+ }
+ }
+
+ /*
+ * If we didn't find out the NAS address, use the
+ * originator's IP 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) {
+ ut.proto = 'S';
+ } else {
+ ut.proto = 'T';
+ }
+
+ ut.time = t - ut.delay;
+
+ /*
+ * Get the utmp filename, via xlat.
+ */
+ filename = NULL;
+ if (radius_axlat(&filename, request, inst->filename, NULL, NULL) < 0) {
+ return RLM_MODULE_FAIL;
+ }
+
+ /*
+ * 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))) {
+ RIDEBUG("NAS %s restarted (Accounting-On packet seen)", nas);
+ rcode = radutmp_zap(request, filename, ut.nas_address, ut.time);
+
+ goto finish;
+ }
+
+ if (status == PW_STATUS_ACCOUNTING_OFF && (ut.nas_address != htonl(INADDR_NONE))) {
+ RIDEBUG("NAS %s rebooted (Accounting-Off packet seen)", nas);
+ rcode = radutmp_zap(request, filename, ut.nas_address, ut.time);
+
+ goto finish;
+ }
+
+ /*
+ * 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) {
+ REDEBUG("NAS %s port %u unknown packet type %d)", nas, ut.nas_port, status);
+ rcode = RLM_MODULE_NOOP;
+
+ goto finish;
+ }
+
+ /*
+ * Translate the User-Name attribute, or whatever else they told us to use.
+ */
+ if (radius_axlat(&expanded, request, inst->username, NULL, NULL) < 0) {
+ rcode = RLM_MODULE_FAIL;
+
+ goto finish;
+ }
+ strlcpy(ut.login, expanded, RUT_NAMESIZE);
+ TALLOC_FREE(expanded);
+
+ /*
+ * Perhaps we don't want to store this record into
+ * radutmp. We skip records:
+ *
+ * - without a NAS-Port (telnet / tcp access)
+ * - with the username "!root" (console admin login)
+ */
+ if (!port_seen) {
+ RWDEBUG2("No NAS-Port seen. Cannot do anything. Checkrad will probably not work!");
+ rcode = RLM_MODULE_NOOP;
+
+ goto finish;
+ }
+
+ if (strncmp(ut.login, "!root", RUT_NAMESIZE) == 0) {
+ RDEBUG2("Not recording administrative user");
+ rcode = RLM_MODULE_NOOP;
+
+ goto finish;
+ }
+
+ /*
+ * Enter into the radutmp file.
+ */
+ fd = open(filename, O_RDWR|O_CREAT, inst->permission);
+ if (fd < 0) {
+ REDEBUG("Error accessing file %s: %s", filename, fr_syserror(errno));
+ rcode = RLM_MODULE_FAIL;
+
+ goto finish;
+ }
+
+ /*
+ * Lock the utmp file, prefer lockf() over flock().
+ */
+ if (rad_lockfd(fd, LOCK_LEN) < 0) {
+ REDEBUG("Error acquiring lock on %s: %s", filename, fr_syserror(errno));
+ rcode = RLM_MODULE_FAIL;
+
+ goto finish;
+ }
+
+ /*
+ * Find the entry for this NAS / portno combination.
+ */
+ if ((cache = nas_port_find(inst->nas_port_list, ut.nas_address, ut.nas_port)) != NULL) {
+ if (lseek(fd, (off_t)cache->offset, SEEK_SET) < 0) {
+ rcode = RLM_MODULE_FAIL;
+ goto finish;
+ }
+ }
+
+ 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;
+ }
+
+ /*
+ * Don't compare stop records to unused entries.
+ */
+ 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) {
+ RWDEBUG("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) {
+ INFO("rlm_radutmp: Login entry for NAS %s port %u duplicate",
+ nas, u.nas_port);
+ r = -1;
+ break;
+ }
+
+ RWDEBUG("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) {
+ /*
+ * Keep the original login time.
+ */
+ ut.time = u.time;
+ }
+
+ if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
+ RWDEBUG("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) {
+ cache = talloc_zero(NULL, NAS_PORT);
+ if (cache) {
+ 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;
+ if (write(fd, &ut, sizeof(u)) < 0) {
+ REDEBUG("Failed writing: %s", fr_syserror(errno));
+
+ rcode = RLM_MODULE_FAIL;
+ goto finish;
+ }
+ }
+
+ /*
+ * 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;
+ if (write(fd, &u, sizeof(u)) < 0) {
+ REDEBUG("Failed writing: %s", fr_syserror(errno));
+
+ rcode = RLM_MODULE_FAIL;
+ goto finish;
+ }
+ } else if (r == 0) {
+ RWDEBUG("Logout for NAS %s port %u, but no Login record", nas, ut.nas_port);
+ }
+ }
+
+ finish:
+
+ talloc_free(filename);
+
+ if (fd > -1) {
+ close(fd); /* and implicitely release the locks */
+ }
+
+ return rcode;
+}
+#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
+ * if it looks like a multilink attempt based on the requested IP
+ * address, otherwise leaves request->simul_mpp alone.
+ *
+ * Check twice. If on the first pass the user exceeds his
+ * max. number of logins, do a second pass and validate all
+ * logins by querying the terminal server (using eg. SNMP).
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_checksimul(void *instance, REQUEST *request)
+{
+ rlm_rcode_t rcode = RLM_MODULE_OK;
+ struct radutmp u;
+ int fd = -1;
+ VALUE_PAIR *vp;
+ uint32_t ipno = 0;
+ char const *call_num = NULL;
+ rlm_radutmp_t *inst = instance;
+
+ char *expanded = NULL;
+ ssize_t len;
+
+ /*
+ * Get the filename, via xlat.
+ */
+ if (radius_axlat(&expanded, request, inst->filename, NULL, NULL) < 0) {
+ return RLM_MODULE_FAIL;
+ }
+
+ fd = open(expanded, O_RDWR);
+ if (fd < 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.
+ */
+ ERROR("rlm_radumtp: Error accessing file %s: %s", expanded, fr_syserror(errno));
+
+ rcode = RLM_MODULE_FAIL;
+
+ goto finish;
+ }
+
+ TALLOC_FREE(expanded);
+
+ len = radius_axlat(&expanded, request, inst->username, NULL, NULL);
+ if (len < 0) {
+ rcode = RLM_MODULE_FAIL;
+
+ goto finish;
+ }
+
+ if (!len) {
+ rcode = RLM_MODULE_NOOP;
+
+ goto finish;
+ }
+
+ /*
+ * WTF? This is probably wrong... we probably want to
+ * be able to check users across multiple session accounting
+ * methods.
+ */
+ request->simul_count = 0;
+
+ /*
+ * Loop over utmp, counting how many people MAY be logged in.
+ */
+ while (read(fd, &u, sizeof(u)) == sizeof(u)) {
+ if (((strncmp(expanded, u.login, RUT_NAMESIZE) == 0) ||
+ (!inst->case_sensitive && (strncasecmp(expanded, u.login, RUT_NAMESIZE) == 0))) &&
+ (u.type == P_LOGIN)) {
+ ++request->simul_count;
+ }
+ }
+
+ /*
+ * 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) {
+ rcode = RLM_MODULE_OK;
+
+ goto finish;
+ }
+ lseek(fd, (off_t)0, SEEK_SET);
+
+ /*
+ * Setup some stuff, like for MPP detection.
+ */
+ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
+ ipno = vp->vp_ipaddr;
+ }
+
+ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != 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)) {
+ fr_ipaddr_t nasaddr;
+
+ if (((strncmp(expanded, u.login, RUT_NAMESIZE) == 0) || (!inst->case_sensitive &&
+ (strncasecmp(expanded, u.login, RUT_NAMESIZE) == 0))) && (u.type == P_LOGIN)) {
+ char session_id[sizeof(u.session_id) + 1];
+ char utmp_login[sizeof(u.login) + 1];
+
+ /* Guarantee string is NULL terminated */
+ u.session_id[sizeof(u.session_id) - 1] = '\0';
+ strlcpy(session_id, u.session_id, sizeof(session_id));
+
+ /*
+ * 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.
+ */
+ memset(utmp_login, 0, sizeof(utmp_login));
+ memcpy(utmp_login, u.login, sizeof(u.login));
+
+ nasaddr.af = AF_INET;
+ nasaddr.ipaddr.ip4addr.s_addr = u.nas_address;
+
+ /*
+ * 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(&nasaddr, u.nas_port, NULL, utmp_login, session_id);
+ rad_lockfd(fd, LOCK_LEN);
+
+ if (rcode == 0) {
+ /*
+ * Stale record - zap it.
+ */
+ session_zap(request, &nasaddr, u.nas_port, NULL, expanded, session_id,
+ u.framed_address, u.proto, 0);
+ }
+ else if (rcode == 1) {
+ /*
+ * User is still logged in.
+ */
+ ++request->simul_count;
+
+ /*
+ * Does it look like a MPP attempt?
+ */
+ if (strchr("SCPA", u.proto) && ipno && u.framed_address == ipno) {
+ request->simul_mpp = 2;
+ } else if (strchr("SCPA", u.proto) && call_num && !strncmp(u.caller_id, call_num,16)) {
+ request->simul_mpp = 2;
+ }
+ } else {
+ RWDEBUG("Failed to check the terminal server for user '%s'.", utmp_login);
+ rcode = RLM_MODULE_FAIL;
+
+ goto finish;
+ }
+ }
+ }
+ finish:
+
+ talloc_free(expanded);
+
+ if (fd > -1) {
+ close(fd); /* and implicitely release the locks */
+ }
+
+ return rcode;
+}
+#endif
+
+/* globally exported name */
+extern module_t rlm_radutmp;
+module_t rlm_radutmp = {
+ .magic = RLM_MODULE_INIT,
+ .name = "radutmp",
+ .type = RLM_TYPE_THREAD_UNSAFE | RLM_TYPE_HUP_SAFE,
+ .inst_size = sizeof(rlm_radutmp_t),
+ .config = module_config,
+ .methods = {
+#ifdef WITH_ACCOUNTING
+ [MOD_ACCOUNTING] = mod_accounting,
+#endif
+#ifdef WITH_SESSION_MGMT
+ [MOD_SESSION] = mod_checksimul
+#endif
+ },
+};
+