mirror of
https://gitlab.com/openconnect/ocserv.git
synced 2026-02-09 08:16:58 +08:00
plain auth: support OTP authentication using usersfile
That adds a dependency on liboath.
This commit is contained in:
16
configure.ac
16
configure.ac
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
2
doc/sample.otp
Normal file
@@ -0,0 +1,2 @@
|
||||
HOTP/T30 test - 8a2d55707a9084982649dadc04b426a06df19ab2 1 691245 2015-09-25T13:18:42L
|
||||
HOTP tost - acbd18db4cc2f85cedef654fccc4a4d8bd537891
|
||||
@@ -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
|
||||
|
||||
@@ -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.";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user