On Wed, 2014-10-29 at 09:34 -0400, Nathaniel McCallum wrote:
> On Wed, 2014-10-29 at 12:21 +0100, Petr Viktorin wrote:
> > On 10/29/2014 10:37 AM, Martin Kosek wrote:
> > > On 10/28/2014 09:59 PM, Nathaniel McCallum wrote:
> > >> On Thu, 2014-10-23 at 18:07 -0400, Nathaniel McCallum wrote:
> > >>> This patch gives the administrator variables to control the size of
> > >>> the authentication and synchronization windows for OTP tokens.
> > >>>
> > >>> https://fedorahosted.org/freeipa/ticket/4511
> > >>>
> > >>> NOTE: There is one known issue with this patch which I don't know how to
> > >>> solve. This patch changes the schema in install/share/60ipaconfig.ldif.
> > >>> On an upgrade, all of the new attributeTypes appear correctly. However,
> > >>> the modifications to the pre-existing objectClass do not show up on the
> > >>> server. What am I doing wrong?
> > >>>
> > >>> After modifying ipaGuiConfig manually, everything in this patch works
> > >>> just fine.
> > >>
> > >> This new version takes into account the new (proper) OIDs and attribute
> > >> names.
> > >
> > > Thanks Nathaniel!
> > >
> > >> The above known issue still remains.
> > >
> > > Petr3, any idea what could have gone wrong? ObjectClass MAY list extension
> > > should work just fine, AFAIK.
> > 
> > You added a blank line to the LDIF file. This is an entry separator, so 
> > the objectClasses after the blank line don't belong to cn=schema, so 
> > they aren't considered in the update.
> > Without the blank line it works fine.
> 
> Thanks for the catch!
> 
> Here is a version without the blank line.

I forgot to remove the old steps defines. This patch performs this
cleanup.


From 6007faa6fc86de5087ab8028febe162557ea46be Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Thu, 23 Oct 2014 15:18:26 -0400
Subject: [PATCH] Make token window sizes configurable

This patch gives the administrator variables to control the size of
the authentication and synchronization windows for OTP tokens.

https://fedorahosted.org/freeipa/ticket/4511
---
 API.txt                                           |   6 +-
 VERSION                                           |   4 +-
 daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.c | 195 +++++++++++++++++-----
 daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.h |  17 ++
 daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c |  79 +++------
 daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c |   7 +-
 daemons/ipa-slapi-plugins/libotp/libotp.c         | 133 +++++++--------
 daemons/ipa-slapi-plugins/libotp/libotp.h         |  30 ++--
 install/share/60ipaconfig.ldif                    |   6 +-
 install/ui/src/freeipa/serverconfig.js            |  10 ++
 install/ui/test/data/ipa_init.json                |   3 +-
 install/updates/40-otp.update                     |   6 +
 ipalib/plugins/config.py                          |  31 +++-
 ipalib/plugins/internal.py                        |   1 +
 14 files changed, 333 insertions(+), 195 deletions(-)

diff --git a/API.txt b/API.txt
index 0000491d7a76fd1d2d50208d314d1600839ce295..4f204d0fa2e33dc4c9202645e111c25d2a545d70 100644
--- a/API.txt
+++ b/API.txt
@@ -514,7 +514,7 @@ args: 0,1,1
 option: Str('version?', exclude='webui')
 output: Output('result', None, None)
 command: config_mod
