On Thu, 2014-12-04 at 14:56 +0100, Petr Vobornik wrote:
> On 2.12.2014 20:57, Nathaniel McCallum wrote:
> > The attached patches I think have a much better overall aesthetic. Now
> > patch 0004 introduces only two new commands:
> > * otpconfig-mod
> > * otpconfig-show
> >
> > Under the covers, a single configuration entity is used:
> > * cn=otp,cn=etc,$SUFFIX
> >
> > Other than these small changes, there are no changes to patch 0004. I
> > have not tested the latest changes, however, due to an unrelated build
> > issue I'm working on.
> >
> > Patch 0005 introduces an umbrella help topic for all OTP related
> > commands (currently: otpconfig, otptoken, otptoken-yubikey).
> >
> > Nathaniel
> >
> 
> Works fine.
> 
> python part of 0004: ACK, but VERSION needs to be updated before push
> 0005: ACK

Fixed and rebased. Patch numbers have changed:
0004 => 0001
0005 => 0002

> One question before push: For per-token configuration, do you intent to 
> extend each token, regardless of type, by 'ipatokenOTPConfig' object 
> class? I.e. to have config attributes for both types? Or do you plan to 
> have special object classes for each token type as we now have for tokens?

I would probably just add the TOTP options to the ipatokenTOTP object
class as MAY. Same for HOTP. The attributes were designed to look like
the other token-type-specific attributes.

I think we are just waiting on Thierry's review of the C code. :)

Nathaniel
From 4be7cd92c19cee4ca8861a520fa490201864ae6a Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Tue, 11 Nov 2014 14:41:42 -0500
Subject: [PATCH 1/2] Make token auth and sync windows configurable

This introduces two new CLI commands:
  * otpconfig-show
  * otpconfig-mod

https://fedorahosted.org/freeipa/ticket/4511
---
 ACI.txt                                           |   2 +
 API.txt                                           |  25 ++++
 VERSION                                           |   4 +-
 daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c |  77 ++++--------
 daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c |   4 +-
 daemons/ipa-slapi-plugins/libotp/otp_config.c     |  89 +++++++++++++-
 daemons/ipa-slapi-plugins/libotp/otp_config.h     |  17 +++
 daemons/ipa-slapi-plugins/libotp/otp_token.c      | 139 +++++++++-------------
 daemons/ipa-slapi-plugins/libotp/otp_token.h      |  26 ++--
 install/share/70ipaotp.ldif                       |   5 +
 install/updates/40-otp.update                     |   9 ++
 ipalib/plugins/otpconfig.py                       | 119 ++++++++++++++++++
 12 files changed, 362 insertions(+), 154 deletions(-)
 create mode 100644 ipalib/plugins/otpconfig.py

