#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 <fcntl.h>
#include <limits.h>
#include "config.h"
#define LOCK_LEN sizeof(struct radutmp)
static const char porttypes[] = "ASITX";
typedef struct nas_port {
uint32_t nasaddr;
unsigned int port;
off_t offset;
struct nas_port *next;
} NAS_PORT;
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 }
};
static int radutmp_instantiate(CONF_SECTION *conf, void **instance)
{
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;
}
static int radutmp_detach(void *instance)
{
NAS_PORT *p, *next;
rlm_radutmp_t *inst = instance;
for (p = inst->nas_port_list ; p ; p=next) {
next = p->next;
free(p);
}
free(inst);
return 0;
}
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(filename, O_RDWR);
if (fd < 0) {
radlog(L_ERR, "rlm_radutmp: Error accessing file %s: %s",
filename, strerror(errno));
return RLM_MODULE_FAIL;
}
rad_lockfd(fd, LOCK_LEN);
while (read(fd, &u, sizeof(u)) == sizeof(u)) {
if ((nasaddr != 0 && nasaddr != u.nas_address) ||
u.type != P_LOGIN)
continue;
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);
return 0;
}
static NAS_PORT *nas_port_find(NAS_PORT *nas_port_list, uint32_t nasaddr, unsigned int port)
{
NAS_PORT *cl;
for(cl = nas_port_list; cl; cl = cl->next)
if (nasaddr == cl->nasaddr &&
port == cl->port)
break;
return cl;
}
static int radutmp_accounting(void *instance, REQUEST *request)
{
struct radutmp ut, u;
VALUE_PAIR *vp;
int status = -1;
int protocol = -1;
time_t t;
int fd;
int port_seen = 0;
int off;
rlm_radutmp_t *inst = instance;
char buffer[256];
char filename[1024];
char ip_name[32];
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;
}
if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) == NULL) {
radlog(L_ERR, "rlm_radutmp: No Accounting-Status-Type record.");
return RLM_MODULE_NOOP;
}
status = vp->vp_integer;
if ((status != PW_STATUS_ACCOUNTING_ON) &&
(status != PW_STATUS_ACCOUNTING_OFF)) do {
int check1 = 0;
int check2 = 0;
if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME))
== NULL || vp->vp_date == 0)
check1 = 1;
if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID))
!= NULL && vp->length == 8 &&
memcmp(vp->vp_strvalue, "00000000", 8) == 0)
check2 = 1;
if (check1 == 0 || check2 == 0) {
#if 0
radlog(L_ERR, "rlm_radutmp: no username in record");
return RLM_MODULE_FAIL;
#else
break;
#endif
}
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;
} while(0);
time(&t);
memset(&ut, 0, sizeof(ut));
ut.porttype = 'A';
ut.nas_address = htonl(INADDR_NONE);
for (vp = request->packet->vps; vp; vp = vp->next) {
switch (vp->attribute) {
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 = 1;
break;
case PW_ACCT_DELAY_TIME:
ut.delay = vp->vp_integer;
break;
case PW_ACCT_SESSION_ID:
off = vp->length - sizeof(ut.session_id);
if (vp->length > 0 &&
vp->vp_strvalue[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->callerid_ok)
strlcpy(ut.caller_id,
(char *)vp->vp_strvalue,
sizeof(ut.caller_id));
break;
}
}
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) {
nas = request->client->shortname;
} else {
nas = ip_ntoa(ip_name, ut.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;
radius_xlat(filename, sizeof(filename), inst->filename, request, NULL);
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 &&
(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;
}
if (status != PW_STATUS_START &&
status != PW_STATUS_STOP &&
status != PW_STATUS_ALIVE) {
radlog(L_ERR, "rlm_radutmp: NAS %s port %u unknown packet type %d)",
nas, ut.nas_port, status);
return RLM_MODULE_NOOP;
}
*buffer = '\0';
radius_xlat(buffer, sizeof(buffer), inst->username, request, NULL);
strlcpy(ut.login, buffer, RUT_NAMESIZE);
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;
}
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;
}
rad_lockfd(fd, LOCK_LEN);
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 &&
u.type == P_IDLE) {
continue;
}
if (status == PW_STATUS_STOP &&
strncmp(ut.session_id, u.session_id,
sizeof(u.session_id)) != 0) {
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, "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;
}
if (status == PW_STATUS_ALIVE &&
strncmp(ut.session_id, u.session_id,
sizeof(u.session_id)) == 0 &&
u.type == P_LOGIN) {
ut.time = u.time;
}
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;
}
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 (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);
return RLM_MODULE_OK;
}
static int radutmp_checksimul(void *instance, REQUEST *request)
{
struct radutmp u;
int fd;
VALUE_PAIR *vp;
uint32_t ipno = 0;
char *call_num = NULL;
int rcode;
rlm_radutmp_t *inst = instance;
char login[256];
char filename[1024];
radius_xlat(filename, sizeof(filename), inst->filename, request, NULL);
if ((fd = open(filename, O_RDWR)) < 0) {
if (errno == ENOENT) {
request->simul_count=0;
return RLM_MODULE_OK;
}
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) {
return RLM_MODULE_NOOP;
}
request->simul_count = 0;
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) ||
!inst->check_nas) {
close(fd);
return RLM_MODULE_OK;
}
lseek(fd, (off_t)0, SEEK_SET);
if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS)) != NULL)
ipno = vp->vp_ipaddr;
if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
call_num = vp->vp_strvalue;
rad_lockfd(fd, LOCK_LEN);
request->simul_count = 0;
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)) {
char session_id[sizeof(u.session_id) + 1];
char utmp_login[sizeof(u.login) + 1];
strlcpy(session_id, u.session_id, sizeof(session_id));
strlcpy(utmp_login, u.login, sizeof(u.login));
rad_unlockfd(fd, LOCK_LEN);
rcode = rad_check_ts(u.nas_address, u.nas_port,
utmp_login, session_id);
rad_lockfd(fd, LOCK_LEN);
if (rcode == 0) {
session_zap(request, u.nas_address,
u.nas_port, login, session_id,
u.framed_address, u.proto,0);
}
else if (rcode == 1) {
++request->simul_count;
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 {
close(fd);
radlog(L_ERR, "rlm_radutmp: Failed to check the terminal server for user '%s'.", utmp_login);
return RLM_MODULE_FAIL;
}
}
}
close(fd);
return RLM_MODULE_OK;
}
module_t rlm_radutmp = {
RLM_MODULE_INIT,
"radutmp",
RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
radutmp_instantiate,
radutmp_detach,
{
NULL,
NULL,
NULL,
radutmp_accounting,
radutmp_checksimul,
NULL,
NULL,
NULL
},
};