#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include "extern.h"
#include "otp.h"
#include "otp_pw_valid.h"
#ifdef HAVE_PTHREAD_H
#include <pthread.h>
#endif
#include <sys/un.h>
static int
otprc2rlmrc(int rc)
{
switch (rc) {
case OTP_RC_OK: return RLM_MODULE_OK;
case OTP_RC_USER_UNKNOWN: return RLM_MODULE_REJECT;
case OTP_RC_AUTHINFO_UNAVAIL: return RLM_MODULE_REJECT;
case OTP_RC_AUTH_ERR: return RLM_MODULE_REJECT;
case OTP_RC_MAXTRIES: return RLM_MODULE_USERLOCK;
case OTP_RC_NEXTPASSCODE: return RLM_MODULE_USERLOCK;
case OTP_RC_IPIN: return RLM_MODULE_REJECT;
case OTP_RC_SERVICE_ERR: return RLM_MODULE_FAIL;
default: return RLM_MODULE_FAIL;
}
}
static otp_fd_t *otp_fd_head;
static pthread_mutex_t otp_fd_head_mutex = PTHREAD_MUTEX_INITIALIZER;
int
otp_pw_valid(REQUEST *request, int pwe, const char *challenge,
const otp_option_t *opt, char passcode[OTP_MAX_PASSCODE_LEN + 1])
{
otp_request_t otp_request;
otp_reply_t otp_reply;
VALUE_PAIR *cvp, *rvp;
char *username = request->username->vp_strvalue;
int rc;
if (request->username->length > OTP_MAX_USERNAME_LEN) {
(void) radlog(L_AUTH, "rlm_otp: username [%s] too long", username);
return RLM_MODULE_REJECT;
}
otp_request.version = 2;
(void) strcpy(otp_request.username, username);
(void) strcpy(otp_request.challenge, challenge);
otp_request.pwe.pwe = pwe;
cvp = pairfind(request->packet->vps, pwattr[pwe - 1]);
rvp = pairfind(request->packet->vps, pwattr[pwe]);
if (!rvp || !cvp)
return RLM_MODULE_REJECT;
switch (otp_request.pwe.pwe) {
case PWE_PAP:
if (rvp->length > OTP_MAX_PASSCODE_LEN) {
(void) radlog(L_AUTH, "rlm_otp: passcode for [%s] too long", username);
return RLM_MODULE_REJECT;
}
(void) strcpy(otp_request.pwe.u.pap.passcode, rvp->vp_strvalue);
break;
case PWE_CHAP:
if (cvp->length > 16) {
(void) radlog(L_AUTH, "rlm_otp: CHAP challenge for [%s] too long",
username);
return RLM_MODULE_INVALID;
}
if (rvp->length != 17) {
(void) radlog(L_AUTH, "rlm_otp: CHAP response for [%s] wrong size",
username);
return RLM_MODULE_INVALID;
}
(void) memcpy(otp_request.pwe.u.chap.challenge, cvp->vp_strvalue,
cvp->length);
otp_request.pwe.u.chap.clen = cvp->length;
(void) memcpy(otp_request.pwe.u.chap.response, rvp->vp_strvalue,
rvp->length);
otp_request.pwe.u.chap.rlen = rvp->length;
break;
case PWE_MSCHAP:
if (cvp->length != 8) {
(void) radlog(L_AUTH, "rlm_otp: MS-CHAP challenge for [%s] wrong size",
username);
return RLM_MODULE_INVALID;
}
if (rvp->length != 50) {
(void) radlog(L_AUTH, "rlm_otp: MS-CHAP response for [%s] wrong size",
username);
return RLM_MODULE_INVALID;
}
(void) memcpy(otp_request.pwe.u.chap.challenge, cvp->vp_strvalue,
cvp->length);
otp_request.pwe.u.chap.clen = cvp->length;
(void) memcpy(otp_request.pwe.u.chap.response, rvp->vp_strvalue,
rvp->length);
otp_request.pwe.u.chap.rlen = rvp->length;
break;
case PWE_MSCHAP2:
if (cvp->length != 16) {
(void) radlog(L_AUTH, "rlm_otp: MS-CHAP2 challenge for [%s] wrong size",
username);
return RLM_MODULE_INVALID;
}
if (rvp->length != 50) {
(void) radlog(L_AUTH, "rlm_otp: MS-CHAP2 response for [%s] wrong size",
username);
return RLM_MODULE_INVALID;
}
(void) memcpy(otp_request.pwe.u.chap.challenge, cvp->vp_strvalue,
cvp->length);
otp_request.pwe.u.chap.clen = cvp->length;
(void) memcpy(otp_request.pwe.u.chap.response, rvp->vp_strvalue,
rvp->length);
otp_request.pwe.u.chap.rlen = rvp->length;
break;
}
otp_request.username[OTP_MAX_USERNAME_LEN] = '\0';
otp_request.challenge[OTP_MAX_CHALLENGE_LEN] = '\0';
if (otp_request.pwe.pwe == PWE_PAP)
otp_request.pwe.u.pap.passcode[OTP_MAX_PASSCODE_LEN] = '\0';
otp_request.allow_sync = opt->allow_sync;
otp_request.allow_async = opt->allow_async;
otp_request.challenge_delay = opt->challenge_delay;
otp_request.resync = 1;
rc = otp_verify(opt, &otp_request, &otp_reply);
if (rc == OTP_RC_OK)
(void) strcpy(passcode, otp_reply.passcode);
return otprc2rlmrc(rc);
}
static int
otp_verify(const otp_option_t *opt,
const otp_request_t *request, otp_reply_t *reply)
{
otp_fd_t *fdp;
int rc;
int tryagain = 2;
retry:
if (!tryagain--)
return -1;
fdp = otp_getfd(opt);
if (!fdp || fdp->fd == -1)
return -1;
if ((rc = otp_write(fdp, (const char *) request, sizeof(*request))) != sizeof(*request)) {
if (rc == 0)
goto retry;
else
return -1;
}
if ((rc = otp_read(fdp, (char *) reply, sizeof(*reply))) != sizeof(*reply)) {
if (rc == 0)
goto retry;
else
return -1;
}
if (reply->version != 1) {
(void) radlog(L_AUTH, "rlm_otp: otpd reply for [%s] invalid "
"(version %d != 1)",
request->username, reply->version);
otp_putfd(fdp, 1);
return -1;
}
if (reply->passcode[OTP_MAX_PASSCODE_LEN] != '\0') {
(void) radlog(L_AUTH, "rlm_otp: otpd reply for [%s] invalid (passcode)",
request->username);
otp_putfd(fdp, 1);
return -1;
}
otp_putfd(fdp, 0);
return reply->rc;
}
static int
otp_read(otp_fd_t *fdp, char *buf, size_t len)
{
ssize_t n;
size_t nread = 0;
while (nread < len) {
if ((n = read(fdp->fd, &buf[nread], len - nread)) == -1) {
if (errno == EINTR) {
continue;
} else {
(void) radlog(L_ERR, "rlm_otp: %s: read from otpd: %s",
__func__, strerror(errno));
otp_putfd(fdp, 1);
return -1;
}
}
if (!n) {
(void) radlog(L_ERR, "rlm_otp: %s: otpd disconnect", __func__);
otp_putfd(fdp, 1);
return 0;
}
nread += n;
}
return nread;
}
static int
otp_write(otp_fd_t *fdp, const char *buf, size_t len)
{
size_t nleft = len;
ssize_t nwrote;
while (nleft) {
if ((nwrote = write(fdp->fd, &buf[len - nleft], nleft)) == -1) {
if (errno == EINTR) {
continue;
} else {
(void) radlog(L_ERR, "rlm_otp: %s: write to otpd: %s",
__func__, strerror(errno));
otp_putfd(fdp, 1);
return errno;
}
}
nleft -= nwrote;
}
return 0;
}
static int
otp_connect(const char *path)
{
int fd;
struct sockaddr_un sa;
size_t sp_len;
sp_len = strlen(path);
if (sp_len > sizeof(sa.sun_path) - 1) {
(void) radlog(L_ERR, "rlm_otp: %s: rendezvous point name too long",
__func__);
return -1;
}
sa.sun_family = AF_UNIX;
(void) strcpy(sa.sun_path, path);
if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
(void) radlog(L_ERR, "rlm_otp: %s: socket: %s", __func__, strerror(errno));
return -1;
}
if (connect(fd, (struct sockaddr *) &sa,
sizeof(sa.sun_family) + sp_len) == -1) {
(void) radlog(L_ERR, "rlm_otp: %s: connect(%s): %s",
__func__, path, strerror(errno));
(void) close(fd);
return -1;
}
return fd;
}
static otp_fd_t *
otp_getfd(const otp_option_t *opt)
{
int rc;
otp_fd_t *fdp;
for (fdp = otp_fd_head; fdp; fdp = fdp->next) {
rc = otp_pthread_mutex_trylock(&fdp->mutex);
if (!rc)
if (!strcmp(fdp->path, opt->otpd_rp))
break;
}
if (!fdp) {
fdp = rad_malloc(sizeof(*fdp));
otp_pthread_mutex_init(&fdp->mutex, NULL);
otp_pthread_mutex_lock(&fdp->mutex);
otp_pthread_mutex_lock(&otp_fd_head_mutex);
fdp->next = otp_fd_head;
otp_fd_head = fdp;
otp_pthread_mutex_unlock(&otp_fd_head_mutex);
fdp->path = opt->otpd_rp;
fdp->fd = -1;
}
if (fdp->fd == -1)
fdp->fd = otp_connect(fdp->path);
return fdp;
}
static void
otp_putfd(otp_fd_t *fdp, int disconnect)
{
if (disconnect) {
(void) close(fdp->fd);
fdp->fd = -1;
}
otp_pthread_mutex_unlock(&fdp->mutex);
}