diff --git a/ACI.txt b/ACI.txt
index 6680f658ee1aa0f961b2681f700557ce6b9238f8..e4b4032d4e021bed6ade8a6cb66e39621bedfb85 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -154,6 +154,8 @@ dn: cn=ng,cn=alt,dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || description || entryusn || hostcategory || ipaenabledflag || ipauniqueid || modifytimestamp || nisdomainname || objectclass || usercategory")(targetfilter = "(objectclass=ipanisnetgroup)")(version 3.0;acl "permission:System: Read Netgroups";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=ng,cn=alt,dc=ipa,dc=example
 aci: (targetfilter = "(objectclass=ipanisnetgroup)")(version 3.0;acl "permission:System: Remove Netgroups";allow (delete) groupdn = "ldap:///cn=System: Remove Netgroups,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=otp,cn=etc,dc=ipa,dc=example
+aci: (targetattr = "cn || ipatokenhotpauthwindow || ipatokenhotpsyncwindow || ipatokentotpauthwindow || ipatokentotpsyncwindow")(targetfilter = "(objectclass=ipatokenotpconfig)")(version 3.0;acl "permission:System: Read OTP Configuration";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=permissions,cn=pbac,dc=ipa,dc=example
 aci: (targetattr = "member")(targetfilter = "(objectclass=ipapermission)")(version 3.0;acl "permission:System: Modify Privilege Membership";allow (write) groupdn = "ldap:///cn=System: Modify Privilege Membership,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index e9768bf1e87d6679c439b98ed696b720937099d2..e5e668b0a79a50ea5c2bf9b6d2ae71fb3dbd13f3 100644
--- a/API.txt
+++ b/API.txt
@@ -2599,6 +2599,31 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: otpconfig_mod
+args: 0,11,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')
+option: Int('ipatokenhotpauthwindow', attribute=True, autofill=False, cli_name='hotp_auth_window', minvalue=1, multivalue=False, required=False)
+option: Int('ipatokenhotpsyncwindow', attribute=True, autofill=False, cli_name='hotp_sync_window', minvalue=1, multivalue=False, required=False)
+option: Int('ipatokentotpauthwindow', attribute=True, autofill=False, cli_name='totp_auth_window', minvalue=5, multivalue=False, required=False)
+option: Int('ipatokentotpsyncwindow', attribute=True, autofill=False, cli_name='totp_sync_window', minvalue=5, multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: otpconfig_show
+args: 0,4,3
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
 command: otptoken_add
 args: 1,23,3
 arg: Str('ipatokenuniqueid', attribute=True, cli_name='id', multivalue=False, primary_key=True, required=False)
diff --git a/VERSION b/VERSION
index deabe766d72b3da50d87de598cc0a43af8937480..d9e804a1d3e7ce0f079e900586889d684d03a8db 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=110
-# Last change: pvoborni - allow to retrieve keytab by hosts
+IPA_API_VERSION_MINOR=111
+# Last change: npmccallum - configurable token windows
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
index 96c55f39ba2a9dc1e9fc80d5f7d46787803ece47..84eff17013d2742d1b5e5c4ea5f4e22ee290d785 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
@@ -68,8 +68,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;
@@ -1113,8 +1111,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.
@@ -1123,53 +1121,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 otp_token **tokens = NULL;
-    bool success = false;
-
-    /* Find all of the user's active tokens. */
-    tokens = otp_token_find(otp_config, 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 = otp_token_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 -= otp_token_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(otp_token_get_sdn(tokens[i])));
-    }
-
-    otp_token_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)
 {
@@ -1189,10 +1140,32 @@ static bool ipapwd_pre_bind_otp(const char *bind_dn, Slapi_Entry *entry,
      */
 
     if (auth_types & OTP_CONFIG_AUTH_TYPE_OTP) {
+        struct otp_token **tokens = NULL;
+
         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 = otp_token_find(otp_config, 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) {
+            otp_token_free_array(tokens);
             return true;
+        }
+
+        if (otp_token_validate_berval(tokens, creds, NULL)) {
+            otp_token_free_array(tokens);
+            return true;
+        }
+
+        otp_token_free_array(tokens);
     }
 
     return auth_types & OTP_CONFIG_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 0aef438023e7f23d7219273e9f5efd5572e73c3f..3a31529f7a69c0bb24a1cc845fa4f9da981be138 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c
@@ -40,8 +40,6 @@
 #include "../libotp/otp_token.h"
 #include "syncreq.h"
 
-#define OTP_SYNC_MAX_STEPS 25
-
 bool sync_request_present(Slapi_PBlock *pb)
 {
     LDAPControl **controls = NULL;
@@ -92,7 +90,7 @@ bool sync_request_handle(const struct otp_config *cfg, Slapi_PBlock *pb,
         if (ber_scanf(ber, "}") != LBER_ERROR) {
             tokens = otp_token_find(cfg, user_dn, token_dn, true, NULL);
             if (tokens != NULL) {
-                success = otp_token_sync_berval(tokens, OTP_SYNC_MAX_STEPS, first, second);
+                success = otp_token_validate_berval(tokens, first, second);
                 otp_token_free_array(tokens);
             }
         }
diff --git a/daemons/ipa-slapi-plugins/libotp/otp_config.c b/daemons/ipa-slapi-plugins/libotp/otp_config.c
index 1b7c1e658f126e3d1e8eabd129bb69dc5c4ce970..ac2cfc72aa9f72af8eb5b5c565650325ac8bf714 100644
--- a/daemons/ipa-slapi-plugins/libotp/otp_config.c
+++ b/daemons/ipa-slapi-plugins/libotp/otp_config.c
@@ -105,6 +105,17 @@ static uint32_t entry_to_authtypes(Slapi_Entry *e, const char *attr)
     return types;
 }
 
+static uint32_t entry_to_window(Slapi_Entry *e, const char *attr)
+{
+    long long val;
+
+    if (e == NULL)
+        return 0;
+
+    val = slapi_entry_attr_get_longlong(e, attr);
+    return val > 0 ? val : 0;
+}
+
 static const struct spec authtypes = {
     entry_to_authtypes,
     "cn=ipaConfig,cn=etc,%s",
@@ -112,6 +123,34 @@ static const struct spec authtypes = {
     OTP_CONFIG_AUTH_TYPE_PASSWORD
 };
 
+static const struct spec totp_auth_window = {
+    entry_to_window,
+    "cn=otp,cn=etc,%s",
+    "ipatokenTOTPauthWindow",
+    300
+};
+
+static const struct spec totp_sync_window = {
+    entry_to_window,
+    "cn=otp,cn=etc,%s",
+    "ipatokenTOTPsyncWindow",
+    86400
+};
+
+static const struct spec hotp_auth_window = {
+    entry_to_window,
+    "cn=otp,cn=etc,%s",
+    "ipatokenHOTPauthWindow",
+    10
+};
+
+static const struct spec hotp_sync_window = {
+    entry_to_window,
+    "cn=otp,cn=etc,%s",
+    "ipatokenHOTPsyncWindow",
+    100
+};
+
 static Slapi_DN *make_sdn(const char *prefix, const Slapi_DN *suffix)
 {
     char *dn = slapi_ch_smprintf(prefix, slapi_sdn_get_dn(suffix));
@@ -126,10 +165,14 @@ static uint32_t find_value(const struct otp_config *cfg,
 
     sdn = make_sdn(spec->prefix, suffix);
     for (struct record *rec = cfg->records; rec != NULL; rec = rec->next) {
-        if (rec->spec == spec) {
-            value = PR_ATOMIC_ADD(&rec->value, 0);
-            break;
-        }
+        if (rec->spec != spec)
+            continue;
+
+        if (slapi_sdn_compare(sdn, rec->sdn) != 0)
+            continue;
+
+        value = PR_ATOMIC_ADD(&rec->value, 0);
+        break;
     }
 
     slapi_sdn_free(&sdn);
@@ -162,6 +205,10 @@ struct otp_config *otp_config_init(Slapi_ComponentId *plugin_id)
 {
     static const struct spec *specs[] = {
         &authtypes,
+        &totp_auth_window,
+        &totp_sync_window,
+        &hotp_auth_window,
+        &hotp_sync_window,
         NULL
     };
 
@@ -272,3 +319,37 @@ uint32_t otp_config_auth_types(const struct otp_config *cfg,
 
     return OTP_CONFIG_AUTH_TYPE_PASSWORD;
 }
+
+struct otp_config_window
+otp_config_window(const struct otp_config *cfg, Slapi_Entry *token_entry)
+{
+    const struct spec *auth = NULL, *sync = NULL;
+    struct otp_config_window wndw = { 0, 0 };
+    const Slapi_DN *sfx;
+    char **clses;
+
+    sfx = slapi_get_suffix_by_dn(slapi_entry_get_sdn_const(token_entry));
+
+    clses = slapi_entry_attr_get_charray(token_entry, SLAPI_ATTR_OBJECTCLASS);
+    for (size_t i = 0; clses != NULL && clses[i] != NULL; i++) {
+        if (strcasecmp(clses[i], "ipatokenTOTP") == 0) {
+            auth = &totp_auth_window;
+            sync = &totp_sync_window;
+            break;
+        }
+
+        if (strcasecmp(clses[i], "ipatokenHOTP") == 0) {
+            auth = &hotp_auth_window;
+            sync = &hotp_sync_window;
+            break;
+        }
+    }
+    slapi_ch_array_free(clses);
+
+    if (auth == NULL || sync == NULL)
+        return wndw;
+
+    wndw.auth = find_value(cfg, sfx, auth);
+    wndw.sync = find_value(cfg, sfx, sync);
+    return wndw;
+}
diff --git a/daemons/ipa-slapi-plugins/libotp/otp_config.h b/daemons/ipa-slapi-plugins/libotp/otp_config.h
index bfd514bd542b7d707e9eab4a9cdf31a4f6839ae5..b7f14fafacbffe2472530505bcd59b8221862c7b 100644
--- a/daemons/ipa-slapi-plugins/libotp/otp_config.h
+++ b/daemons/ipa-slapi-plugins/libotp/otp_config.h
@@ -49,6 +49,11 @@
 
 struct otp_config;
 
+struct otp_config_window {
+    uint32_t auth;
+    uint32_t sync;
+};
+
 struct otp_config *otp_config_init(Slapi_ComponentId *plugin_id);
 
 void otp_config_fini(struct otp_config **cfg);
@@ -63,3 +68,15 @@ Slapi_ComponentId *otp_config_plugin_id(const struct otp_config *cfg);
  */
 uint32_t otp_config_auth_types(const struct otp_config *cfg,
                                Slapi_Entry *user_entry);
+
+/* Gets the window sizes for a token.
+ *
+ * The entry should be queried for the following attributes:
+ *   objectClass
+ *   ipatokenTOTPauthWindow
+ *   ipatokenTOTPsyncWindow
+ *   ipatokenHOTPauthWindow
+ *   ipatokenHOTPsyncWindow
+ */
+struct otp_config_window otp_config_window(const struct otp_config *cfg,
+                                           Slapi_Entry *token_entry);
diff --git a/daemons/ipa-slapi-plugins/libotp/otp_token.c b/daemons/ipa-slapi-plugins/libotp/otp_token.c
index eef07268507444897d50509a54f2877866b9c07a..bc6acc42c8a62dce2f8c715099786a5c0fcc8e07 100644
--- a/daemons/ipa-slapi-plugins/libotp/otp_token.c
+++ b/daemons/ipa-slapi-plugins/libotp/otp_token.c
@@ -38,6 +38,7 @@
  * END COPYRIGHT BLOCK **/
 
 #include "otp_token.h"
+#include "otp_config.h"
 #include "hotp.h"
 
 #include <time.h>
@@ -63,10 +64,11 @@ struct otp_token {
     Slapi_DN *sdn;
     struct hotp_token token;
     enum type type;
+    struct otp_config_window window;
     union {
         struct {
             uint64_t watermark;
-            unsigned int step;
+            int step; /* Seconds. */
             int offset;
         } totp;
         struct {
@@ -247,6 +249,7 @@ static struct otp_token *otp_token_new(const struct otp_config *cfg,
     if (token == NULL)
         return NULL;
     token->cfg = cfg;
+    token->window = otp_config_window(cfg, entry);
 
     /* Get the token type. */
     vals = slapi_entry_attr_get_charray(entry, "objectClass");
@@ -300,7 +303,7 @@ static struct otp_token *otp_token_new(const struct otp_config *cfg,
 
         /* 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 TYPE_HOTP:
@@ -431,42 +434,11 @@ struct otp_token **otp_token_find(const struct otp_config *cfg,
     return find(cfg, user_dn, token_dn, actfilt, filter);
 }
 
-int otp_token_get_digits(struct otp_token *token)
-{
-    return token == NULL ? 0 : token->token.digits;
-}
-
 const Slapi_DN *otp_token_get_sdn(struct otp_token *token)
 {
     return token->sdn;
 }
 
-static bool otp_token_validate(struct otp_token *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 == TYPE_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.
  *
@@ -474,45 +446,40 @@ static bool otp_token_validate(struct otp_token *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 otp_token_validate_berval(struct otp_token *token, size_t steps,
-                               const struct berval *code, bool tail)
+static bool step_is_valid(struct otp_token *token, bool sync, uint32_t i)
 {
-    struct berval tmp;
-    uint32_t otp;
-
-    if (token == NULL || code == NULL)
-        return false;
-    tmp = *code;
-
-    if (tmp.bv_len < token->token.digits)
+    uint32_t window = sync ? token->window.sync : token->window.auth;
+
+    switch (token->type) {
+    case TYPE_TOTP:
+        return i * token->totp.step < window;
+    case TYPE_HOTP:
+        return i < window;
+    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 otp_token_validate(token, steps, otp);
+    }
 }
 
-static bool otp_token_sync(struct otp_token * const *tokens, size_t steps,
-                           uint32_t first_code, uint32_t second_code)
+bool otp_token_validate_berval(struct otp_token * const *tokens,
+                               struct berval *first_code,
+                               struct berval *second_code)
 {
     time_t now = 0;
 
@@ -522,33 +489,45 @@ static bool otp_token_sync(struct otp_token * const *tokens, size_t steps,
     if (time(&now) == (time_t) -1)
         return false;
 
-    for (int i = 0; i <= steps; i++) {
+    for (uint32_t 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;
+            /* Don't validate beyond the specified window. */
+            if (!step_is_valid(tokens[j], second_code != NULL, i))
+                continue;
+            cnt++;
+
+            /* Parse the first code. */
+            if (!bvtod(first_code, tokens[j]->token.digits, &first))
+                continue;
+
+            /* Parse the second code. */
+            if (second_code != NULL) {
+                secondp = &second;
+                if (!bvtod(second_code, tokens[j]->token.digits, secondp))
+                    continue;
+            }
+
+            /* Validate the positive/negative steps. */
+            if (!validate(tokens[j], now, i, first, secondp) &&
+                !validate(tokens[j], now, 0 - i, first, secondp))
+                continue;
+
+            /* Codes validated; 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 otp_token_sync_berval(struct otp_token * 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 otp_token_sync(tokens, steps, first, second);
-}
diff --git a/daemons/ipa-slapi-plugins/libotp/otp_token.h b/daemons/ipa-slapi-plugins/libotp/otp_token.h
index 4b159077d933555d18e804174e29e22f1e8f0110..4cef9a63b379c7179fb1316a01f03122d52994ec 100644
--- a/daemons/ipa-slapi-plugins/libotp/otp_token.h
+++ b/daemons/ipa-slapi-plugins/libotp/otp_token.h
@@ -70,19 +70,19 @@ struct otp_token **otp_token_find(const struct otp_config *cfg,
                                   const char *user_dn, const char *token_dn,
                                   bool active, const char *filter);
 
-/* Get the length of the token code. */
-int otp_token_get_digits(struct otp_token *token);
-
 /* Get the SDN of the token. */
 const Slapi_DN *otp_token_get_sdn(struct otp_token *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 otp_token_validate_berval(struct otp_token *token, size_t steps,
-                               const struct berval *code, bool tail);
-
-/* Synchronize the token within a range of steps. */
-bool otp_token_sync_berval(struct otp_token * const *tokens, size_t steps,
-                           const struct berval *first_code,
-                           const struct berval *second_code);
-
+/* Perform OTP authentication.
+ *
+ * If only the first code is specified, validation will be performed and the
+ * validated token will be stripped.
+ *
+ * If both codes are specified, synchronization will be performed and the
+ * validated tokens will be stripped.
+ *
+ * Returns true if and only if all specified tokens were validated.
+ */
+bool otp_token_validate_berval(struct otp_token * const *tokens,
+                               struct berval *first_code,
+                               struct berval *second_code);
diff --git a/install/share/70ipaotp.ldif b/install/share/70ipaotp.ldif
index bc95556682ef65ba375aa2f3cab6f53621641b3f..b35ab6235e9661e060cac5bb75ce8b96ce96fb25 100644
--- a/install/share/70ipaotp.ldif
+++ b/install/share/70ipaotp.ldif
@@ -24,8 +24,13 @@ attributeTypes: (2.16.840.1.113730.3.8.16.1.19 NAME 'ipatokenRadiusRetries' DESC
 attributeTypes: (2.16.840.1.113730.3.8.16.1.20 NAME 'ipatokenUserMapAttribute' DESC 'Attribute to map from the user entry for RADIUS server authentication' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
 attributeTypes: (2.16.840.1.113730.3.8.16.1.21 NAME 'ipatokenHOTPcounter' DESC 'HOTP counter' 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.16.1.22 NAME 'ipatokenTOTPwatermark' DESC 'TOTP watermark' 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.16.1.23 NAME 'ipatokenTOTPauthWindow' DESC 'TOTP Auth Window (maximum authentication variance in seconds)' 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.16.1.24 NAME 'ipatokenTOTPsyncWindow' DESC 'TOTP Sync Window (maximum synchronization variance in seconds)' 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.16.1.25 NAME 'ipatokenHOTPauthWindow' DESC 'HOTP Auth Window (maximum authentication skip-ahead)' 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.16.1.26 NAME 'ipatokenHOTPsyncWindow' DESC 'HOTP Sync Window (maximum synchronization skip-ahead)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
 objectClasses:  (2.16.840.1.113730.3.8.16.2.1  NAME 'ipaToken' SUP top ABSTRACT DESC 'Abstract token class for tokens' MUST (ipatokenUniqueID) MAY (description $ managedBy $ ipatokenOwner $ ipatokenDisabled $ ipatokenNotBefore $ ipatokenNotAfter $ ipatokenVendor $ ipatokenModel $ ipatokenSerial) X-ORIGIN 'IPA OTP')
 objectClasses:  (2.16.840.1.113730.3.8.16.2.2  NAME 'ipatokenTOTP' SUP ipaToken STRUCTURAL DESC 'TOTP Token Type' MUST (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenTOTPclockOffset $ ipatokenTOTPtimeStep) MAY (ipatokenTOTPwatermark) X-ORIGIN 'IPA OTP')
 objectClasses:  (2.16.840.1.113730.3.8.16.2.3  NAME 'ipatokenRadiusProxyUser' SUP top AUXILIARY DESC 'Radius Proxy User' MAY (ipatokenRadiusConfigLink $ ipatokenRadiusUserName) X-ORIGIN 'IPA OTP')
 objectClasses:  (2.16.840.1.113730.3.8.16.2.4  NAME 'ipatokenRadiusConfiguration' SUP top STRUCTURAL DESC 'Proxy Radius Configuration' MUST (cn $ ipatokenRadiusServer $ ipatokenRadiusSecret) MAY (description $ ipatokenRadiusTimeout $ ipatokenRadiusRetries $ ipatokenUserMapAttribute) X-ORIGIN 'IPA OTP')
 objectClasses:  (2.16.840.1.113730.3.8.16.2.5  NAME 'ipatokenHOTP' SUP ipaToken STRUCTURAL DESC 'HOTP Token Type' MUST (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenHOTPcounter) X-ORIGIN 'IPA OTP')
+objectClasses:  (2.16.840.1.113730.3.8.16.2.6  NAME 'ipatokenOTPConfig' SUP top STRUCTURAL DESC 'OTP Global Configuration' MUST (cn) MAY (ipatokenTOTPauthWindow $ ipatokenTOTPsyncWindow $ ipatokenHOTPauthWindow $ ipatokenHOTPsyncWindow) X-ORIGIN 'IPA OTP')
diff --git a/install/updates/40-otp.update b/install/updates/40-otp.update
index 83808b7185d0aa9b14d89962cb33f75693ac07e3..7cdff44ba8078140c5382a9fc11ca1aa9e0b1416 100644
--- a/install/updates/40-otp.update
+++ b/install/updates/40-otp.update
@@ -3,6 +3,15 @@ default: objectClass: nsContainer
 default: objectClass: top
 default: cn: otp
 
+dn: cn=otp,cn=etc,$SUFFIX
+default: objectClass: ipatokenOTPConfig
+default: objectClass: top
+default: cn: otp
+default: ipatokenTOTPauthWindow: 300
+default: ipatokenTOTPsyncWindow: 86400
+default: ipatokenHOTPauthWindow: 10
+default: ipatokenHOTPsyncWindow: 100
+
 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/otpconfig.py b/ipalib/plugins/otpconfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..440440dc9879123294d83bc6600917e72f6358ff
--- /dev/null
+++ b/ipalib/plugins/otpconfig.py
@@ -0,0 +1,119 @@
+# Authors:
+#   Nathaniel McCallum <npmccal...@redhat.com>
+#
+# Copyright (C) 2014  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+from ipalib import _, api, Int
+from ipalib.plugable import Registry
+from ipalib.plugins.baseldap import DN, LDAPObject, LDAPUpdate, LDAPRetrieve
+
+__doc__ = _("""
+OTP configuration
+
+Manage the default values that IPA uses for OTP tokens.
+
+EXAMPLES:
+
+ Show basic OTP configuration:
+   ipa otpconfig-show
+
+ Show all OTP configuration options:
+   ipa otpconfig-show --all
+
+ Change maximum TOTP authentication window to 10 minutes:
+   ipa otpconfig-mod --totp-auth-window=600
+
+ Change maximum TOTP synchronization window to 12 hours:
+   ipa otpconfig-mod --totp-sync-window=43200
+
+ Change maximum HOTP authentication window to 5:
+   ipa hotpconfig-mod --hotp-auth-window=5
+
+ Change maximum HOTP synchronization window to 50:
+   ipa hotpconfig-mod --hotp-sync-window=50
+""")
+
+register = Registry()
+
+
+@register()
+class otpconfig(LDAPObject):
+    object_name = _('OTP configuration options')
+    default_attributes = [
+        'ipatokentotpauthwindow',
+        'ipatokentotpsyncwindow',
+        'ipatokenhotpauthwindow',
+        'ipatokenhotpsyncwindow',
+    ]
+
+    container_dn = DN(('cn', 'otp'), ('cn', 'etc'))
+    permission_filter_objectclasses = ['ipatokenotpconfig']
+    managed_permissions = {
+        'System: Read OTP Configuration': {
+            'replaces_global_anonymous_aci': True,
+            'ipapermbindruletype': 'all',
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'ipatokentotpauthwindow', 'ipatokentotpsyncwindow',
+                'ipatokenhotpauthwindow', 'ipatokenhotpsyncwindow',
+                'cn',
+            },
+        },
+    }
+
+    label = _('OTP Configuration')
+    label_singular = _('OTP Configuration')
+
+    takes_params = (
+        Int('ipatokentotpauthwindow',
+            cli_name='totp_auth_window',
+            label=_('TOTP authentication Window'),
+            doc=_('TOTP authentication time variance (seconds)'),
+            minvalue=5,
+        ),
+        Int('ipatokentotpsyncwindow',
+            cli_name='totp_sync_window',
+            label=_('Synchronization Window'),
+            doc=_('TOTP synchronization time variance (seconds)'),
+            minvalue=5,
+        ),
+        Int('ipatokenhotpauthwindow',
+            cli_name='hotp_auth_window',
+            label=_('HOTP Authentication Window'),
+            doc=_('HOTP authentication skip-ahead'),
+            minvalue=1,
+        ),
+        Int('ipatokenhotpsyncwindow',
+            cli_name='hotp_sync_window',
+            label=_('HOTP Synchronization Window'),
+            doc=_('HOTP synchronization skip-ahead'),
+            minvalue=1,
+        ),
+    )
+
+    def get_dn(self, *keys, **kwargs):
+        return self.container_dn + api.env.basedn
+
+
+@register()
+class otpconfig_mod(LDAPUpdate):
+    __doc__ = _('Modify OTP configuration options.')
+
+
+@register()
+class otpconfig_show(LDAPRetrieve):
+    __doc__ = _('Show the current OTP configuration.')
-- 
2.1.0

From 800ab21b157796afe91da64662ea9d4d51251b1e Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Tue, 2 Dec 2014 14:43:27 -0500
Subject: [PATCH 2/2] Create an OTP help topic

This allows the various OTP related commands to be grouped together
in the IPA CLI documentation.
---
 ipalib/plugins/otpconfig.py        | 2 ++
 ipalib/plugins/otptoken.py         | 2 ++
 ipalib/plugins/otptoken_yubikey.py | 3 +++
 3 files changed, 7 insertions(+)

diff --git a/ipalib/plugins/otpconfig.py b/ipalib/plugins/otpconfig.py
index 440440dc9879123294d83bc6600917e72f6358ff..b69bfb27e5436dc2b3d8eb16dd30534d452aae87 100644
--- a/ipalib/plugins/otpconfig.py
+++ b/ipalib/plugins/otpconfig.py
@@ -49,6 +49,8 @@ EXAMPLES:
 
 register = Registry()
 
+topic = ('otp', _('One time password commands'))
+
 
 @register()
 class otpconfig(LDAPObject):
diff --git a/ipalib/plugins/otptoken.py b/ipalib/plugins/otptoken.py
index f0850854f98e84e44acdcef311225220ac0129a3..41a7f1087b783486704a066fe35e16a4db125bf2 100644
--- a/ipalib/plugins/otptoken.py
+++ b/ipalib/plugins/otptoken.py
@@ -61,6 +61,8 @@ EXAMPLES:
 
 register = Registry()
 
+topic = ('otp', _('One time password commands'))
+
 TOKEN_TYPES = {
     u'totp': ['ipatokentotpclockoffset', 'ipatokentotptimestep'],
     u'hotp': ['ipatokenhotpcounter']
diff --git a/ipalib/plugins/otptoken_yubikey.py b/ipalib/plugins/otptoken_yubikey.py
index 4c2594182c08a9de3f2f5861aac60b39a9ce022a..58fc18308f0cfe407881b5fbb5e653c5afbd0eba 100644
--- a/ipalib/plugins/otptoken_yubikey.py
+++ b/ipalib/plugins/otptoken_yubikey.py
@@ -44,6 +44,9 @@ EXAMPLES:
 
 register = Registry()
 
+topic = ('otp', _('One time password commands'))
+
+
 @register()
 class otptoken_add_yubikey(Command):
     __doc__ = _('Add a new YubiKey OTP token.')
-- 
2.1.0

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

Reply via email to