-args: 0,25,3
+args: 0,29,3
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('delattr*', cli_name='delattr', exclude='webui')
@@ -525,6 +525,8 @@ option: Str('ipadefaultprimarygroup', attribute=True, autofill=False, cli_name='
 option: Str('ipagroupobjectclasses', attribute=True, autofill=False, cli_name='groupobjectclasses', csv=True, multivalue=True, required=False)
 option: IA5Str('ipagroupsearchfields', attribute=True, autofill=False, cli_name='groupsearch', multivalue=False, required=False)
 option: IA5Str('ipahomesrootdir', attribute=True, autofill=False, cli_name='homedirectory', multivalue=False, required=False)
+option: Int('ipahotpauthwindow', attribute=True, autofill=False, cli_name='hotp_auth_window', maxvalue=1000, minvalue=1, multivalue=False, required=False)
+option: Int('ipahotpsyncwindow', attribute=True, autofill=False, cli_name='hotp_sync_window', maxvalue=1000, minvalue=1, multivalue=False, required=False)
 option: StrEnum('ipakrbauthzdata', attribute=True, autofill=False, cli_name='pac_type', csv=True, multivalue=True, required=False, values=(u'MS-PAC', u'PAD', u'nfs:NONE'))
 option: Int('ipamaxusernamelength', attribute=True, autofill=False, cli_name='maxusername', minvalue=1, multivalue=False, required=False)
 option: Bool('ipamigrationenabled', attribute=True, autofill=False, cli_name='enable_migration', multivalue=False, required=False)
@@ -533,6 +535,8 @@ option: Int('ipasearchrecordslimit', attribute=True, autofill=False, cli_name='s
 option: Int('ipasearchtimelimit', attribute=True, autofill=False, cli_name='searchtimelimit', minvalue=-1, multivalue=False, required=False)
 option: Str('ipaselinuxusermapdefault', attribute=True, autofill=False, cli_name='ipaselinuxusermapdefault', multivalue=False, required=False)
 option: Str('ipaselinuxusermaporder', attribute=True, autofill=False, cli_name='ipaselinuxusermaporder', multivalue=False, required=False)
+option: Int('ipatotpauthwindow', attribute=True, autofill=False, cli_name='totp_auth_window', maxvalue=2678400, minvalue=30, multivalue=False, required=False)
+option: Int('ipatotpsyncwindow', attribute=True, autofill=False, cli_name='totp_sync_window', maxvalue=2678400, minvalue=30, multivalue=False, required=False)
 option: StrEnum('ipauserauthtype', attribute=True, autofill=False, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius', u'otp'))
 option: Str('ipauserobjectclasses', attribute=True, autofill=False, cli_name='userobjectclasses', csv=True, multivalue=True, required=False)
 option: IA5Str('ipausersearchfields', attribute=True, autofill=False, cli_name='usersearch', multivalue=False, required=False)
diff --git a/VERSION b/VERSION
index b0d41e5e1ec59ddefbdcccf588b97bac2ff798ee..9ac8551510a525822a1e356e7241f52cebfbe288 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=108
-# Last change: pvoborni - manage authorization of keytab operations
+IPA_API_VERSION_MINOR=109
+# Last change: npmccallum - OTP window configuration
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.c
index 3ab5668edd7edcb9eaf247c18b964f6584c9d439..8fd7da7c94efdc038bee7709cb75f94e4ab2942a 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.c
@@ -40,15 +40,34 @@
 #include "authcfg.h"
 #include "ipapwd.h"
 
-#include "pratom.h"
+#include <pratom.h>
+#include <plstr.h>
 
-static struct config {
-    struct config *next;
+#define DEFAULT_AUTH_TOTP 300
+#define DEFAULT_AUTH_HOTP 10
+#define DEFAULT_SYNC_TOTP 86400
+#define DEFAULT_SYNC_HOTP 100
+#define DEFAULTS_AUTH { DEFAULT_AUTH_HOTP, DEFAULT_AUTH_TOTP }
+#define DEFAULTS_SYNC { DEFAULT_SYNC_HOTP, DEFAULT_SYNC_TOTP }
+#define DEFAULTS (struct config) { \
+        AUTHCFG_AUTH_TYPE_NONE, \
+        DEFAULTS_AUTH, \
+        DEFAULTS_SYNC \
+    }
+
+struct config {
+    uint32_t authtype;
+    struct otptoken_window auth;
+    struct otptoken_window sync;
+};
+
+static struct record {
+    struct record *next;
     Slapi_DN *suffix;
-    uint32_t config;
-} *config;
+    struct config cfg;
+} *configs;
 
-static uint32_t string_to_config(const char *str)
+static uint32_t string_to_types(const char *str)
 {
     static const struct {
         const char *string;
@@ -70,7 +89,7 @@ static uint32_t string_to_config(const char *str)
     return AUTHCFG_AUTH_TYPE_NONE;
 }
 
-static uint32_t entry_to_config(Slapi_Entry *e)
+static uint32_t entry_to_types(Slapi_Entry *e)
 {
     char **auth_types = NULL;
 
@@ -84,13 +103,48 @@ static uint32_t entry_to_config(Slapi_Entry *e)
 
     uint32_t types = AUTHCFG_AUTH_TYPE_NONE;
     for (uint32_t i = 0; auth_types[i] != NULL; i++)
-        types |= string_to_config(auth_types[i]);
+        types |= string_to_types(auth_types[i]);
 
     slapi_ch_array_free(auth_types);
 
     return types;
 }
 
+static long long entry_to_window(Slapi_Entry *e, const char *attr)
+{
+    struct {
+        const char *attr;
+        long long dflt;
+    } defaults[] = {
+        { "ipaTOTPAuthWindow", DEFAULT_AUTH_TOTP },
+        { "ipaHOTPAuthWindow", DEFAULT_AUTH_HOTP },
+        { "ipaTOTPSyncWindow", DEFAULT_SYNC_TOTP },
+        { "ipaHOTPSyncWindow", DEFAULT_SYNC_HOTP },
+        { NULL, 0 }
+    };
+
+    long long window = slapi_entry_attr_get_longlong(e, attr);
+    if (window > 0)
+        return window;
+
+    for (size_t i = 0; defaults[i].attr != NULL; i++) {
+        if (PL_strcasecmp(defaults[i].attr, attr) == 0)
+            return defaults[i].dflt;
+    }
+
+    return 0;
+}
+
+static struct config entry_to_config(Slapi_Entry *e) {
+    return (struct config) {
+        .authtype = entry_to_types(e),
+        .auth.hotp = entry_to_window(e, "ipaHOTPAuthWindow"),
+        .auth.totp = entry_to_window(e, "ipaTOTPAuthWindow"),
+        .sync.hotp = entry_to_window(e, "ipaHOTPSyncWindow"),
+        .sync.totp = entry_to_window(e, "ipaTOTPSyncWindow")
+    };
+}
+
 static Slapi_DN *suffix_to_config_dn(Slapi_DN *suffix)
 {
     Slapi_DN *sdn = NULL;
@@ -108,28 +162,35 @@ static Slapi_DN *suffix_to_config_dn(Slapi_DN *suffix)
     return sdn;
 }
 
-static uint32_t suffix_to_config(Slapi_DN *suffix)
+static struct config suffix_to_config(Slapi_DN *suffix)
 {
-    static char *attrs[] = { "ipaUserAuthType", NULL };
+    static char *attrs[] = {
+        "ipaUserAuthType",
+        "ipaHOTPAuthWindow",
+        "ipaTOTPAuthWindow",
+        "ipaHOTPSyncWindow",
+        "ipaTOTPSyncWindow",
+        NULL
+    };
+
+    struct config cfg = DEFAULTS;
     Slapi_Entry *entry = NULL;
     Slapi_DN *sdn = NULL;
-    uint32_t types;
     int ret;
 
     sdn = suffix_to_config_dn(suffix);
     if (sdn == NULL)
-        return AUTHCFG_AUTH_TYPE_NONE;
+        return cfg;
 
     ret = slapi_search_internal_get_entry(sdn, attrs, &entry,
                                           ipapwd_get_plugin_id());
     slapi_sdn_free(&sdn);
     if (ret != LDAP_SUCCESS)
-        return AUTHCFG_AUTH_TYPE_NONE;
+        return cfg;
 
-    types = entry_to_config(entry);
+    cfg = entry_to_config(entry);
     slapi_entry_free(entry);
-
-    return types;
+    return cfg;
 }
 
 static Slapi_DN *sdn_to_suffix(Slapi_DN *sdn)
@@ -171,7 +232,7 @@ static bool sdn_is_config(Slapi_DN *sdn)
     return cmp == 0;
 }
 
-void cache_free(struct config **cfg)
+void cache_free(struct record **cfg)
 {
     if (cfg == NULL || *cfg == NULL)
         return;
@@ -183,12 +244,12 @@ void cache_free(struct config **cfg)
 
 bool authcfg_init(void)
 {
-    struct config *cfg = NULL;
+    struct record *cfg = NULL;
     Slapi_DN *sfx = NULL;
     void *node = NULL;
 
     /* If we are already initialized, return true. */
-    if (config != NULL)
+    if (configs != NULL)
         return true;
 
     /* Look up the config for each suffix. */
@@ -201,9 +262,9 @@ bool authcfg_init(void)
         }
 
         cfg->suffix = sfx;
-        cfg->config = suffix_to_config(sfx);
-        cfg->next = config;
-        config = cfg;
+        cfg->cfg = suffix_to_config(sfx);
+        cfg->next = configs;
+        configs = cfg;
     }
 
     return true;
@@ -211,36 +272,50 @@ bool authcfg_init(void)
 
 void authcfg_fini(void)
 {
-    cache_free(&config);
+    cache_free(&configs);
+}
+
+static struct record *find_record_sdn(Slapi_DN *user_dn)
+{
+    Slapi_DN *sfx = sdn_to_suffix(user_dn);
+
+    /* Find the global config. */
+    for (struct record *rec = configs; rec && sfx; rec = rec->next) {
+        if (slapi_sdn_compare(sfx, rec->suffix) == 0)
+            return rec;
+    }
+
+    return NULL;
+}
+
+static struct record *find_record_dn(const char *dn)
+{
+    Slapi_DN *sdn;
+    struct record *rec;
+
+    sdn = slapi_sdn_new_dn_byval(dn);
+    rec = find_record_sdn(sdn);
+    slapi_sdn_free(&sdn);
+    return rec;
 }
 
 uint32_t authcfg_get_auth_types(Slapi_Entry *user_entry)
 {
     uint32_t glbl = AUTHCFG_AUTH_TYPE_NONE;
     uint32_t user = AUTHCFG_AUTH_TYPE_NONE;
-    Slapi_DN *sfx = NULL;
-    Slapi_DN *sdn = NULL;
+    struct record *rec;
 
-    /* Find the root suffix. */
-    sdn = slapi_entry_get_sdn(user_entry);
-    sfx = sdn_to_suffix(sdn);
-
-    /* Find the global config. */
-    if (sfx != NULL) {
-        for (struct config *cfg = config; cfg && sfx; cfg = cfg->next) {
-            if (slapi_sdn_compare(sfx, cfg->suffix) == 0) {
-                glbl = PR_ATOMIC_ADD(&cfg->config, 0);
-                break;
-            }
-        }
-    }
+    /* Read the record. */
+    rec = find_record_sdn(slapi_entry_get_sdn(user_entry));
+    if (rec != NULL)
+        glbl = PR_ATOMIC_ADD(&rec->cfg.authtype, 0);
 
     /* Global disabled overrides user settings. */
     if (glbl & AUTHCFG_AUTH_TYPE_DISABLED)
         return AUTHCFG_AUTH_TYPE_DISABLED;
 
     /* Get the user's config. */
-    user = entry_to_config(user_entry);
+    user = entry_to_types(user_entry);
 
     if (user == AUTHCFG_AUTH_TYPE_NONE) {
         if (glbl == AUTHCFG_AUTH_TYPE_NONE)
@@ -251,9 +326,37 @@ uint32_t authcfg_get_auth_types(Slapi_Entry *user_entry)
     return user & ~AUTHCFG_AUTH_TYPE_DISABLED;
 }
 
+struct otptoken_window authcfg_get_auth_window(const char *user_dn)
+{
+    struct otptoken_window window = DEFAULTS_AUTH;
+    struct record *rec;
+
+    rec = find_record_dn(user_dn);
+    if (rec != NULL) {
+        window.hotp = PR_ATOMIC_ADD(&rec->cfg.auth.hotp, 0);
+        window.totp = PR_ATOMIC_ADD(&rec->cfg.auth.totp, 0);
+    }
+
+    return window;
+}
+
+struct otptoken_window authcfg_get_sync_window(const char *user_dn)
+{
+    struct otptoken_window window = DEFAULTS_SYNC;
+    struct record *rec;
+
+    rec = find_record_dn(user_dn);
+    if (rec != NULL) {
+        window.hotp = PR_ATOMIC_ADD(&rec->cfg.sync.hotp, 0);
+        window.totp = PR_ATOMIC_ADD(&rec->cfg.sync.totp, 0);
+    }
+
+    return window;
+}
+
 void authcfg_reload_global_config(Slapi_DN *sdn, Slapi_Entry *config_entry)
 {
-    uint32_t glbl = AUTHCFG_AUTH_TYPE_NONE;
+    struct config cfg = DEFAULTS;
     Slapi_DN *sfx = NULL;
     Slapi_DN *dest;
 
@@ -263,7 +366,7 @@ void authcfg_reload_global_config(Slapi_DN *sdn, Slapi_Entry *config_entry)
     /* Added, modified, moved into place. */
     if (sdn_is_config(dest)) {
         sfx = sdn_to_suffix(dest);
-        glbl = entry_to_config(config_entry);
+        cfg = entry_to_config(config_entry);
 
     /* Deleted, moved out of place. */
     } else if (sdn_is_config(sdn)) {
@@ -271,9 +374,13 @@ void authcfg_reload_global_config(Slapi_DN *sdn, Slapi_Entry *config_entry)
     }
 
     /* Reload config. */
-    for (struct config *cfg = config; cfg && sfx; cfg = cfg->next) {
-        if (slapi_sdn_compare(sfx, cfg->suffix) == 0) {
-            PR_ATOMIC_SET(&cfg->config, glbl);
+    for (struct record *rec = configs; rec && sfx; rec = rec->next) {
+        if (slapi_sdn_compare(sfx, rec->suffix) == 0) {
+            PR_ATOMIC_SET(&rec->cfg.authtype, cfg.authtype);
+            PR_ATOMIC_SET(&rec->cfg.auth.hotp, cfg.auth.hotp);
+            PR_ATOMIC_SET(&rec->cfg.auth.totp, cfg.auth.totp);
+            PR_ATOMIC_SET(&rec->cfg.sync.hotp, cfg.sync.hotp);
+            PR_ATOMIC_SET(&rec->cfg.sync.totp, cfg.sync.totp);
             break;
         }
     }
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.h b/daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.h
index c2fc24605c0f915261a57967c43c35ab6e773263..c7e0222cad387172b60862bdf458d4da6a1c533c 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.h
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/authcfg.h
@@ -43,6 +43,7 @@
 
 #include <dirsrv/slapi-plugin.h>
 #include <stdbool.h>
+#include <libotp.h>
 
 #define AUTHCFG_AUTH_TYPE_NONE     0
 #define AUTHCFG_AUTH_TYPE_DISABLED 1
@@ -71,6 +72,22 @@ void authcfg_fini(void);
  */
 uint32_t authcfg_get_auth_types(Slapi_Entry *user_entry);
 
+/* Gets the window size for authentication requests.
+ *
+ * This reads the "ipaTOTPAuthWindow" and "ipaHOTPAuthWindow" attributes.
+ *
+ * Thread Safety: YES
+ */
+struct otptoken_window authcfg_get_auth_window(const char *user_dn);
+
+/* Gets the window size for synchronization requests.
+ *
+ * This reads the "ipaTOTPSyncWindow" and "ipaHOTPSyncWindow" attributes.
+ *
+ * Thread Safety: YES
+ */
+struct otptoken_window authcfg_get_sync_window(const char *user_dn);
+
 /* Reloads configuration from the specified global config entry.
  *
  * If the provided entry isn't a global config entry, this is a no-op.
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
index 60ceaaa7ab0cd282efb45f1a89de9dbd240a452c..e9e83dcdbbbb21d0838b777938b6e0cdab90e110 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
@@ -69,8 +69,6 @@
 #define IPAPWD_OP_ADD 1
 #define IPAPWD_OP_MOD 2
 
-#define OTP_VALIDATE_STEPS 3
-
 extern Slapi_PluginDesc ipapwd_plugin_desc;
 extern void *ipapwd_plugin_id;
 extern const char *ipa_realm_tree;
@@ -1127,8 +1125,8 @@ done:
 }
 
 /*
- * Authenticates creds against OTP tokens. Returns true when authentication
- * completed successfully against a token OR when a user has no active tokens.
+ * This function handles the bind functionality for OTP. The return value
+ * indicates if the OTP portion of authentication was successful.
  *
  * WARNING: This function DOES NOT authenticate the first factor. Only the OTP
  *          code is validated! You still need to validate the first factor.
@@ -1137,53 +1135,6 @@ done:
  *       value at the end. This leaves only the password in creds for later
  *       validation.
  */
-static bool ipapwd_do_otp_auth(const char *dn, Slapi_Entry *bind_entry,
-                               struct berval *creds)
-{
-    struct otptoken **tokens = NULL;
-    bool success = false;
-
-    /* Find all of the user's active tokens. */
-    tokens = otptoken_find(ipapwd_plugin_id, dn, NULL, true, NULL);
-    if (tokens == NULL) {
-        slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
-                        "%s: can't find tokens for '%s'.\n", __func__, dn);
-        return false;
-    }
-
-    /* If the user has no active tokens, succeed. */
-    success = tokens[0] == NULL;
-
-    /* Loop through each token. */
-    for (int i = 0; tokens[i] && !success; i++) {
-        /* Attempt authentication. */
-        success = otptoken_validate_berval(tokens[i], OTP_VALIDATE_STEPS,
-                                           creds, true);
-
-        /* Truncate the password to remove the OTP code at the end. */
-        if (success) {
-            creds->bv_len -= otptoken_get_digits(tokens[i]);
-            creds->bv_val[creds->bv_len] = '\0';
-        }
-
-        slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
-                        "%s: token authentication %s "
-                        "(user: '%s', token: '%s\').\n", __func__,
-                        success ? "succeeded" : "failed", dn,
-                        slapi_sdn_get_ndn(otptoken_get_sdn(tokens[i])));
-    }
-
-    otptoken_free_array(tokens);
-    return success;
-}
-
-/*
- * This function handles the bind functionality for OTP. The return value
- * indicates if the OTP portion of authentication was successful.
- *
- * NOTE: This function may modify creds. See explanation in the comment for
- *       ipapwd_do_otp_auth() above.
- */
 static bool ipapwd_pre_bind_otp(const char *bind_dn, Slapi_Entry *entry,
                                 struct berval *creds)
 {
@@ -1207,10 +1158,34 @@ static bool ipapwd_pre_bind_otp(const char *bind_dn, Slapi_Entry *entry,
      */
 
     if (auth_types & AUTHCFG_AUTH_TYPE_OTP) {
+        struct otptoken **tokens = NULL;
+        struct otptoken_window window;
+
         LOG_PLUGIN_NAME(IPAPWD_PLUGIN_NAME,
                         "Attempting OTP authentication for '%s'.\n", bind_dn);
-        if (ipapwd_do_otp_auth(bind_dn, entry, creds))
+
+        /* Find all of the user's active tokens. */
+        tokens = otptoken_find(ipapwd_plugin_id, bind_dn, NULL, true, NULL);
+        if (tokens == NULL) {
+            slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+                            "%s: can't find tokens for '%s'.\n",
+                            __func__, bind_dn);
+            return false;
+        }
+
+        /* If the user has no active tokens, succeed. */
+        if (tokens[0] == NULL) {
+            otptoken_free_array(tokens);
             return true;
+        }
+
+        window = authcfg_get_auth_window(bind_dn);
+        if (otptoken_validate_berval(tokens, &window, creds, NULL, true)) {
+            otptoken_free_array(tokens);
+            return true;
+        }
+
+        otptoken_free_array(tokens);
     }
 
     return auth_types & AUTHCFG_AUTH_TYPE_PASSWORD;
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c
index 2bfcf10a271a497741f08bb519020cd159eb4aeb..73e64a73a436a7f815224589d1c42883c081f052 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c
@@ -38,11 +38,9 @@
  * END COPYRIGHT BLOCK **/
 
 
-#include <libotp.h>
+#include "authcfg.h"
 #include "syncreq.h"
 
-#define OTP_SYNC_MAX_STEPS 25
-
 bool sync_request_present(Slapi_PBlock *pb)
 {
     LDAPControl **controls = NULL;
@@ -93,7 +91,8 @@ bool sync_request_handle(Slapi_ComponentId *plugin_id, Slapi_PBlock *pb,
         if (ber_scanf(ber, "}") != LBER_ERROR) {
             tokens = otptoken_find(plugin_id, user_dn, token_dn, true, NULL);
             if (tokens != NULL) {
-                success = otptoken_sync_berval(tokens, OTP_SYNC_MAX_STEPS, first, second);
+                struct otptoken_window window = authcfg_get_sync_window(user_dn);
+                success = otptoken_validate_berval(tokens, &window, first, second, false);
                 otptoken_free_array(tokens);
             }
         }
diff --git a/daemons/ipa-slapi-plugins/libotp/libotp.c b/daemons/ipa-slapi-plugins/libotp/libotp.c
index c65aef04348da5abdb1eeb6267e785f0342c7d51..9256f133f8497704b65e6a7c12aec56a5dc747b1 100644
--- a/daemons/ipa-slapi-plugins/libotp/libotp.c
+++ b/daemons/ipa-slapi-plugins/libotp/libotp.c
@@ -67,7 +67,7 @@ struct otptoken {
     union {
         struct {
             uint64_t watermark;
-            unsigned int step;
+            int step; /* Seconds. */
             int offset;
         } totp;
         struct {
@@ -316,7 +316,7 @@ static struct otptoken *otptoken_new(Slapi_ComponentId *id, Slapi_Entry *entry)
 
         /* Get step. */
         token->totp.step = slapi_entry_attr_get_uint(entry, T("timeStep"));
-        if (token->totp.step == 0)
+        if (token->totp.step < 5)
             token->totp.step = IPA_OTP_DEFAULT_TOKEN_STEP;
         break;
     case OTPTOKEN_HOTP:
@@ -450,41 +450,11 @@ struct otptoken **otptoken_find(Slapi_ComponentId *id, const char *user_dn,
     return find(id, user_dn, token_dn, actfilt, filter);
 }
 
-int otptoken_get_digits(struct otptoken *token)
-{
-    return token == NULL ? 0 : token->token.digits;
-}
-
 const Slapi_DN *otptoken_get_sdn(struct otptoken *token)
 {
     return token->sdn;
 }
 
-static bool otptoken_validate(struct otptoken *token, size_t steps,
-                              uint32_t code)
-{
-    time_t now = 0;
-
-    if (token == NULL)
-        return false;
-
-    /* We only need the local time for time-based tokens. */
-    if (token->type == OTPTOKEN_TOTP && time(&now) == (time_t) -1)
-        return false;
-
-    for (int i = 0; i <= steps; i++) {
-        /* Validate the positive step. */
-        if (validate(token, now, i, code, NULL))
-            return true;
-
-        /* Validate the negative step. */
-        if (validate(token, now, 0 - i, code, NULL))
-            return true;
-    }
-
-    return false;
-}
-
 
 /*
  *  Convert code berval to decimal.
@@ -493,45 +463,44 @@ static bool otptoken_validate(struct otptoken *token, size_t steps,
  *    1. If we have leading zeros, atol() fails.
  *    2. Neither support limiting conversion by length.
  */
-static bool bvtod(const struct berval *code, uint32_t *out)
+static bool bvtod(const struct berval *code, int digits, uint32_t *out)
 {
     *out = 0;
 
-    for (ber_len_t i = 0; i < code->bv_len; i++) {
+    if (code == NULL || digits <= 0 || code->bv_len < digits)
+        return false;
+
+    for (ber_len_t i = code->bv_len - digits; i < code->bv_len; i++) {
         if (code->bv_val[i] < '0' || code->bv_val[i] > '9')
             return false;
         *out *= 10;
         *out += code->bv_val[i] - '0';
     }
 
-    return code->bv_len != 0;
+    return true;
 }
 
-bool otptoken_validate_berval(struct otptoken *token, size_t steps,
-                              const struct berval *code, bool tail)
+static bool valid_step(const struct otptoken *token,
+                       const struct otptoken_window *window,
+                       int step)
 {
-    struct berval tmp;
-    uint32_t otp;
+    switch (token->type) {
+    case OTPTOKEN_HOTP:
+        return step < window->hotp;
 
-    if (token == NULL || code == NULL)
-        return false;
-    tmp = *code;
+    case OTPTOKEN_TOTP:
+        return window->totp - step * token->totp.step >= 0;
 
-    if (tmp.bv_len < token->token.digits)
+    default:
         return false;
-
-    if (tail)
-        tmp.bv_val = &tmp.bv_val[tmp.bv_len - token->token.digits];
-    tmp.bv_len = token->token.digits;
-
-    if (!bvtod(&tmp, &otp))
-        return false;
-
-    return otptoken_validate(token, steps, otp);
+    }
 }
 
-static bool otptoken_sync(struct otptoken * const *tokens, size_t steps,
-                          uint32_t first_code, uint32_t second_code)
+bool otptoken_validate_berval(struct otptoken * const *tokens,
+                              const struct otptoken_window *window,
+                              struct berval *first_code,
+                              struct berval *second_code,
+                              bool strip)
 {
     time_t now = 0;
 
@@ -541,33 +510,43 @@ static bool otptoken_sync(struct otptoken * const *tokens, size_t steps,
     if (time(&now) == (time_t) -1)
         return false;
 
-    for (int i = 0; i <= steps; i++) {
+    for (int i = 0, cnt = 1; cnt != 0; i++) {
+        cnt = 0;
         for (int j = 0; tokens[j] != NULL; j++) {
-            /* Validate the positive step. */
-            if (validate(tokens[j], now, i, first_code, &second_code))
-                return true;
+            uint32_t *secondp = NULL;
+            uint32_t second;
+            uint32_t first;
 
-            /* Validate the negative step. */
-            if (validate(tokens[j], now, 0 - i, first_code, &second_code))
-                return true;
+            if (!valid_step(tokens[j], window, i))
+                continue;
+            cnt++;
+
+            if (!bvtod(first_code, tokens[j]->token.digits, &first))
+                return false;
+
+            if (second_code != NULL) {
+                secondp = &second;
+                if (!bvtod(second_code, tokens[j]->token.digits, secondp))
+                    return false;
+            }
+
+            /* Validate the positive/negative steps. */
+            if (!validate(tokens[j], now, i, first, secondp) &&
+                !validate(tokens[j], now, 0 - i, first, secondp))
+                continue;
+
+            if (strip) {
+                first_code->bv_len -= tokens[j]->token.digits;
+                first_code->bv_val[first_code->bv_len] = '\0';
+                if (second_code != NULL) {
+                    second_code->bv_len -= tokens[j]->token.digits;
+                    second_code->bv_val[second_code->bv_len] = '\0';
+                }
+            }
+
+            return true;
         }
     }
 
     return false;
 }
-
-bool otptoken_sync_berval(struct otptoken * const *tokens, size_t steps,
-                          const struct berval *first_code,
-                          const struct berval *second_code)
-{
-    uint32_t second = 0;
-    uint32_t first = 0;
-
-    if (!bvtod(first_code, &first))
-        return false;
-
-    if (!bvtod(second_code, &second))
-        return false;
-
-    return otptoken_sync(tokens, steps, first, second);
-}
diff --git a/daemons/ipa-slapi-plugins/libotp/libotp.h b/daemons/ipa-slapi-plugins/libotp/libotp.h
index 24915f8667dfa13ffb1ae47d223df95d4622012e..bafe68816447e41ffc70acafdd676402d6d82410 100644
--- a/daemons/ipa-slapi-plugins/libotp/libotp.h
+++ b/daemons/ipa-slapi-plugins/libotp/libotp.h
@@ -49,6 +49,10 @@
 #include <stdlib.h>
 
 struct otptoken;
+struct otptoken_window {
+    int32_t hotp; /* Counts */
+    int32_t totp; /* Seconds */
+};
 
 /* Frees the token array. */
 void otptoken_free_array(struct otptoken **tokens);
@@ -74,20 +78,22 @@ struct otptoken **otptoken_find(Slapi_ComponentId *id, const char *user_dn,
                                 const char *token_dn, bool active,
                                 const char *filter);
 
-/* Get the length of the token code. */
-int otptoken_get_digits(struct otptoken *token);
-
 /* Get the SDN of the token. */
 const Slapi_DN *otptoken_get_sdn(struct otptoken *token);
 
-/* Validate the token code within a range of steps. If tail is true,
- * it will be assumed that the token is specified at the end of the string. */
-bool otptoken_validate_berval(struct otptoken *token, size_t steps,
-                              const struct berval *code, bool tail);
-
-/* Synchronize the token within a range of steps. */
-bool otptoken_sync_berval(struct otptoken * const *tokens, size_t steps,
-                          const struct berval *first_code,
-                          const struct berval *second_code);
+/* Perform OTP authentication.
+ *
+ * The first_code is required. The second_code is optional (may be NULL).
+ *
+ * If strip is true and a token value is successfully found, it will be
+ * stripped from first_code/second_code.
+ *
+ * Returns true if and only if a token was validated.
+ */
+bool otptoken_validate_berval(struct otptoken * const *tokens,
+                              const struct otptoken_window *window,
+                              struct berval *first_code,
+                              struct berval *second_code,
+                              bool strip);
 
 #endif /* LIBOTP_H_ */
diff --git a/install/share/60ipaconfig.ldif b/install/share/60ipaconfig.ldif
index 692690714aabad6b5e34328fe24cfab62bc0d70c..d3c2b5b32f44136a91b2cd6163b81b34f8914859 100644
--- a/install/share/60ipaconfig.ldif
+++ b/install/share/60ipaconfig.ldif
@@ -43,11 +43,15 @@ attributeTypes: ( 2.16.840.1.113730.3.8.3.23 NAME 'ipaCertificateSubjectBase' SY
 attributeTypes: (2.16.840.1.113730.3.8.3.16 NAME 'ipaConfigString' DESC 'Generic configuration stirng' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
 attributeTypes: ( 2.16.840.1.113730.3.8.3.26 NAME 'ipaSELinuxUserMapDefault' DESC 'Default SELinux user' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3')
 attributeTypes: ( 2.16.840.1.113730.3.8.3.27 NAME 'ipaSELinuxUserMapOrder' DESC 'Available SELinux user context ordering' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3')
+attributeTypes: ( 2.16.840.1.113730.3.8.11.66 NAME 'ipaHOTPAuthWindow' DESC 'HOTP Auth Window (max skip-ahead count for authentication)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
+attributeTypes: ( 2.16.840.1.113730.3.8.11.67 NAME 'ipaTOTPAuthWindow' DESC 'TOTP Auth Window (max allowed clock-skew for authentication)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
+attributeTypes: ( 2.16.840.1.113730.3.8.11.68 NAME 'ipaHOTPSyncWindow' DESC 'HOTP Sync Window (max skip-ahead count for syncing)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
+attributeTypes: ( 2.16.840.1.113730.3.8.11.69 NAME 'ipaTOTPSyncWindow' DESC 'TOTP Sync Window (max allowed clock-skew for syncing)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
 ###############################################
 ##
 ## ObjectClasses
 ##
 ## ipaGuiConfig - GUI config parameters objectclass
-objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain $ ipaMigrationEnabled $ ipaCertificateSubjectBase $ ipaSELinuxUserMapDefault $ ipaSELinuxUserMapOrder $ ipaKrbAuthzData ) )
+objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain $ ipaMigrationEnabled $ ipaCertificateSubjectBase $ ipaSELinuxUserMapDefault $ ipaSELinuxUserMapOrder $ ipaKrbAuthzData $ ipaTOTPAuthWindow $ ipaTOTPSyncWindow $ ipaHOTPAuthWindow $ ipaHOTPSyncWindow) )
 ## ipaConfigObject - Generic config strings object holder
 objectClasses: (2.16.840.1.113730.3.8.4.13 NAME 'ipaConfigObject' DESC 'generic config object for IPA' AUXILIARY MAY ( ipaConfigString ) X-ORIGIN 'IPA v2' )
diff --git a/install/ui/src/freeipa/serverconfig.js b/install/ui/src/freeipa/serverconfig.js
index d134c88824533ce83f8b0dc11ca3a059601b6f7d..f894341a8c235d8578c068537b499d4c68a798d9 100644
--- a/install/ui/src/freeipa/serverconfig.js
+++ b/install/ui/src/freeipa/serverconfig.js
@@ -123,6 +123,16 @@ return {
                             options: IPA.create_options(['MS-PAC', 'PAD', 'nfs:NONE'])
                         }
                     ]
+                },
+                {
+                    name: 'otp',
+                    label: '@i18n:objects.config.otp',
+                    fields: [
+                        'ipahotpauthwindow',
+                        'ipatotpauthwindow',
+                        'ipahotpsyncwindow',
+                        'ipatotpsyncwindow',
+                    ]
                 }
             ],
             needs_update: true
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index bbe334b7df774f1956a6dca98d5bb54049a7ee00..0f6347b6206d67cd8fd226e30a5d35861fc1a6b7 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -264,7 +264,8 @@
                             "search": "Search Options",
                             "selinux": "SELinux Options",
                             "service": "Service Options",
-                            "user": "User Options"
+                            "user": "User Options",
+                            "otp": "One Time Password Options"
                         },
                         "delegation": {},
                         "dnsconfig": {
diff --git a/install/updates/40-otp.update b/install/updates/40-otp.update
index 83808b7185d0aa9b14d89962cb33f75693ac07e3..17ecf4eee7851e60dd007194a3e781dc79328763 100644
--- a/install/updates/40-otp.update
+++ b/install/updates/40-otp.update
@@ -3,6 +3,12 @@ default: objectClass: nsContainer
 default: objectClass: top
 default: cn: otp
 
+dn: cn=ipaConfig,cn=etc,$SUFFIX
+addifnew: ipaHOTPAuthWindow: 10
+addifnew: ipaTOTPAuthWindow: 300
+addifnew: ipaHOTPSyncWindow: 100
+addifnew: ipaTOTPSyncWindow: 86400
+
 dn: $SUFFIX
 remove: aci:'(target = "ldap:///ipatokenuniqueid=*,cn=otp,$SUFFIX";)(targetfilter = "(objectClass=ipaToken)")(version 3.0; acl "Users can create and delete tokens"; allow (add, delete) userattr = "ipatokenOwner#SELFDN";)'
 remove: aci:'(targetfilter = "(objectClass=ipaToken)")(targetattrs = "objectclass || ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can read basic token info"; allow (read, search, compare) userattr = "ipatokenOwner#USERDN";)'
diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py
index 077ef2c42ea4b2c7a2c5801a6558408326d2c757..e661e2ed05a9f8937ed6e1f6858ece08ebe283d0 100644
--- a/ipalib/plugins/config.py
+++ b/ipalib/plugins/config.py
@@ -96,7 +96,8 @@ class config(LDAPObject):
         'ipamigrationenabled', 'ipacertificatesubjectbase',
         'ipapwdexpadvnotify', 'ipaselinuxusermaporder',
         'ipaselinuxusermapdefault', 'ipaconfigstring', 'ipakrbauthzdata',
-        'ipauserauthtype'
+        'ipauserauthtype', 'ipahotpauthwindow', 'ipatotpauthwindow',
+        'ipahotpsyncwindow', 'ipatotpsyncwindow',
     ]
     container_dn = DN(('cn', 'ipaconfig'), ('cn', 'etc'))
     permission_filter_objectclasses = ['ipaguiconfig']
@@ -231,6 +232,34 @@ class config(LDAPObject):
             values=(u'password', u'radius', u'otp'),
             csv=True,
         ),
+        Int('ipahotpauthwindow?',
+            cli_name='hotp_auth_window',
+            label=_('HOTP Authentication Window (counts)'),
+            doc=_('Maximum counter skip-ahead for HOTP authentication'),
+            minvalue=1,
+            maxvalue=1000,
+        ),
+        Int('ipatotpauthwindow?',
+            cli_name='totp_auth_window',
+            label=_('TOTP Authentication Window (seconds)'),
+            doc=_('Maximum clock-skew (seconds) for TOTP authentication'),
+            minvalue=30,
+            maxvalue=2678400,
+        ),
+        Int('ipahotpsyncwindow?',
+            cli_name='hotp_sync_window',
+            label=_('HOTP Synchronization Window (counts)'),
+            doc=_('Maximum counter skip-ahead for HOTP synchronization'),
+            minvalue=1,
+            maxvalue=1000,
+        ),
+        Int('ipatotpsyncwindow?',
+            cli_name='totp_sync_window',
+            label=_('TOTP Synchronization Window (seconds)'),
+            doc=_('Maximum clock-skew (seconds) for TOTP synchronization'),
+            minvalue=30,
+            maxvalue=2678400,
+        ),
     )
 
     def get_dn(self, *keys, **kwargs):
diff --git a/ipalib/plugins/internal.py b/ipalib/plugins/internal.py
index b85f2d077110128963e26ccf0f43e21141c46f4a..e2ab0f0b55b115d55497c1c68141fc0c88e54463 100644
--- a/ipalib/plugins/internal.py
+++ b/ipalib/plugins/internal.py
@@ -408,6 +408,7 @@ class i18n_messages(Command):
                 "selinux": _("SELinux Options"),
                 "service": _("Service Options"),
                 "user": _("User Options"),
+                "otp": _("One Time Password Options"),
             },
             "delegation": {
             },
-- 
2.1.0

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to