plain auth: support OTP authentication using usersfile

That adds a dependency on liboath.
This commit is contained in:
Nikos Mavrogiannopoulos
2015-09-25 11:38:56 +02:00
parent 568d6fa767
commit e5d02eb228
12 changed files with 123 additions and 17 deletions

View File

@@ -143,6 +143,21 @@ AC_LIB_HAVE_LINKFLAGS(readline,, [
fi
fi
have_liboath=no
AC_ARG_WITH(liboath,
AS_HELP_STRING([--without-liboath], [do not include OTP support]),
test_for_liboath=$withval,
test_for_liboath=yes)
echo xxx x$test_for_liboath
if test x$test_for_liboath = xyes;then
PKG_CHECK_MODULES([LIBOATH], [liboath], [
AC_DEFINE([HAVE_LIBOATH], 1, [Enable the liboath library])
have_liboath=yes],
[have_liboath=no])
fi
have_glibc=no
AC_LIB_HAVE_LINKFLAGS(c,, [
#include <stdio.h>
@@ -482,6 +497,7 @@ Summary of build options:
LZ4 compression: ${enable_lz4}
readline: ${have_readline}
libnl3: ${have_libnl3}
liboath: ${have_liboath}
glibc (sha2crypt): ${have_glibc}
local talloc: ${with_local_talloc}
local protobuf-c: ${with_local_protobuf_c}

View File

@@ -1,7 +1,7 @@
EXTRA_DIST = design.dia sample.config scripts/ocserv-script sample.passwd \
systemd/socket-activated/ocserv.service systemd/standalone/ocserv.service \
systemd/socket-activated/ocserv.socket README-radius.md \
profile.xml
profile.xml sample.otp
dist_man_MANS = ocserv.8 ocpasswd.8 occtl.8

View File

@@ -12,12 +12,14 @@
# This enabled PAM authentication of the user. The gid-min option is used
# by auto-select-group option, in order to select the minimum valid group ID.
#
# plain[passwd=/etc/ocserv/ocpasswd]
# plain[passwd=/etc/ocserv/ocpasswd,otp=/etc/ocserv/users.otp]
# The plain option requires specifying a password file which contains
# entries of the following format.
# "username:groupname1,groupname2:encoded-password"
# One entry must be listed per line, and 'ocpasswd' should be used
# to generate password entries.
# to generate password entries. The 'otp' suboption allows to specify
# an oath password file to be used for one time passwords; the format of
# the file is described in https://code.google.com/p/mod-authn-otp/wiki/UsersFile
#
# radius[config=/etc/radiusclient/radiusclient.conf,groupconfig=true,nas-identifier=name,override-interim-updates=false]:
# The radius option requires specifying freeradius-client configuration
@@ -36,6 +38,7 @@
#auth = "pam"
#auth = "pam[gid-min=1000]"
#auth = "plain[passwd=./sample.passwd,otp=./sample.otp]"
auth = "plain[passwd=./sample.passwd]"
#auth = "certificate"
#auth = "radius[config=/etc/radiusclient/radiusclient.conf,groupconfig=true]"

2
doc/sample.otp Normal file
View File

@@ -0,0 +1,2 @@
HOTP/T30 test - 8a2d55707a9084982649dadc04b426a06df19ab2 1 691245 2015-09-25T13:18:42L
HOTP tost - acbd18db4cc2f85cedef654fccc4a4d8bd537891

View File

@@ -5,7 +5,7 @@ AM_CPPFLAGS = -I$(srcdir)/../gl/ -I$(builddir)/../gl/ \
$(LIBGNUTLS_CFLAGS) \
$(LIBPROTOBUF_C_CFLAGS) $(LIBLZ4_CFLAGS) \
$(LIBNL3_CFLAGS) $(LIBREADLINE_CFLAGS) \
$(LIBTALLOC_CFLAGS) $(LIBDBUS_CFLAGS) \
$(LIBTALLOC_CFLAGS) $(LIBDBUS_CFLAGS) $(LIBOATH_CFLAGS) \
$(LIBKRB5_CFLAGS) $(LIBTASN1_CFLAGS) $(RADCLI_CFLAGS)
BUILT_SOURCES = ocpasswd-args.c ocpasswd-args.h \
@@ -103,7 +103,7 @@ ocserv_LDADD += $(LIBGNUTLS_LIBS) $(PAM_LIBS) $(LIBUTIL) \
$(LIBSECCOMP) $(LIBWRAP) $(LIBCRYPT) $(NEEDED_HTTP_PARSER_LIBS) \
$(LIBPROTOBUF_C_LIBS) $(LIBSYSTEMD) $(LIBTALLOC_LIBS) \
$(RADCLI_LIBS) $(LIBLZ4_LIBS) $(LIBKRB5_LIBS) \
$(LIBTASN1_LIBS)
$(LIBTASN1_LIBS) $(LIBOATH_LIBS)
if PCL

View File

@@ -22,4 +22,5 @@
const char* pass_msg_second = "Please enter your challenge password.";
const char* pass_msg_failed = "Login failed.\nPlease enter your password.";
const char* pass_msg_otp = "Please enter your OTP password.";

View File

@@ -4,6 +4,7 @@
#define MAX_PASSWORD_TRIES 3
extern const char* pass_msg_second;
extern const char* pass_msg_otp;
extern const char* pass_msg_failed;
#endif

View File

@@ -34,6 +34,9 @@
#include "auth/common.h"
#include <ccan/htable/htable.h>
#include <ccan/hash/hash.h>
#ifdef HAVE_LIBOATH
# include <liboath/oath.h>
#endif
#define MAX_CPASS_SIZE 128
@@ -46,9 +49,11 @@ struct plain_ctx_st {
const char *pass_msg;
unsigned retries;
unsigned failed; /* non-zero if the username is wrong */
};
static char *password_file = NULL;
static char *otp_file = NULL;
static void plain_global_init(void *pool, void *additional)
{
@@ -59,10 +64,24 @@ static void plain_global_init(void *pool, void *additional)
exit(1);
}
password_file = talloc_strdup(pool, config->passwd);
if (password_file == NULL) {
fprintf(stderr, "plain: memory error\n");
exit(1);
#ifdef HAVE_LIBOATH
oath_init();
#endif
if (config->passwd) {
password_file = talloc_strdup(pool, config->passwd);
if (password_file == NULL) {
fprintf(stderr, "plain: memory error\n");
exit(1);
}
}
if (config->otp_file) {
otp_file = talloc_strdup(pool, config->otp_file);
if (otp_file == NULL) {
fprintf(stderr, "plain: memory error\n");
exit(1);
}
}
return;
@@ -133,6 +152,13 @@ static int read_auth_pass(struct plain_ctx_st *pctx)
char *p, *sp;
int ret;
if (password_file == NULL) {
/* no password file is set */
return 0;
}
pctx->failed = 1;
fp = fopen(password_file, "r");
if (fp == NULL) {
syslog(LOG_AUTH,
@@ -168,6 +194,7 @@ static int read_auth_pass(struct plain_ctx_st *pctx)
p = strsep(&sp, ":");
if (p != NULL) {
strlcpy(pctx->cpass, p, sizeof(pctx->cpass));
pctx->failed = 0;
ret = 0;
goto exit;
}
@@ -185,6 +212,7 @@ static int read_auth_pass(struct plain_ctx_st *pctx)
p = strtok_r(NULL, ":", &sp);
if (p != NULL) {
strlcpy(pctx->cpass, p, sizeof(pctx->cpass));
pctx->failed = 0;
ret = 0;
goto exit;
}
@@ -219,6 +247,7 @@ static int plain_auth_init(void **ctx, void *pool, const char *username, const c
strlcpy(pctx->username, username, sizeof(pctx->username));
pctx->pass_msg = NULL; /* use default */
/* this doesn't fail on password mismatch but sets p->failed */
ret = read_auth_pass(pctx);
if (ret < 0) {
talloc_free(pctx);
@@ -227,6 +256,15 @@ static int plain_auth_init(void **ctx, void *pool, const char *username, const c
*ctx = pctx;
if (pctx->cpass[0] == 0 && pctx->failed == 0) {
/* if there is no password set, nor an OTP file; don't ask for password */
if (otp_file == NULL)
return 0;
/* only OTP is present */
pctx->pass_msg = pass_msg_otp;
}
return ERR_AUTH_CONTINUE;
}
@@ -272,10 +310,9 @@ static int plain_auth_pass(void *ctx, const char *pass, unsigned pass_len)
{
struct plain_ctx_st *pctx = ctx;
if (pctx->cpass[0] != 0
&& strcmp(crypt(pass, pctx->cpass), pctx->cpass) == 0)
return 0;
else {
if (pctx->failed || (pctx->cpass[0] != 0
&& strcmp(crypt(pass, pctx->cpass), pctx->cpass) != 0)) {
if (pctx->retries++ < MAX_PASSWORD_TRIES-1) {
pctx->pass_msg = pass_msg_failed;
return ERR_AUTH_CONTINUE;
@@ -286,6 +323,41 @@ static int plain_auth_pass(void *ctx, const char *pass, unsigned pass_len)
return ERR_AUTH_FAIL;
}
}
if (pctx->cpass[0] == 0 && otp_file == NULL) {
syslog(LOG_AUTH,
"plain-auth: user '%s' has empty password and no OTP file configured",
pctx->username);
return ERR_AUTH_FAIL;
}
#ifdef HAVE_LIBOATH
if (otp_file != NULL) {
int ret;
time_t last;
if (pctx->cpass[0] != 0) { /* we just checked the password */
pctx->cpass[0] = 0;
pctx->pass_msg = pass_msg_otp;
return ERR_AUTH_CONTINUE;
}
/* no primary password -> check OTP */
ret = oath_authenticate_usersfile(otp_file, pctx->username,
pass, 10, NULL, &last);
if (ret != OATH_OK) {
syslog(LOG_AUTH,
"plain-auth: OTP auth failed for '%s': %s",
pctx->username, oath_strerror(ret));
return ERR_AUTH_FAIL;
}
}
#endif
if (pctx->failed)
return ERR_AUTH_FAIL;
return 0;
}
static int plain_auth_msg(void *ctx, void *pool, passwd_msg_st *pst)

View File

@@ -50,6 +50,7 @@ typedef struct radius_cfg_st {
typedef struct plain_cfg_st {
char *passwd;
char *otp_file;
} plain_cfg_st;
typedef struct pam_cfg_st {

View File

@@ -1141,6 +1141,9 @@ void print_version(tOptions *opts, tOptDesc *desc)
#ifdef HAVE_LIBWRAP
fprintf(stderr, "tcp-wrappers, ");
#endif
#ifdef HAVE_LIBOATH
fprintf(stderr, "oath, ");
#endif
#ifdef HAVE_RADIUS
fprintf(stderr, "radius, ");
#endif

View File

@@ -87,12 +87,14 @@ An example configuration file follows.
# This enabled PAM authentication of the user. The gid-min option is used
# by auto-select-group option, in order to select the minimum valid group ID.
#
# plain[passwd=/etc/ocserv/ocpasswd]
# plain[passwd=/etc/ocserv/ocpasswd,otp=/etc/ocserv/users.otp]
# The plain option requires specifying a password file which contains
# entries of the following format.
# "username:groupname1,groupname2:encoded-password"
# One entry must be listed per line, and 'ocpasswd' should be used
# to generate password entries.
# to generate password entries. The 'otp' suboption allows to specify
# an oath password file to be used for one time passwords; the format of
# the file is described in https://code.google.com/p/mod-authn-otp/wiki/UsersFile
#
# radius[config=/etc/radiusclient/radiusclient.conf,groupconfig=true,nas-identifier=name]:
# The radius option requires specifying freeradius-client configuration

View File

@@ -314,6 +314,11 @@ void *plain_get_brackets_string(struct perm_cfg_st *config, const char *str)
if (c_strcasecmp(vals[i].name, "passwd") == 0) {
additional->passwd = vals[i].value;
vals[i].value = NULL;
#ifdef HAVE_LIBOATH
} else if (c_strcasecmp(vals[i].name, "otp") == 0) {
additional->otp_file = vals[i].value;
vals[i].value = NULL;
#endif
} else {
fprintf(stderr, "unknown option '%s'\n", vals[i].name);
exit(1);
@@ -322,8 +327,8 @@ void *plain_get_brackets_string(struct perm_cfg_st *config, const char *str)
free_expanded_brackets_string(vals, vals_size);
}
if (additional->passwd == NULL) {
fprintf(stderr, "plain: no password file specified\n");
if (additional->passwd == NULL && additional->otp_file == NULL) {
fprintf(stderr, "plain: no password or OTP file specified\n");
exit(1);
}