12216fc83f
The OTP patches add basic support for TOTP and Radius. The 389-ds patch sets KRB5CCNAME in /etc/sysconfig/dirsrv so it can get a usable ccache.
1712 lines
60 KiB
Diff
1712 lines
60 KiB
Diff
From 62d32838e4c8b593a12e39ed282011d3859f7813 Mon Sep 17 00:00:00 2001
|
|
From: Nathaniel McCallum <npmccallum@redhat.com>
|
|
Date: Tue, 16 Apr 2013 16:00:09 -0400
|
|
Subject: [PATCH 6/6] Add OTP support to ipa-pwd-extop
|
|
|
|
During LDAP bind, this now plugin determines if a user is enabled
|
|
for OTP authentication. If so, then the OTP is validated in addition
|
|
to the password. This allows 2FA during user binds.
|
|
|
|
https://fedorahosted.org/freeipa/ticket/3367
|
|
http://freeipa.org/page/V3/OTP
|
|
---
|
|
daemons/configure.ac | 39 +-
|
|
.../ipa-slapi-plugins/ipa-pwd-extop/Makefile.am | 42 ++-
|
|
daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c | 398 +++++++++++++++++++++
|
|
daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c | 130 +++++++
|
|
.../ipa-pwd-extop/ipa_pwd_extop.c | 109 +++++-
|
|
daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h | 36 ++
|
|
daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c | 180 ++++++++++
|
|
daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 307 +++++++++++++++-
|
|
daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c | 82 +++++
|
|
daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c | 103 ++++++
|
|
10 files changed, 1368 insertions(+), 58 deletions(-)
|
|
create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c
|
|
create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c
|
|
create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c
|
|
create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c
|
|
|
|
diff --git a/daemons/configure.ac b/daemons/configure.ac
|
|
index 371c28d0493ebe88bc45d3b83def4b8d59461013..21d4e7a77c98e3dc7c630724b1124f1c213d0e6f 100644
|
|
--- a/daemons/configure.ac
|
|
+++ b/daemons/configure.ac
|
|
@@ -22,37 +22,10 @@ AM_CONDITIONAL([HAVE_GCC], [test "$ac_cv_prog_gcc" = yes])
|
|
AC_SUBST(VERSION)
|
|
|
|
dnl ---------------------------------------------------------------------------
|
|
-dnl - Check for NSPR
|
|
+dnl - Check for NSPR/NSS
|
|
dnl ---------------------------------------------------------------------------
|
|
-AC_CHECK_HEADER(nspr4/nspr.h)
|
|
-AC_CHECK_HEADER(nspr/nspr.h)
|
|
-if test "x$ac_cv_header_nspr4_nspr_h" = "xno" && test "x$ac_cv_header_nspr_nspr_h" = "xno" ; then
|
|
- AC_MSG_ERROR([Required NSPR header not available (nspr-devel)])
|
|
-fi
|
|
-if test "x$ac_cv_header_nspr4_nspr_h" = "xyes" ; then
|
|
- NSPR4="-I/usr/include/nspr4"
|
|
-fi
|
|
-if test "x$ac_cv_header_nspr_nspr_h" = "xyes" ; then
|
|
- NSPR4="-I/usr/include/nspr"
|
|
-fi
|
|
-
|
|
-dnl ---------------------------------------------------------------------------
|
|
-dnl - Check for NSS
|
|
-dnl ---------------------------------------------------------------------------
|
|
-SAVE_CPPFLAGS=$CPPFLAGS
|
|
-CPPFLAGS=$NSPR4
|
|
-AC_CHECK_HEADER(nss3/nss.h)
|
|
-AC_CHECK_HEADER(nss/nss.h)
|
|
-CPPFLAGS=$SAVE_CPPFLAGS
|
|
-if test "x$ac_cv_header_nss3_nss_h" = "xno" && test "x$ac_cv_header_nss_nss_h" = "xno" ; then
|
|
- AC_MSG_ERROR([Required NSS header not available (nss-devel)])
|
|
-fi
|
|
-if test "x$ac_cv_header_nss3_nss_h" = "xyes" ; then
|
|
- NSS3="-I/usr/include/nss3"
|
|
-fi
|
|
-if test "x$ac_cv_header_nss_nss_h" = "xyes" ; then
|
|
- NSS3="-I/usr/include/nss"
|
|
-fi
|
|
+PKG_CHECK_MODULES([NSPR], [nspr], [], [AC_MSG_ERROR([libnspr not found])])
|
|
+PKG_CHECK_MODULES([NSS], [nss], [], [AC_MSG_ERROR([libnss not found])])
|
|
|
|
dnl ---------------------------------------------------------------------------
|
|
dnl - Check for DS slapi plugin
|
|
@@ -60,7 +33,7 @@ dnl ---------------------------------------------------------------------------
|
|
|
|
# Need to hack CPPFLAGS to be able to correctly detetct slapi-plugin.h
|
|
SAVE_CPPFLAGS=$CPPFLAGS
|
|
-CPPFLAGS=$NSPR4
|
|
+CPPFLAGS=$NSPR_CFLAGS
|
|
AC_CHECK_HEADER(dirsrv/slapi-plugin.h)
|
|
if test "x$ac_cv_header_dirsrv_slapi-plugin_h" = "xno" ; then
|
|
AC_MSG_ERROR([Required 389-ds header not available (389-ds-base-devel)])
|
|
@@ -96,7 +69,7 @@ dnl - Check for Mozilla LDAP and OpenLDAP SDK
|
|
dnl ---------------------------------------------------------------------------
|
|
|
|
SAVE_CPPFLAGS=$CPPFLAGS
|
|
-CPPFLAGS="$NSPR4 $NSS3"
|
|
+CPPFLAGS="$NSPR_CFLAGS $NSS_CFLAGS"
|
|
AC_CHECK_HEADER(svrcore.h)
|
|
AC_CHECK_HEADER(svrcore/svrcore.h)
|
|
if test "x$ac_cv_header_svrcore_h" = "xno" && test "x$ac_cv_header_svrcore_svrcore_h" = "xno" ; then
|
|
@@ -144,7 +117,7 @@ AC_ARG_WITH([openldap],
|
|
[compile plugins with openldap instead of mozldap])],
|
|
[], [])
|
|
|
|
-LDAP_CFLAGS="${OPENLDAP_CFLAGS} $NSPR4 $NSS3 -DUSE_OPENLDAP"
|
|
+LDAP_CFLAGS="${OPENLDAP_CFLAGS} $NSPR_CFLAGS $NSS_CFLAGS -DUSE_OPENLDAP"
|
|
LDAP_LIBS="${OPENLDAP_LIBS}"
|
|
AC_DEFINE_UNQUOTED(WITH_OPENLDAP, 1, [Use OpenLDAP libraries])
|
|
|
|
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
|
|
index 90f940fd3ad43e637743c8410023a6675792dea6..b53b2e1e445ccc9e756aa1ecb2656f19980cd001 100644
|
|
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
|
|
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
|
|
@@ -1,7 +1,8 @@
|
|
NULL =
|
|
|
|
-PLUGIN_COMMON_DIR=../common
|
|
-KRB5_UTIL_DIR= ../../../util
|
|
+MAINTAINERCLEANFILES = *~ Makefile.in
|
|
+PLUGIN_COMMON_DIR = ../common
|
|
+KRB5_UTIL_DIR = ../../../util
|
|
KRB5_UTIL_SRCS = $(KRB5_UTIL_DIR)/ipa_krb5.c \
|
|
$(KRB5_UTIL_DIR)/ipa_pwd.c \
|
|
$(KRB5_UTIL_DIR)/ipa_pwd_ntlm.c
|
|
@@ -23,13 +24,30 @@ AM_CPPFLAGS = \
|
|
$(SSL_CFLAGS) \
|
|
$(WARN_CFLAGS) \
|
|
$(NULL)
|
|
+
|
|
+AM_LDFLAGS = \
|
|
+ $(KRB5_LIBS) \
|
|
+ $(SSL_LIBS) \
|
|
+ $(LDAP_LIBS) \
|
|
+ $(NSPR_LIBS) \
|
|
+ $(NSS_LIBS) \
|
|
+ -avoid-version \
|
|
+ -export-symbols-regex ^ipapwd_init$
|
|
|
|
-plugindir = $(libdir)/dirsrv/plugins
|
|
-plugin_LTLIBRARIES = \
|
|
- libipa_pwd_extop.la \
|
|
- $(NULL)
|
|
+# OTP Convenience Library and Tests
|
|
+noinst_LTLIBRARIES = libotp.la
|
|
+libotp_la_SOURCES = otp.c
|
|
+check_PROGRAMS = t_hotp t_totp
|
|
+t_hotp_LDADD = libotp.la
|
|
+t_totp_LDADD = libotp.la
|
|
+TESTS = $(check_PROGRAMS)
|
|
|
|
+# Plugin Binary
|
|
+plugindir = $(libdir)/dirsrv/plugins
|
|
+plugin_LTLIBRARIES = libipa_pwd_extop.la
|
|
+libipa_pwd_extop_la_LIBADD = libotp.la
|
|
libipa_pwd_extop_la_SOURCES = \
|
|
+ auth.c \
|
|
common.c \
|
|
encoding.c \
|
|
prepost.c \
|
|
@@ -37,14 +55,6 @@ libipa_pwd_extop_la_SOURCES = \
|
|
$(KRB5_UTIL_SRCS) \
|
|
$(NULL)
|
|
|
|
-libipa_pwd_extop_la_LDFLAGS = -avoid-version
|
|
-
|
|
-libipa_pwd_extop_la_LIBADD = \
|
|
- $(KRB5_LIBS) \
|
|
- $(SSL_LIBS) \
|
|
- $(LDAP_LIBS) \
|
|
- $(NULL)
|
|
-
|
|
appdir = $(IPA_DATA_DIR)
|
|
app_DATA = \
|
|
pwd-extop-conf.ldif \
|
|
@@ -55,6 +65,4 @@ EXTRA_DIST = \
|
|
$(app_DATA) \
|
|
$(NULL)
|
|
|
|
-MAINTAINERCLEANFILES = \
|
|
- *~ \
|
|
- Makefile.in
|
|
+
|
|
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..ae47bab33cc924f9feb3db05b6da5bc094c21914
|
|
--- /dev/null
|
|
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c
|
|
@@ -0,0 +1,398 @@
|
|
+/** BEGIN COPYRIGHT BLOCK
|
|
+ * This Program 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; version 3 of the License.
|
|
+ *
|
|
+ * 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., 59 Temple
|
|
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
+ *
|
|
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
|
|
+ * right to link the code of this Program with code not covered under the GNU
|
|
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
|
|
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
|
|
+ * permitted under this exception must only link to the code of this Program
|
|
+ * through those well defined interfaces identified in the file named EXCEPTION
|
|
+ * found in the source code files (the "Approved Interfaces"). The files of
|
|
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
|
|
+ * the Approved Interfaces without causing the resulting work to be covered by
|
|
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
|
|
+ * additions to the list of Approved Interfaces. You must obey the GNU General
|
|
+ * Public License in all respects for all of the Program code and other code used
|
|
+ * in conjunction with the Program except the Non-GPL Code covered by this
|
|
+ * exception. If you modify this file, you may extend this exception to your
|
|
+ * version of the file, but you are not obligated to do so. If you do not wish to
|
|
+ * provide this exception without modification, you must delete this exception
|
|
+ * statement from your version and license this file solely under the GPL without
|
|
+ * exception.
|
|
+ *
|
|
+ *
|
|
+ * Copyright (C) 2013 Red Hat, Inc.
|
|
+ * All rights reserved.
|
|
+ * END COPYRIGHT BLOCK **/
|
|
+
|
|
+#include "ipapwd.h"
|
|
+
|
|
+#define IPA_OTP_TOKEN_TOTP_OC "ipaTokenTOTP"
|
|
+#define IPA_OTP_DEFAULT_TOKEN_ALGORITHM "sha1"
|
|
+#define IPA_OTP_DEFAULT_TOKEN_OFFSET 0
|
|
+#define IPA_OTP_DEFAULT_TOKEN_STEP 30
|
|
+
|
|
+/*
|
|
+ * From otp.c
|
|
+ */
|
|
+bool ipapwd_hotp(const uint8_t *key, size_t len, const char *algo, int digits,
|
|
+ uint64_t counter, uint32_t *out);
|
|
+
|
|
+bool ipapwd_totp(const uint8_t *key, size_t len, const char *algo, int digits,
|
|
+ time_t time, int offset, unsigned int step, uint32_t *out);
|
|
+
|
|
+/* From ipa_pwd_extop.c */
|
|
+extern void *ipapwd_plugin_id;
|
|
+
|
|
+/* Data types. */
|
|
+struct token {
|
|
+ struct {
|
|
+ uint8_t *data;
|
|
+ size_t len;
|
|
+ } key;
|
|
+ char *algo;
|
|
+ int len;
|
|
+ union {
|
|
+ struct {
|
|
+ uint64_t counter;
|
|
+ } hotp;
|
|
+ struct {
|
|
+ unsigned int step;
|
|
+ int offset;
|
|
+ } totp;
|
|
+ };
|
|
+ bool (*auth)(const struct token *token, uint32_t otp);
|
|
+};
|
|
+
|
|
+struct credentials {
|
|
+ struct token token;
|
|
+ Slapi_Value *ltp;
|
|
+ uint32_t otp;
|
|
+};
|
|
+
|
|
+static const char *valid_algos[] = { "sha1", "sha256", "sha384",
|
|
+ "sha512", NULL };
|
|
+
|
|
+static inline bool is_algo_valid(const char *algo)
|
|
+{
|
|
+ int i, ret;
|
|
+
|
|
+ for (i = 0; valid_algos[i]; i++) {
|
|
+ ret = strcasecmp(algo, valid_algos[i]);
|
|
+ if (ret == 0)
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static const struct berval *entry_attr_get_berval(const Slapi_Entry* e,
|
|
+ const char *type)
|
|
+{
|
|
+ Slapi_Attr* attr = NULL;
|
|
+ Slapi_Value *v;
|
|
+ int ret;
|
|
+
|
|
+ ret = slapi_entry_attr_find(e, type, &attr);
|
|
+ if (ret != 0 || attr == NULL)
|
|
+ return NULL;
|
|
+
|
|
+ ret = slapi_attr_first_value(attr, &v);
|
|
+ if (ret < 0)
|
|
+ return NULL;
|
|
+
|
|
+ return slapi_value_get_berval(v);
|
|
+}
|
|
+
|
|
+/* Authenticate a totp token. Return zero on success. */
|
|
+static bool auth_totp(const struct token *token, uint32_t otp)
|
|
+{
|
|
+ time_t times[5];
|
|
+ uint32_t val;
|
|
+ int i;
|
|
+
|
|
+ /* Get the token value for now and two steps in either direction. */
|
|
+ times[0] = time(NULL);
|
|
+ times[1] = times[0] + token->totp.step * 1;
|
|
+ times[2] = times[0] - token->totp.step * 1;
|
|
+ times[3] = times[0] + token->totp.step * 2;
|
|
+ times[4] = times[0] - token->totp.step * 2;
|
|
+ if (times[0] == -1)
|
|
+ return false;
|
|
+
|
|
+ /* Check all the times for a match. */
|
|
+ for (i = 0; i < sizeof(times) / sizeof(times[0]); i++) {
|
|
+ if (!ipapwd_totp(token->key.data, token->key.len, token->algo,
|
|
+ token->len, times[i], token->totp.offset,
|
|
+ token->totp.step, &val)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (val == otp) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static void token_free_contents(struct token *token)
|
|
+{
|
|
+ if (token == NULL)
|
|
+ return;
|
|
+
|
|
+ slapi_ch_free_string(&token->algo);
|
|
+ slapi_ch_free((void **) &token->key.data);
|
|
+}
|
|
+
|
|
+/* Decode an OTP token entry. Return zero on success. */
|
|
+static bool token_decode(Slapi_Entry *te, struct token *token)
|
|
+{
|
|
+ const struct berval *tmp;
|
|
+
|
|
+ /* Get key. */
|
|
+ tmp = entry_attr_get_berval(te, IPA_OTP_TOKEN_KEY_TYPE);
|
|
+ if (tmp == NULL) {
|
|
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
|
|
+ "token_decode: key not set for token \"%s\".\n",
|
|
+ slapi_entry_get_ndn(te));
|
|
+ return false;
|
|
+ }
|
|
+ token->key.len = tmp->bv_len;
|
|
+ token->key.data = (void *) slapi_ch_malloc(token->key.len);
|
|
+ memcpy(token->key.data, tmp->bv_val, token->key.len);
|
|
+
|
|
+ /* Get length. */
|
|
+ token->len = slapi_entry_attr_get_int(te, IPA_OTP_TOKEN_LENGTH_TYPE);
|
|
+ if (token->len < 6 || token->len > 10) {
|
|
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
|
|
+ "token_decode: %s is not defined or invalid "
|
|
+ "for token \"%s\".\n", IPA_OTP_TOKEN_LENGTH_TYPE,
|
|
+ slapi_entry_get_ndn(te));
|
|
+ token_free_contents(token);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* Get algorithm. */
|
|
+ token->algo = slapi_entry_attr_get_charptr(te,
|
|
+ IPA_OTP_TOKEN_ALGORITHM_TYPE);
|
|
+ if (token->algo == NULL)
|
|
+ token->algo = slapi_ch_strdup(IPA_OTP_DEFAULT_TOKEN_ALGORITHM);
|
|
+ if (!is_algo_valid(token->algo)) {
|
|
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
|
|
+ "token_decode: invalid token algorithm "
|
|
+ "specified for token \"%s\".\n",
|
|
+ slapi_entry_get_ndn(te));
|
|
+ token_free_contents(token);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* Currently, we only support TOTP. */
|
|
+ token->auth = auth_totp;
|
|
+
|
|
+ /* Get offset. */
|
|
+ token->totp.offset = slapi_entry_attr_get_int(te,
|
|
+ IPA_OTP_TOKEN_OFFSET_TYPE);
|
|
+ if (token->totp.offset == 0)
|
|
+ token->totp.offset = IPA_OTP_DEFAULT_TOKEN_OFFSET;
|
|
+
|
|
+ /* Get step. */
|
|
+ token->totp.step = slapi_entry_attr_get_uint(te, IPA_OTP_TOKEN_STEP_TYPE);
|
|
+ if (token->totp.step == 0)
|
|
+ token->totp.step = IPA_OTP_DEFAULT_TOKEN_STEP;
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static void credentials_free_contents(struct credentials *credentials)
|
|
+{
|
|
+ if (!credentials)
|
|
+ return;
|
|
+
|
|
+ token_free_contents(&credentials->token);
|
|
+ slapi_value_free(&credentials->ltp);
|
|
+}
|
|
+
|
|
+/* Parse credentials and token entry. Return zero on success. */
|
|
+static bool credentials_parse(Slapi_Entry *te, struct berval *creds,
|
|
+ struct credentials *credentials)
|
|
+{
|
|
+ char *tmp;
|
|
+ int len;
|
|
+
|
|
+ if (!token_decode(te, &credentials->token))
|
|
+ return false;
|
|
+
|
|
+ /* Is the credential too short? If so, error. */
|
|
+ if (credentials->token.len >= creds->bv_len) {
|
|
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
|
|
+ "credentials_parse: supplied credential is less "
|
|
+ "than or equal to %s for token \"%s\".\n",
|
|
+ IPA_OTP_TOKEN_LENGTH_TYPE, slapi_entry_get_ndn(te));
|
|
+ token_free_contents(&credentials->token);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* Extract the password from the supplied credential. We hand the
|
|
+ * memory off to a Slapi_Value, so we don't want to directly free the
|
|
+ * string. */
|
|
+ len = creds->bv_len - credentials->token.len;
|
|
+ tmp = slapi_ch_calloc(len + 1, sizeof(char));
|
|
+ strncpy(tmp, creds->bv_val, len);
|
|
+ credentials->ltp = slapi_value_new_string_passin(tmp);
|
|
+
|
|
+ /* Extract the token value as a (minimum) 32-bit unsigned integer. */
|
|
+ tmp = slapi_ch_calloc(credentials->token.len + 1, sizeof(char));
|
|
+ strncpy(tmp, creds->bv_val + len, credentials->token.len);
|
|
+ credentials->otp = strtoul(tmp, NULL, 10);
|
|
+ slapi_ch_free_string(&tmp);
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Attempts to perform OTP authentication for the passed in bind entry using
|
|
+ * the passed in credentials.
|
|
+ */
|
|
+bool ipapwd_do_otp_auth(Slapi_Entry *bind_entry, struct berval *creds)
|
|
+{
|
|
+ Slapi_PBlock *search_pb = NULL;
|
|
+ Slapi_Value **pwd_vals = NULL;
|
|
+ Slapi_Attr *pwd_attr = NULL;
|
|
+ Slapi_Entry **tokens = NULL;
|
|
+ Slapi_DN *base_sdn = NULL;
|
|
+ Slapi_Backend *be = NULL;
|
|
+ char *user_dn = NULL;
|
|
+ char *filter = NULL;
|
|
+ int pwd_numvals = 0;
|
|
+ bool ret = false;
|
|
+ int result = 0;
|
|
+ int hint = 0;
|
|
+ int i = 0;
|
|
+
|
|
+ search_pb = slapi_pblock_new();
|
|
+
|
|
+ /* Fetch the user DN. */
|
|
+ user_dn = slapi_entry_get_ndn(bind_entry);
|
|
+ if (user_dn == NULL) {
|
|
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
|
|
+ "ipapwd_do_otp_auth: error retrieving bind DN.\n");
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Search for TOTP tokens associated with this user. We search for
|
|
+ * tokens who list this user as the owner in the same backend where
|
|
+ * the user entry is located. */
|
|
+ filter = slapi_ch_smprintf("(&(%s=%s)(%s=%s))", SLAPI_ATTR_OBJECTCLASS,
|
|
+ IPA_OTP_TOKEN_TOTP_OC, IPA_OTP_TOKEN_OWNER_TYPE,
|
|
+ user_dn);
|
|
+
|
|
+ be = slapi_be_select(slapi_entry_get_sdn(bind_entry));
|
|
+ if (be != NULL) {
|
|
+ base_sdn = (Slapi_DN *) slapi_be_getsuffix(be, 0);
|
|
+ }
|
|
+ if (base_sdn == NULL) {
|
|
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
|
|
+ "ipapwd_do_otp_auth: error determining the search "
|
|
+ "base for user \"%s\".\n",
|
|
+ user_dn);
|
|
+ }
|
|
+
|
|
+ slapi_search_internal_set_pb(search_pb, slapi_sdn_get_ndn(base_sdn),
|
|
+ LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL,
|
|
+ NULL, ipapwd_plugin_id, 0);
|
|
+
|
|
+ slapi_search_internal_pb(search_pb);
|
|
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
|
|
+
|
|
+ if (LDAP_SUCCESS != result) {
|
|
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
|
|
+ "ipapwd_do_otp_auth: error searching for tokens "
|
|
+ "associated with user \"%s\" (err=%d).\n",
|
|
+ user_dn, result);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &tokens);
|
|
+
|
|
+ if (tokens == NULL) {
|
|
+ /* This user has no associated tokens, so just bail out. */
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Fetch the userPassword values so we can perform the password checks
|
|
+ * when processing tokens below. */
|
|
+ if (slapi_entry_attr_find(bind_entry, SLAPI_USERPWD_ATTR, &pwd_attr) != 0 ||
|
|
+ slapi_attr_get_numvalues(pwd_attr, &pwd_numvals) != 0) {
|
|
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
|
|
+ "ipapwd_do_otp_auth: no passwords are set for user "
|
|
+ "\"%s\".\n", user_dn);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* We need to create a Slapi_Value array of the present password values
|
|
+ * for the compare function. There's no nicer way of doing this. */
|
|
+ pwd_vals = (Slapi_Value **) slapi_ch_calloc(pwd_numvals,
|
|
+ sizeof(Slapi_Value *));
|
|
+
|
|
+ for (hint = slapi_attr_first_value(pwd_attr, &pwd_vals[i]); hint != -1;
|
|
+ hint = slapi_attr_next_value(pwd_attr, hint, &pwd_vals[i])) {
|
|
+ ++i;
|
|
+ }
|
|
+
|
|
+ /* Loop through each token and attempt to authenticate. */
|
|
+ for (i = 0; tokens && tokens[i]; i++) {
|
|
+ struct credentials credentials;
|
|
+
|
|
+ /* Parse the token entry and the credentials. */
|
|
+ if (!credentials_parse(tokens[i], creds, &credentials))
|
|
+ continue;
|
|
+
|
|
+ /* Check if the password portion of the credential is correct. */
|
|
+ i = slapi_pw_find_sv(pwd_vals, credentials.ltp);
|
|
+ if (i != 0) {
|
|
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
|
|
+ "ipapwd_do_otp_auth: password check failed when "
|
|
+ "processing token \"%s\" for user \"%s\".\n",
|
|
+ slapi_entry_get_ndn(tokens[i]), user_dn);
|
|
+ credentials_free_contents(&credentials);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* Attempt to perform OTP authentication for this token. */
|
|
+ if (!credentials.token.auth(&credentials.token, credentials.otp)) {
|
|
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
|
|
+ "ipapwd_do_otp_auth: OTP auth failed when "
|
|
+ "processing token \"%s\" for user \"%s\".\n",
|
|
+ slapi_entry_get_ndn(tokens[i]), user_dn);
|
|
+ credentials_free_contents(&credentials);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* Authentication successful! */
|
|
+ credentials_free_contents(&credentials);
|
|
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
|
|
+ "ipapwd_do_otp_auth: successfully "
|
|
+ "authenticated user \"%s\" using token "
|
|
+ "\"%s\".\n",
|
|
+ user_dn, slapi_entry_get_ndn(tokens[i]));
|
|
+ ret = true;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+done:
|
|
+ slapi_ch_free_string(&filter);
|
|
+ slapi_free_search_results_internal(search_pb);
|
|
+ slapi_pblock_destroy(search_pb);
|
|
+ return ret;
|
|
+}
|
|
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
|
index bb1d96ade8c22bf60138a78957e409cf1b0de055..a54e91d87b5e700775b05dd2859c95abd739b626 100644
|
|
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
|
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
|
@@ -40,6 +40,9 @@
|
|
#include "ipapwd.h"
|
|
#include "util.h"
|
|
|
|
+/* Attribute defines */
|
|
+#define IPA_OTP_USER_AUTH_TYPE "ipaUserAuthType"
|
|
+
|
|
/* Type of connection for this operation;*/
|
|
#define LDAP_EXTOP_PASSMOD_CONN_SECURE
|
|
|
|
@@ -67,6 +70,133 @@ static const char *ipapwd_def_encsalts[] = {
|
|
NULL
|
|
};
|
|
|
|
+static PRInt32 g_allowed_auth_types = 0;
|
|
+
|
|
+/*
|
|
+ * Checks if an authentication type is allowed. A NULL terminated
|
|
+ * list of allowed auth type values is passed in along with the flag
|
|
+ * for the auth type you are inquiring about. If auth_type_list is
|
|
+ * NULL, the global config will be consulted.
|
|
+ */
|
|
+bool ipapwd_is_auth_type_allowed(char **auth_type_list, int auth_type)
|
|
+{
|
|
+ char *auth_type_value = NULL;
|
|
+ int i = 0;
|
|
+
|
|
+ /* Get the string value for the authentication type we are checking for. */
|
|
+ switch (auth_type) {
|
|
+ case IPA_OTP_AUTH_TYPE_OTP:
|
|
+ auth_type_value = IPA_OTP_AUTH_TYPE_VALUE_OTP;
|
|
+ break;
|
|
+ case IPA_OTP_AUTH_TYPE_PASSWORD:
|
|
+ auth_type_value = IPA_OTP_AUTH_TYPE_VALUE_PASSWORD;
|
|
+ break;
|
|
+ case IPA_OTP_AUTH_TYPE_PKINIT:
|
|
+ auth_type_value = IPA_OTP_AUTH_TYPE_VALUE_PKINIT;
|
|
+ break;
|
|
+ default: /* Unknown type.*/
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (auth_type_list == NULL) {
|
|
+ /* Check if the requested authentication type is in the global list. */
|
|
+ PRInt32 auth_type_flags;
|
|
+
|
|
+ /* Do an atomic read of the allowed auth types bit field. */
|
|
+ auth_type_flags = PR_ATOMIC_ADD(&g_allowed_auth_types, 0);
|
|
+
|
|
+ /* Check if the flag for the desired auth type is set. */
|
|
+ return auth_type_flags & auth_type;
|
|
+ }
|
|
+
|
|
+ /* Check if the requested authentication type is in the user list. */
|
|
+ for (i = 0; auth_type_list[i]; i++) {
|
|
+ if (strcasecmp(auth_type_list[i], auth_type_value) == 0) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Parses and validates an OTP config entry. If apply is non-zero, then
|
|
+ * we will load and start using the new config. You can simply
|
|
+ * validate config without making any changes by setting apply to false.
|
|
+ */
|
|
+bool ipapwd_parse_otp_config_entry(Slapi_Entry * e, bool apply)
|
|
+{
|
|
+ PRInt32 allowed_auth_types = 0;
|
|
+ PRInt32 default_auth_types = 0;
|
|
+ char **auth_types = NULL;
|
|
+
|
|
+ /* If no auth types are set, we default to only allowing password
|
|
+ * authentication. Other authentication types can be allowed at the
|
|
+ * user level. */
|
|
+ default_auth_types |= IPA_OTP_AUTH_TYPE_PASSWORD;
|
|
+
|
|
+ if (e == NULL) {
|
|
+ /* There is no config entry, so just set the defaults. */
|
|
+ allowed_auth_types = default_auth_types;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Parse and validate the config entry. We currently tolerate invalid
|
|
+ * config settings, so there is no real validation performed. We will
|
|
+ * likely want to reject invalid config as we expand the plug-in
|
|
+ * functionality, so the validation logic is here for us to use later. */
|
|
+
|
|
+ /* Fetch the auth type values from the config entry. */
|
|
+ auth_types = slapi_entry_attr_get_charray(e, IPA_OTP_USER_AUTH_TYPE);
|
|
+ if (auth_types == NULL) {
|
|
+ /* No allowed auth types are specified, so set the defaults. */
|
|
+ allowed_auth_types = default_auth_types;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Check each type to see if it is set. */
|
|
+ if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_DISABLED)) {
|
|
+ allowed_auth_types |= IPA_OTP_AUTH_TYPE_DISABLED;
|
|
+ }
|
|
+
|
|
+ if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_PASSWORD)) {
|
|
+ allowed_auth_types |= IPA_OTP_AUTH_TYPE_PASSWORD;
|
|
+ }
|
|
+
|
|
+ if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_OTP)) {
|
|
+ allowed_auth_types |= IPA_OTP_AUTH_TYPE_OTP;
|
|
+ }
|
|
+
|
|
+ if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_PKINIT)) {
|
|
+ allowed_auth_types |= IPA_OTP_AUTH_TYPE_PKINIT;
|
|
+ }
|
|
+
|
|
+ if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_RADIUS)) {
|
|
+ allowed_auth_types |= IPA_OTP_AUTH_TYPE_RADIUS;
|
|
+ }
|
|
+
|
|
+ slapi_ch_array_free(auth_types);
|
|
+
|
|
+done:
|
|
+ if (apply) {
|
|
+ /* Atomically set the global allowed types. */
|
|
+ PR_ATOMIC_SET(&g_allowed_auth_types, allowed_auth_types);
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool ipapwd_otp_is_disabled(void)
|
|
+{
|
|
+ PRInt32 auth_type_flags;
|
|
+
|
|
+ /* Do an atomic read of the allowed auth types bit field. */
|
|
+ auth_type_flags = PR_ATOMIC_ADD(&g_allowed_auth_types, 0);
|
|
+
|
|
+ /* Check if the disabled bit is set. */
|
|
+ return auth_type_flags & IPA_OTP_AUTH_TYPE_DISABLED;
|
|
+}
|
|
+
|
|
static struct ipapwd_krbcfg *ipapwd_getConfig(void)
|
|
{
|
|
krb5_error_code krberr;
|
|
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
|
|
index b64084e9d503733a797dacfb06471ad580a0c886..88cdcb10f4e13392f9dc81f9e13adf20c81e01f6 100644
|
|
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
|
|
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
|
|
@@ -87,6 +87,30 @@ Slapi_PluginDesc ipapwd_plugin_desc = {
|
|
void *ipapwd_plugin_id;
|
|
static int usetxn = 0;
|
|
|
|
+static Slapi_DN *_ConfigAreaDN = NULL;
|
|
+static Slapi_DN *_PluginDN = NULL;
|
|
+static bool g_plugin_started = false;
|
|
+
|
|
+void *ipapwd_get_plugin_id(void)
|
|
+{
|
|
+ return ipapwd_plugin_id;
|
|
+}
|
|
+
|
|
+Slapi_DN *ipapwd_get_otp_config_area(void)
|
|
+{
|
|
+ return _ConfigAreaDN;
|
|
+}
|
|
+
|
|
+Slapi_DN *ipapwd_get_plugin_sdn(void)
|
|
+{
|
|
+ return _PluginDN;
|
|
+}
|
|
+
|
|
+bool ipapwd_get_plugin_started(void)
|
|
+{
|
|
+ return g_plugin_started;
|
|
+}
|
|
+
|
|
static int filter_keys(struct ipapwd_krbcfg *krbcfg,
|
|
struct ipapwd_keyset *kset)
|
|
{
|
|
@@ -1195,6 +1219,35 @@ Slapi_Filter *ipapwd_string2filter(char *strfilter)
|
|
return ret;
|
|
}
|
|
|
|
+/* Loads the OTP config entry, parses it, and applies it. */
|
|
+static inline bool ipapwd_load_otp_config(void)
|
|
+{
|
|
+ char *config_attrs[] = { IPA_USER_AUTH_TYPE, NULL };
|
|
+ Slapi_Entry *config_entry = NULL;
|
|
+ Slapi_DN *config_sdn = NULL;
|
|
+
|
|
+ /* If we are using an alternate config area, check it for our
|
|
+ * configuration, otherwise we just use our main plug-in config
|
|
+ * entry. */
|
|
+ if ((config_sdn = ipapwd_get_otp_config_area()) == NULL) {
|
|
+ config_sdn = ipapwd_get_plugin_sdn();
|
|
+ }
|
|
+
|
|
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
|
|
+ "Looking for config settings in \"%s\".\n",
|
|
+ config_sdn ? slapi_sdn_get_ndn(config_sdn) : "null");
|
|
+
|
|
+ /* Fetch the config entry. */
|
|
+ slapi_search_internal_get_entry(config_sdn, config_attrs, &config_entry,
|
|
+ ipapwd_plugin_id);
|
|
+
|
|
+ /* Parse and apply the config. */
|
|
+ ipapwd_parse_otp_config_entry(config_entry, true);
|
|
+
|
|
+ slapi_entry_free(config_entry);
|
|
+ return true;
|
|
+}
|
|
+
|
|
/* Init data structs */
|
|
static int ipapwd_start( Slapi_PBlock *pb )
|
|
{
|
|
@@ -1203,8 +1256,37 @@ static int ipapwd_start( Slapi_PBlock *pb )
|
|
char *realm = NULL;
|
|
char *config_dn;
|
|
Slapi_Entry *config_entry = NULL;
|
|
+ Slapi_DN *plugindn = NULL;
|
|
+ char *config_area = NULL;
|
|
int ret;
|
|
|
|
+ /* Check if we're already started */
|
|
+ if (g_plugin_started) {
|
|
+ return LDAP_SUCCESS;
|
|
+ }
|
|
+
|
|
+ /* Get the plug-in target dn from the system and store for future use. */
|
|
+ slapi_pblock_get(pb, SLAPI_TARGET_SDN, &plugindn);
|
|
+ if (plugindn == NULL || slapi_sdn_get_ndn_len(plugindn) == 0) {
|
|
+ LOG_FATAL("No plugin dn?\n");
|
|
+ return LDAP_OPERATIONS_ERROR;
|
|
+ }
|
|
+ _PluginDN = slapi_sdn_dup(plugindn);
|
|
+
|
|
+ /* Set the alternate config area if one is defined. */
|
|
+ slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_AREA, &config_area);
|
|
+ if (config_area != NULL) {
|
|
+ _ConfigAreaDN = slapi_sdn_new_normdn_byval(config_area);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Load the config.
|
|
+ */
|
|
+ if (!ipapwd_load_otp_config()) {
|
|
+ LOG_FATAL("Unable to load plug-in config\n");
|
|
+ return LDAP_OPERATIONS_ERROR;
|
|
+ }
|
|
+
|
|
krberr = krb5_init_context(&krbctx);
|
|
if (krberr) {
|
|
LOG_FATAL("krb5_init_context failed\n");
|
|
@@ -1273,6 +1355,7 @@ static int ipapwd_start( Slapi_PBlock *pb )
|
|
}
|
|
|
|
ret = LDAP_SUCCESS;
|
|
+ g_plugin_started = true;
|
|
|
|
done:
|
|
free(realm);
|
|
@@ -1281,7 +1364,25 @@ done:
|
|
return ret;
|
|
}
|
|
|
|
+/* Clean up any resources allocated at startup. */
|
|
+static int ipapwd_close(Slapi_PBlock * pb)
|
|
+{
|
|
+ if (!g_plugin_started) {
|
|
+ goto done;
|
|
+ }
|
|
|
|
+ g_plugin_started = false;
|
|
+
|
|
+ /* We are not guaranteed that other threads are finished accessing
|
|
+ * PluginDN or ConfigAreaDN, so we don't want to free them. This is
|
|
+ * only a one-time leak at shutdown, so it should be fine.
|
|
+ * slapi_sdn_free(&_PluginDN);
|
|
+ * slapi_sdn_free(&_ConfigAreaDN);
|
|
+ */
|
|
+
|
|
+done:
|
|
+ return 0;
|
|
+}
|
|
|
|
static char *ipapwd_oid_list[] = {
|
|
EXOP_PASSWD_OID,
|
|
@@ -1328,12 +1429,13 @@ int ipapwd_init( Slapi_PBlock *pb )
|
|
* plug-in function that handles the operation identified by
|
|
* OID 1.3.6.1.4.1.4203.1.11.1 . Also specify the version of the server
|
|
* plug-in */
|
|
- ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
|
|
+ ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)ipapwd_start);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipapwd_oid_list);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipapwd_name_list);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)ipapwd_extop);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *)ipapwd_close);
|
|
if (ret) {
|
|
LOG("Failed to set plug-in version, function, and OID.\n" );
|
|
return -1;
|
|
@@ -1361,5 +1463,10 @@ int ipapwd_init( Slapi_PBlock *pb )
|
|
"IPA pwd post ops", NULL,
|
|
ipapwd_plugin_id);
|
|
|
|
+ slapi_register_plugin("internalpostoperation", 1,
|
|
+ "ipapwd_intpost_init", ipapwd_intpost_init,
|
|
+ "IPA pwd internal post ops", NULL,
|
|
+ ipapwd_plugin_id);
|
|
+
|
|
return 0;
|
|
}
|
|
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
|
|
index 372441ddd11e4a0d8f4e4f709590ab5e89e5a7d4..74b63627689da9e519ec15d1e2020fa50ea7f75c 100644
|
|
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
|
|
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
|
|
@@ -76,6 +76,30 @@
|
|
#define IPA_CHANGETYPE_ADMIN 1
|
|
#define IPA_CHANGETYPE_DSMGR 2
|
|
|
|
+/*
|
|
+ * Attribute type defines
|
|
+ */
|
|
+#define IPA_USER_AUTH_TYPE "ipaUserAuthType"
|
|
+#define IPA_OTP_TOKEN_OWNER_TYPE "ipaTokenOwner"
|
|
+#define IPA_OTP_TOKEN_LENGTH_TYPE "ipaTokenOTPDigits"
|
|
+#define IPA_OTP_TOKEN_KEY_TYPE "ipaTokenOTPKey"
|
|
+#define IPA_OTP_TOKEN_ALGORITHM_TYPE "ipaTokenOTPAlgorithm"
|
|
+#define IPA_OTP_TOKEN_OFFSET_TYPE "ipaTokenTOTPClockOffset"
|
|
+#define IPA_OTP_TOKEN_STEP_TYPE "ipaTokenTOTPTimeStep"
|
|
+
|
|
+/* Authentication type defines */
|
|
+#define IPA_OTP_AUTH_TYPE_NONE 0
|
|
+#define IPA_OTP_AUTH_TYPE_DISABLED 1
|
|
+#define IPA_OTP_AUTH_TYPE_PASSWORD 2
|
|
+#define IPA_OTP_AUTH_TYPE_OTP 4
|
|
+#define IPA_OTP_AUTH_TYPE_PKINIT 8
|
|
+#define IPA_OTP_AUTH_TYPE_RADIUS 16
|
|
+#define IPA_OTP_AUTH_TYPE_VALUE_DISABLED "DISABLED"
|
|
+#define IPA_OTP_AUTH_TYPE_VALUE_PASSWORD "PASSWORD"
|
|
+#define IPA_OTP_AUTH_TYPE_VALUE_OTP "OTP"
|
|
+#define IPA_OTP_AUTH_TYPE_VALUE_PKINIT "PKINIT"
|
|
+#define IPA_OTP_AUTH_TYPE_VALUE_RADIUS "RADIUS"
|
|
+
|
|
struct ipapwd_data {
|
|
Slapi_Entry *target;
|
|
char *dn;
|
|
@@ -112,6 +136,9 @@ struct ipapwd_krbcfg {
|
|
bool allow_nt_hash;
|
|
};
|
|
|
|
+bool ipapwd_is_auth_type_allowed(char **auth_type_list, int auth_type);
|
|
+bool ipapwd_parse_otp_config_entry(Slapi_Entry * e, bool apply);
|
|
+bool ipapwd_otp_is_disabled(void);
|
|
int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e,
|
|
int *is_root, int *is_krb, int *is_smb, int *is_ipant,
|
|
char *attr, int access);
|
|
@@ -152,6 +179,15 @@ int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg,
|
|
int ipapwd_ext_init(void);
|
|
int ipapwd_pre_init(Slapi_PBlock *pb);
|
|
int ipapwd_post_init(Slapi_PBlock *pb);
|
|
+int ipapwd_intpost_init(Slapi_PBlock *pb);
|
|
int ipapwd_pre_init_betxn(Slapi_PBlock *pb);
|
|
int ipapwd_post_init_betxn(Slapi_PBlock *pb);
|
|
|
|
+/* from ipa_pwd_extop.c */
|
|
+void *ipapwd_get_plugin_id(void);
|
|
+Slapi_DN *ipapwd_get_otp_config_area(void);
|
|
+Slapi_DN *ipapwd_get_plugin_sdn(void);
|
|
+bool ipapwd_get_plugin_started(void);
|
|
+
|
|
+/* from auth.c */
|
|
+bool ipapwd_do_otp_auth(Slapi_Entry *bind_entry, struct berval *creds);
|
|
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..6c0f8554b5ee3afd8e78333b30f56272048d8c4d
|
|
--- /dev/null
|
|
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c
|
|
@@ -0,0 +1,180 @@
|
|
+/** BEGIN COPYRIGHT BLOCK
|
|
+ * This Program 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; version 3 of the License.
|
|
+ *
|
|
+ * 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., 59 Temple
|
|
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
+ *
|
|
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
|
|
+ * right to link the code of this Program with code not covered under the GNU
|
|
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
|
|
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
|
|
+ * permitted under this exception must only link to the code of this Program
|
|
+ * through those well defined interfaces identified in the file named EXCEPTION
|
|
+ * found in the source code files (the "Approved Interfaces"). The files of
|
|
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
|
|
+ * the Approved Interfaces without causing the resulting work to be covered by
|
|
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
|
|
+ * additions to the list of Approved Interfaces. You must obey the GNU General
|
|
+ * Public License in all respects for all of the Program code and other code used
|
|
+ * in conjunction with the Program except the Non-GPL Code covered by this
|
|
+ * exception. If you modify this file, you may extend this exception to your
|
|
+ * version of the file, but you are not obligated to do so. If you do not wish to
|
|
+ * provide this exception without modification, you must delete this exception
|
|
+ * statement from your version and license this file solely under the GPL without
|
|
+ * exception.
|
|
+ *
|
|
+ *
|
|
+ * Copyright (C) 2013 Red Hat, Inc.
|
|
+ * All rights reserved.
|
|
+ * END COPYRIGHT BLOCK **/
|
|
+
|
|
+/*
|
|
+ * This file contains an implementation of HOTP (RFC 4226) and TOTP (RFC 6238).
|
|
+ * For details of how these algorithms work, please see the relevant RFCs.
|
|
+ */
|
|
+
|
|
+#include <stdbool.h>
|
|
+#include <time.h>
|
|
+
|
|
+#include <nss.h>
|
|
+#include <pk11pub.h>
|
|
+#include <hasht.h>
|
|
+#include <prnetdb.h>
|
|
+
|
|
+struct digest_buffer {
|
|
+ uint8_t buf[SHA512_LENGTH];
|
|
+ unsigned int len;
|
|
+};
|
|
+
|
|
+static const struct {
|
|
+ const char *algo;
|
|
+ CK_MECHANISM_TYPE mech;
|
|
+} algo2mech[] = {
|
|
+ { "sha1", CKM_SHA_1_HMAC },
|
|
+ { "sha256", CKM_SHA256_HMAC },
|
|
+ { "sha384", CKM_SHA384_HMAC },
|
|
+ { "sha512", CKM_SHA512_HMAC },
|
|
+ { }
|
|
+};
|
|
+
|
|
+/*
|
|
+ * This code is mostly cargo-cult taken from here:
|
|
+ * http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn5.html
|
|
+ *
|
|
+ * It should implement HMAC with the given mechanism (SHA: 1, 256, 384, 512).
|
|
+ */
|
|
+static bool hmac(SECItem *key, CK_MECHANISM_TYPE mech, const SECItem *in,
|
|
+ struct digest_buffer *out)
|
|
+{
|
|
+ SECItem param = { siBuffer, NULL, 0 };
|
|
+ PK11SlotInfo *slot = NULL;
|
|
+ PK11SymKey *symkey = NULL;
|
|
+ PK11Context *ctx = NULL;
|
|
+ bool ret = false;
|
|
+ SECStatus s;
|
|
+
|
|
+ slot = PK11_GetBestSlot(mech, NULL);
|
|
+ if (slot == NULL) {
|
|
+ slot = PK11_GetInternalKeySlot();
|
|
+ if (slot == NULL) {
|
|
+ goto done;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ symkey = PK11_ImportSymKey(slot, mech, PK11_OriginUnwrap,
|
|
+ CKA_SIGN, key, NULL);
|
|
+ if (symkey == NULL)
|
|
+ goto done;
|
|
+
|
|
+ ctx = PK11_CreateContextBySymKey(mech, CKA_SIGN, symkey, ¶m);
|
|
+ if (ctx == NULL)
|
|
+ goto done;
|
|
+
|
|
+ s = PK11_DigestBegin(ctx);
|
|
+ if (s != SECSuccess)
|
|
+ goto done;
|
|
+
|
|
+ s = PK11_DigestOp(ctx, in->data, in->len);
|
|
+ if (s != SECSuccess)
|
|
+ goto done;
|
|
+
|
|
+ s = PK11_DigestFinal(ctx, out->buf, &out->len, sizeof(out->buf));
|
|
+ if (s != SECSuccess)
|
|
+ goto done;
|
|
+
|
|
+ ret = true;
|
|
+
|
|
+done:
|
|
+ if (ctx != NULL)
|
|
+ PK11_DestroyContext(ctx, PR_TRUE);
|
|
+ if (symkey != NULL)
|
|
+ PK11_FreeSymKey(symkey);
|
|
+ if (slot != NULL)
|
|
+ PK11_FreeSlot(slot);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * An implementation of HOTP (RFC 4226).
|
|
+ */
|
|
+bool ipapwd_hotp(const uint8_t *key, size_t len, const char *algo, int digits,
|
|
+ uint64_t counter, uint32_t *out)
|
|
+{
|
|
+ const SECItem cntr = { siBuffer, (uint8_t *) &counter, sizeof(counter) };
|
|
+ SECItem keyitm = { siBuffer, (uint8_t *) key, len };
|
|
+ CK_MECHANISM_TYPE mech = CKM_SHA_1_HMAC;
|
|
+ PRUint64 offset, binary, div;
|
|
+ struct digest_buffer digest;
|
|
+ int i;
|
|
+
|
|
+ /* Convert counter to network byte order. */
|
|
+ counter = PR_htonll(counter);
|
|
+
|
|
+ /* Find the mech. */
|
|
+ for (i = 0; algo2mech[i].algo; i++) {
|
|
+ if (strcasecmp(algo2mech[i].algo, algo) == 0) {
|
|
+ mech = algo2mech[i].mech;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Create the digits divisor. */
|
|
+ for (div = 1; digits > 0; digits--) {
|
|
+ div *= 10;
|
|
+ }
|
|
+
|
|
+ /* Do the digest. */
|
|
+ if (!hmac(&keyitm, mech, &cntr, &digest)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* Truncate. */
|
|
+ offset = digest.buf[digest.len - 1] & 0xf;
|
|
+ binary = (digest.buf[offset + 0] & 0x7f) << 0x18;
|
|
+ binary |= (digest.buf[offset + 1] & 0xff) << 0x10;
|
|
+ binary |= (digest.buf[offset + 2] & 0xff) << 0x08;
|
|
+ binary |= (digest.buf[offset + 3] & 0xff) << 0x00;
|
|
+ binary = binary % div;
|
|
+
|
|
+ *out = binary;
|
|
+ return true;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * An implementation of TOTP (RFC 6238).
|
|
+ */
|
|
+bool ipapwd_totp(const uint8_t *key, size_t len, const char *algo, int digits,
|
|
+ time_t time, int offset, unsigned int step, uint32_t *out)
|
|
+{
|
|
+ if (step == 0)
|
|
+ return false;
|
|
+
|
|
+ return ipapwd_hotp(key, len, algo, digits, (time - offset) / step, out);
|
|
+}
|
|
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
|
|
index 0318cecdcc37690838ffc778817cd60c9a8376a0..8a222650cbd7348f419c8b697fa9b9784a66eb22 100644
|
|
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
|
|
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
|
|
@@ -67,6 +67,9 @@
|
|
#define IPAPWD_OP_ADD 1
|
|
#define IPAPWD_OP_MOD 2
|
|
|
|
+#define IPAPWD_OP_NOT_HANDLED 0
|
|
+#define IPAPWD_OP_HANDLED 1
|
|
+
|
|
extern Slapi_PluginDesc ipapwd_plugin_desc;
|
|
extern void *ipapwd_plugin_id;
|
|
extern const char *ipa_realm_tree;
|
|
@@ -975,7 +978,77 @@ static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods,
|
|
return ret;
|
|
}
|
|
|
|
-static int ipapwd_post_op(Slapi_PBlock *pb)
|
|
+/*
|
|
+ * Check if we want to process this operation. We need to be
|
|
+ * sure that the operation succeeded.
|
|
+ */
|
|
+static bool ipapwd_otp_oktodo(Slapi_PBlock *pb)
|
|
+{
|
|
+ bool ok = false;
|
|
+ int oprc = 0;
|
|
+ int ret = 1;
|
|
+
|
|
+ ret = slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc);
|
|
+ if (ret != 0) {
|
|
+ LOG_FATAL("Could not get parameters\n");
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* This plugin should only execute if the operation succeeded. */
|
|
+ ok = oprc == 0;
|
|
+
|
|
+done:
|
|
+ return ok;
|
|
+}
|
|
+
|
|
+static bool ipapwd_dn_is_otp_config(Slapi_DN *sdn)
|
|
+{
|
|
+ bool ret = false;
|
|
+ Slapi_DN *dn;
|
|
+
|
|
+ /* If an alternate config area is configured, it is considered to be
|
|
+ * the config entry, otherwise the main plug-in config entry is used. */
|
|
+ if (sdn != NULL) {
|
|
+ dn = ipapwd_get_otp_config_area();
|
|
+ if (dn == NULL)
|
|
+ dn = ipapwd_get_plugin_sdn();
|
|
+
|
|
+ ret = slapi_sdn_compare(sdn, dn) == 0;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ipapwd_post_modadd_otp(Slapi_PBlock *pb)
|
|
+{
|
|
+ Slapi_Entry *config_entry = NULL;
|
|
+ Slapi_DN *sdn = NULL;
|
|
+
|
|
+ /* Just bail if we are not started yet, or if the operation failed. */
|
|
+ if (!ipapwd_get_plugin_started() || !ipapwd_otp_oktodo(pb)) {
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Check if a change affected our config entry and reload the
|
|
+ * in-memory config settings if needed. */
|
|
+ slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
|
|
+ if (ipapwd_dn_is_otp_config(sdn)) {
|
|
+ /* The config entry was added or modified, so reload it from
|
|
+ * the post-op entry. */
|
|
+ slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &config_entry);
|
|
+ if (config_entry == NULL) {
|
|
+ LOG_FATAL("Unable to retrieve config entry.\n");
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ ipapwd_parse_otp_config_entry(config_entry, true);
|
|
+ }
|
|
+
|
|
+done:
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ipapwd_post_modadd(Slapi_PBlock *pb)
|
|
{
|
|
void *op;
|
|
struct ipapwd_operation *pwdop = NULL;
|
|
@@ -991,6 +1064,11 @@ static int ipapwd_post_op(Slapi_PBlock *pb)
|
|
|
|
LOG_TRACE("=>\n");
|
|
|
|
+ ret = ipapwd_post_modadd_otp(pb);
|
|
+ if (ret != 0) {
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
/* time to get the operation handler */
|
|
ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
|
|
if (ret != 0) {
|
|
@@ -1111,6 +1189,202 @@ done:
|
|
return 0;
|
|
}
|
|
|
|
+static int ipapwd_post_modrdn_otp(Slapi_PBlock *pb)
|
|
+{
|
|
+ Slapi_Entry *config_entry = NULL;
|
|
+ Slapi_DN *new_sdn = NULL;
|
|
+ Slapi_DN *sdn = NULL;
|
|
+
|
|
+ /* Just bail if we are not started yet, or if the operation failed. */
|
|
+ if (!ipapwd_get_plugin_started() || !ipapwd_otp_oktodo(pb)) {
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Check if a change affected our config entry and reload the
|
|
+ * in-memory config settings if needed. */
|
|
+ slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
|
|
+ if (ipapwd_dn_is_otp_config(sdn)) {
|
|
+ /* Our config entry was renamed. We treat this like the entry
|
|
+ * was deleted, so just set the defaults. */
|
|
+ ipapwd_parse_otp_config_entry(NULL, true);
|
|
+ } else {
|
|
+ /* Check if an entry was renamed such that it has become our
|
|
+ * config entry. If so, reload the config from this new entry. */
|
|
+ slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &config_entry);
|
|
+ if (config_entry == NULL) {
|
|
+ LOG_FATAL("Unable to retrieve renamed entry.\n");
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ new_sdn = slapi_entry_get_sdn(config_entry);
|
|
+ if (new_sdn == NULL) {
|
|
+ LOG_FATAL("Unable to retrieve DN of renamed entry.\n");
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ if (ipapwd_dn_is_otp_config(new_sdn)) {
|
|
+ ipapwd_parse_otp_config_entry(config_entry, true);
|
|
+ }
|
|
+ }
|
|
+
|
|
+done:
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ipapwd_post_del_otp(Slapi_PBlock *pb)
|
|
+{
|
|
+ Slapi_DN *sdn = NULL;
|
|
+ int ret = 0;
|
|
+
|
|
+ /* Just bail if we are not started yet, or if the operation failed. */
|
|
+ if (!ipapwd_get_plugin_started() || !ipapwd_otp_oktodo(pb)) {
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Check if a change affected our config entry and reload the
|
|
+ * in-memory config settings if needed. */
|
|
+ slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
|
|
+ if (ipapwd_dn_is_otp_config(sdn)) {
|
|
+ /* The config entry was deleted, so this just sets the defaults. */
|
|
+ ipapwd_parse_otp_config_entry(NULL, true);
|
|
+ }
|
|
+
|
|
+done:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* Handle OTP authentication. */
|
|
+static int ipapwd_pre_bind_otp(Slapi_PBlock * pb)
|
|
+{
|
|
+ char *user_attrs[] = { IPA_USER_AUTH_TYPE, NULL };
|
|
+ int ret = IPAPWD_OP_NOT_HANDLED;
|
|
+ Slapi_Entry *bind_entry = NULL;
|
|
+ struct berval *creds = NULL;
|
|
+ const char *bind_dn = NULL;
|
|
+ Slapi_DN *bind_sdn = NULL;
|
|
+ int result = LDAP_SUCCESS;
|
|
+ char **auth_types = NULL;
|
|
+ int method;
|
|
+ int i;
|
|
+
|
|
+ /* If we didn't start successfully, bail. */
|
|
+ if (!ipapwd_get_plugin_started()) {
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* If global disabled flag is set, just punt. */
|
|
+ if (ipapwd_otp_is_disabled()) {
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Retrieve parameters for bind operation. */
|
|
+ i = slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
|
|
+ if (i == 0) {
|
|
+ i = slapi_pblock_get(pb, SLAPI_BIND_TARGET_SDN, &bind_sdn);
|
|
+ if (i == 0) {
|
|
+ i = slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &creds);
|
|
+ }
|
|
+ }
|
|
+ if (i != 0) {
|
|
+ LOG_FATAL("Not handled (can't retrieve bind parameters)\n");
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ bind_dn = slapi_sdn_get_dn(bind_sdn);
|
|
+
|
|
+ /* We only handle non-anonymous simple binds. We just pass everything
|
|
+ * else through to the server. */
|
|
+ if (method != LDAP_AUTH_SIMPLE || *bind_dn == '\0' || creds->bv_len == 0) {
|
|
+ LOG_TRACE("Not handled (not simple bind or NULL dn/credentials)\n");
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Check if any allowed authentication types are set in the user entry.
|
|
+ * If not, we just use the global settings from the config entry. */
|
|
+ result = slapi_search_internal_get_entry(bind_sdn, user_attrs, &bind_entry,
|
|
+ ipapwd_get_plugin_id());
|
|
+ if (result != LDAP_SUCCESS) {
|
|
+ LOG_FATAL("Not handled (could not search for BIND dn %s - error "
|
|
+ "%d : %s)\n", bind_dn, result, ldap_err2string(result));
|
|
+ goto done;
|
|
+ }
|
|
+ if (bind_entry == NULL) {
|
|
+ LOG_FATAL("Not handled (could not find entry for BIND dn %s)\n", bind_dn);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ i = slapi_check_account_lock(pb, bind_entry, 0, 0, 0);
|
|
+ if (i == 1) {
|
|
+ LOG_TRACE("Not handled (account %s inactivated.)\n", bind_dn);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ auth_types = slapi_entry_attr_get_charray(bind_entry, IPA_USER_AUTH_TYPE);
|
|
+
|
|
+ /*
|
|
+ * IMPORTANT SECTION!
|
|
+ *
|
|
+ * This section handles authentication logic, so be careful!
|
|
+ *
|
|
+ * The basic idea of this section is:
|
|
+ * 1. If OTP is enabled, try to use it first. If successful, send response.
|
|
+ * 2. If OTP was not enabled/successful, check if password is enabled.
|
|
+ * 3. If password is not enabled, send failure response.
|
|
+ * 4. Otherwise, fall through to standard server password authentication.
|
|
+ *
|
|
+ */
|
|
+
|
|
+ /* If OTP is allowed, attempt to do OTP authentication. */
|
|
+ if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_OTP)) {
|
|
+ LOG_PLUGIN_NAME(IPAPWD_PLUGIN_NAME,
|
|
+ "Attempting OTP authentication for '%s'.\n", bind_dn);
|
|
+ if (ipapwd_do_otp_auth(bind_entry, creds)) {
|
|
+ /* FIXME - NGK - If the auth type request control was sent,
|
|
+ * construct the response control to indicate what auth type was
|
|
+ * used. We might be able to do this in the
|
|
+ * SLAPI_PLUGIN_PRE_RESULT_FN callback instead of here. */
|
|
+
|
|
+ /* FIXME - NGK - What about other controls, like the pwpolicy
|
|
+ * control? If any other critical controls are set, we need to
|
|
+ * either process them properly or reject the operation with an
|
|
+ * unsupported critical control error. */
|
|
+
|
|
+ /* Send response approving authentication. */
|
|
+ slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
|
|
+ ret = IPAPWD_OP_HANDLED;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* If OTP failed or was not enabled, we need to figure out if we can fall
|
|
+ * back to standard password authentication or give an error. */
|
|
+ if (ret != IPAPWD_OP_HANDLED) {
|
|
+ if (!ipapwd_is_auth_type_allowed(auth_types,
|
|
+ IPA_OTP_AUTH_TYPE_PASSWORD)) {
|
|
+ /* Password authentication is disabled, so we have failed. */
|
|
+ slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS,
|
|
+ NULL, NULL, 0, NULL);
|
|
+ ret = IPAPWD_OP_HANDLED;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Password authentication is permitted, so tell the server that we
|
|
+ * didn't handle this request. Then the server will perform standard
|
|
+ * password authentication. */
|
|
+ LOG_PLUGIN_NAME(IPAPWD_PLUGIN_NAME,
|
|
+ "Attempting PASSWORD authentication for \"%s\".\n",
|
|
+ bind_dn);
|
|
+
|
|
+ /* FIXME - NGK - Do we need to figure out how to build
|
|
+ * the reponse control in this case? Maybe we can use a
|
|
+ * SLAPI_PLUGIN_PRE_RESULT_FN callback to handle that? */
|
|
+ }
|
|
+
|
|
+done:
|
|
+ slapi_ch_array_free(auth_types);
|
|
+ slapi_entry_free(bind_entry);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
/* PRE BIND Operation:
|
|
* Used for password migration from DS to IPA.
|
|
* Gets the clean text password, authenticates the user and generates
|
|
@@ -1137,6 +1411,12 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
|
|
|
|
LOG_TRACE("=>\n");
|
|
|
|
+ /* Try to do OTP first. */
|
|
+ ret = ipapwd_pre_bind_otp(pb);
|
|
+ if (ret == IPAPWD_OP_HANDLED) {
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
/* get BIND parameters */
|
|
ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn);
|
|
ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
|
|
@@ -1295,8 +1575,6 @@ done:
|
|
return 0;
|
|
}
|
|
|
|
-
|
|
-
|
|
/* Init pre ops */
|
|
int ipapwd_pre_init(Slapi_PBlock *pb)
|
|
{
|
|
@@ -1330,20 +1608,35 @@ int ipapwd_post_init(Slapi_PBlock *pb)
|
|
|
|
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
|
|
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_op);
|
|
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_modadd);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *)ipapwd_post_del_otp);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_modadd);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *)ipapwd_post_modrdn_otp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
+int ipapwd_intpost_init(Slapi_PBlock *pb)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *)ipapwd_post_modadd_otp);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *)ipapwd_post_del_otp);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *)ipapwd_post_modadd_otp);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *)ipapwd_post_modrdn_otp);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
int ipapwd_post_init_betxn(Slapi_PBlock *pb)
|
|
{
|
|
int ret;
|
|
|
|
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
|
|
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_op);
|
|
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_op);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_modadd);
|
|
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_modadd);
|
|
|
|
return ret;
|
|
}
|
|
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..d57f9ab68bebb2c77f3bc327c50bdd6eb480f67e
|
|
--- /dev/null
|
|
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c
|
|
@@ -0,0 +1,82 @@
|
|
+/** BEGIN COPYRIGHT BLOCK
|
|
+ * This Program 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; version 3 of the License.
|
|
+ *
|
|
+ * 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., 59 Temple
|
|
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
+ *
|
|
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
|
|
+ * right to link the code of this Program with code not covered under the GNU
|
|
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
|
|
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
|
|
+ * permitted under this exception must only link to the code of this Program
|
|
+ * through those well defined interfaces identified in the file named EXCEPTION
|
|
+ * found in the source code files (the "Approved Interfaces"). The files of
|
|
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
|
|
+ * the Approved Interfaces without causing the resulting work to be covered by
|
|
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
|
|
+ * additions to the list of Approved Interfaces. You must obey the GNU General
|
|
+ * Public License in all respects for all of the Program code and other code used
|
|
+ * in conjunction with the Program except the Non-GPL Code covered by this
|
|
+ * exception. If you modify this file, you may extend this exception to your
|
|
+ * version of the file, but you are not obligated to do so. If you do not wish to
|
|
+ * provide this exception without modification, you must delete this exception
|
|
+ * statement from your version and license this file solely under the GPL without
|
|
+ * exception.
|
|
+ *
|
|
+ *
|
|
+ * Copyright (C) 2013 Red Hat, Inc.
|
|
+ * All rights reserved.
|
|
+ * END COPYRIGHT BLOCK **/
|
|
+
|
|
+#include <assert.h>
|
|
+#include <stdbool.h>
|
|
+#include <stdint.h>
|
|
+#include <stddef.h>
|
|
+#include <time.h>
|
|
+#include <string.h>
|
|
+#include <nss.h>
|
|
+
|
|
+/*
|
|
+ * From otp.c
|
|
+ */
|
|
+bool ipapwd_hotp(const uint8_t *key, size_t len, const char *algo, int digits,
|
|
+ uint64_t counter, uint32_t *out);
|
|
+
|
|
+/* All HOTP test examples from RFC 4226 (Appendix D). */
|
|
+static const uint8_t *key = (uint8_t *) "12345678901234567890";
|
|
+static const uint32_t answers[] = {
|
|
+ 755224,
|
|
+ 287082,
|
|
+ 359152,
|
|
+ 969429,
|
|
+ 338314,
|
|
+ 254676,
|
|
+ 287922,
|
|
+ 162583,
|
|
+ 399871,
|
|
+ 520489
|
|
+};
|
|
+
|
|
+int
|
|
+main(int argc, const char *argv[])
|
|
+{
|
|
+ uint32_t otp;
|
|
+ int i;
|
|
+
|
|
+ NSS_NoDB_Init(".");
|
|
+
|
|
+ for (i = 0; i < sizeof(answers) / sizeof(*answers); i++) {
|
|
+ assert(ipapwd_hotp(key, 20, "sha1", 6, i, &otp));
|
|
+ assert(otp == answers[i]);
|
|
+ }
|
|
+
|
|
+ NSS_Shutdown();
|
|
+ return 0;
|
|
+}
|
|
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..2df8d245818f90277ece273a8f0591538a4707a6
|
|
--- /dev/null
|
|
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c
|
|
@@ -0,0 +1,103 @@
|
|
+/** BEGIN COPYRIGHT BLOCK
|
|
+ * This Program 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; version 3 of the License.
|
|
+ *
|
|
+ * 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., 59 Temple
|
|
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
+ *
|
|
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
|
|
+ * right to link the code of this Program with code not covered under the GNU
|
|
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
|
|
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
|
|
+ * permitted under this exception must only link to the code of this Program
|
|
+ * through those well defined interfaces identified in the file named EXCEPTION
|
|
+ * found in the source code files (the "Approved Interfaces"). The files of
|
|
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
|
|
+ * the Approved Interfaces without causing the resulting work to be covered by
|
|
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
|
|
+ * additions to the list of Approved Interfaces. You must obey the GNU General
|
|
+ * Public License in all respects for all of the Program code and other code used
|
|
+ * in conjunction with the Program except the Non-GPL Code covered by this
|
|
+ * exception. If you modify this file, you may extend this exception to your
|
|
+ * version of the file, but you are not obligated to do so. If you do not wish to
|
|
+ * provide this exception without modification, you must delete this exception
|
|
+ * statement from your version and license this file solely under the GPL without
|
|
+ * exception.
|
|
+ *
|
|
+ *
|
|
+ * Copyright (C) 2013 Red Hat, Inc.
|
|
+ * All rights reserved.
|
|
+ * END COPYRIGHT BLOCK **/
|
|
+
|
|
+#include <assert.h>
|
|
+#include <stdbool.h>
|
|
+#include <stdint.h>
|
|
+#include <stddef.h>
|
|
+#include <time.h>
|
|
+#include <string.h>
|
|
+#include <nss.h>
|
|
+
|
|
+/*
|
|
+ * From otp.c
|
|
+ */
|
|
+bool ipapwd_totp(const uint8_t *key, size_t len, const char *algo, int digits,
|
|
+ time_t time, int offset, unsigned int step, uint32_t *out);
|
|
+
|
|
+#define SHA1 "sha1", (uint8_t *) "12345678901234567890", 20
|
|
+#define SHA256 "sha256", (uint8_t *) "12345678901234567890123456789012", 32
|
|
+#define SHA512 "sha512", (uint8_t *) "12345678901234567890123456789012" \
|
|
+ "34567890123456789012345678901234", 64
|
|
+
|
|
+/* All TOTP test examples from RFC 6238 (Appendix B). */
|
|
+const static struct {
|
|
+ const char *algo;
|
|
+ const uint8_t *key;
|
|
+ size_t len;
|
|
+ time_t time;
|
|
+ uint32_t answer;
|
|
+} tests[] = {
|
|
+ { SHA1, 59, 94287082 },
|
|
+ { SHA256, 59, 46119246 },
|
|
+ { SHA512, 59, 90693936 },
|
|
+ { SHA1, 1111111109, 7081804 },
|
|
+ { SHA256, 1111111109, 68084774 },
|
|
+ { SHA512, 1111111109, 25091201 },
|
|
+ { SHA1, 1111111111, 14050471 },
|
|
+ { SHA256, 1111111111, 67062674 },
|
|
+ { SHA512, 1111111111, 99943326 },
|
|
+ { SHA1, 1234567890, 89005924 },
|
|
+ { SHA256, 1234567890, 91819424 },
|
|
+ { SHA512, 1234567890, 93441116 },
|
|
+ { SHA1, 2000000000, 69279037 },
|
|
+ { SHA256, 2000000000, 90698825 },
|
|
+ { SHA512, 2000000000, 38618901 },
|
|
+#ifdef _LP64 /* Only do these tests on 64-bit systems. */
|
|
+ { SHA1, 20000000000, 65353130 },
|
|
+ { SHA256, 20000000000, 77737706 },
|
|
+ { SHA512, 20000000000, 47863826 },
|
|
+#endif
|
|
+};
|
|
+
|
|
+int
|
|
+main(int argc, const char *argv[])
|
|
+{
|
|
+ uint32_t otp;
|
|
+ int i;
|
|
+
|
|
+ NSS_NoDB_Init(".");
|
|
+
|
|
+ for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
|
|
+ assert(ipapwd_totp(tests[i].key, tests[i].len, tests[i].algo,
|
|
+ 8, tests[i].time, 0, 30, &otp));
|
|
+ assert(otp == tests[i].answer);
|
|
+ }
|
|
+
|
|
+ NSS_Shutdown();
|
|
+ return 0;
|
|
+}
|
|
--
|
|
1.8.2.1
|
|
|