summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ACI.txt2
-rw-r--r--API.txt25
-rw-r--r--VERSION4
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c77
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c4
-rw-r--r--daemons/ipa-slapi-plugins/libotp/otp_config.c89
-rw-r--r--daemons/ipa-slapi-plugins/libotp/otp_config.h17
-rw-r--r--daemons/ipa-slapi-plugins/libotp/otp_token.c137
-rw-r--r--daemons/ipa-slapi-plugins/libotp/otp_token.h26
-rw-r--r--install/share/70ipaotp.ldif5
-rw-r--r--install/updates/40-otp.update9
-rw-r--r--ipalib/plugins/otpconfig.py119
12 files changed, 361 insertions, 153 deletions
diff --git a/ACI.txt b/ACI.txt
index 6680f658e..e4b4032d4 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 e9768bf1e..e5e668b0a 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 deabe766d..d9e804a1d 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 96c55f39b..84eff1701 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 0aef43802..3a31529f7 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 1b7c1e658..ac2cfc72a 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 bfd514bd5..b7f14fafa 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 eef072685..bc6acc42c 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)
- return false;
-
- if (tail)
- tmp.bv_val = &tmp.bv_val[tmp.bv_len - token->token.digits];
- tmp.bv_len = token->token.digits;
+ uint32_t window = sync ? token->window.sync : token->window.auth;
- if (!bvtod(&tmp, &otp))
+ switch (token->type) {
+ case TYPE_TOTP:
+ return i * token->totp.step < window;
+ case TYPE_HOTP:
+ return i < window;
+ default:
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;
+
+ /* 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';
+ }
- /* Validate the negative step. */
- if (validate(tokens[j], now, 0 - i, first_code, &second_code))
- return true;
+ 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 4b159077d..4cef9a63b 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 bc9555668..b35ab6235 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 83808b718..7cdff44ba 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 000000000..440440dc9
--- /dev/null
+++ b/ipalib/plugins/otpconfig.py
@@ -0,0 +1,119 @@
+# Authors:
+# Nathaniel McCallum <npmccallum@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.')