freeipa/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
Alexander Bokovoy b9a6027410 ipa-pwd-extop: use timegm() instead of mktime() to preserve timezone offset
"Kerberos principal expiration" is set in UTC and when server is in
different timezone, the time difference between timezone is respected by
the IPA server/client for Kerberos authentication.

The problem is due to mktime() assuming default time zone but since we
parse the time using Zulu (UTC+0) timezone, mktime() forces current time
zone offset added.

The method is using mktime() and comparing to the current time obtained
with time(NULL). According to its man page, mktime is considering the
time as local time:

   The mktime() function converts a broken-down time structure,  expressed
   as  local  time, to calendar time representation.

Instead mktime() we should use timegm(). The problem is that it is
non-standard GNU extension and it is recommended (in the man page for
timegm(3)) to avoid its use. An alternative is to set TZ=UTC, call
mktime(), unset TZ, but since we are running in a multi-threaded
environment this is problematic.

On the other hand, we already rely on GNU extensions and enable them
with -D_DEFAULT_SOURCE=1, so use of timegm() is enabled already.

The fix, therefore, is to use timegm() instead of mktime() in
daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c in two places where we
first do 'strptime()' with Zulu time zone (in ipapwd_pre_bind() and
ipapwd_write_krb_keys()).

Fixes: https://pagure.io/freeipa/issue/8362

Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-by: Simo Sorce <simo@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
2020-06-08 18:06:16 +02:00

1598 lines
53 KiB
C

/** BEGIN COPYRIGHT BLOCK
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, 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/>.
*
* Additional permission under GPLv3 section 7:
*
* In the following paragraph, "GPL" means the GNU General Public
* License, version 3 or any later version, and "Non-GPL Code" means
* code that is governed neither by the GPL nor a license
* compatible with the GPL.
*
* You may link the code of this Program with Non-GPL Code and convey
* linked combinations including the two, provided that such Non-GPL
* Code only links to the code of this Program through those well
* defined interfaces identified in the file named EXCEPTION found in
* the source code files (the "Approved Interfaces"). The files of
* Non-GPL Code may instantiate templates or use macros or inline
* functions from the Approved Interfaces without causing the resulting
* work to be covered by the GPL. Only the copyright holders of this
* Program may make changes or additions to the list of Approved
* Interfaces.
*
* Authors:
* Simo Sorce <ssorce@redhat.com>
*
* Copyright (C) 2007-2010 Red Hat, Inc.
* All rights reserved.
* END COPYRIGHT BLOCK **/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
/* strptime needs _XOPEN_SOURCE and endian.h needs __USE_BSD
* _GNU_SOURCE imply both, and we use it elsewhere, so use this */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirsrv/slapi-plugin.h>
#include <lber.h>
#include <time.h>
#include <endian.h>
#include "ipapwd.h"
#include "util.h"
#include "otpctrl.h"
#define IPAPWD_OP_NULL 0
#define IPAPWD_OP_ADD 1
#define IPAPWD_OP_MOD 2
extern Slapi_PluginDesc ipapwd_plugin_desc;
extern void *ipapwd_plugin_id;
extern const char *ipa_realm_tree;
struct otp_config *otp_config = NULL;
/* structure with information for each extension */
struct ipapwd_op_ext {
char *object_name; /* name of the object extended */
int object_type; /* handle to the extended object */
int handle; /* extension handle */
};
/*****************************************************************************
* pre/post operations to intercept writes to userPassword
****************************************************************************/
static struct ipapwd_op_ext ipapwd_op_ext_list;
static void *ipapwd_op_ext_constructor(void *object, void *parent)
{
struct ipapwd_operation *ext;
ext = (struct ipapwd_operation *)slapi_ch_calloc(1, sizeof(struct ipapwd_operation));
return ext;
}
static void ipapwd_op_ext_destructor(void *ext, void *object, void *parent)
{
struct ipapwd_operation *pwdop = (struct ipapwd_operation *)ext;
if (!pwdop)
return;
if (pwdop->pwd_op != IPAPWD_OP_NULL) {
slapi_ch_free_string(&(pwdop->pwdata.dn));
slapi_ch_free_string(&(pwdop->pwdata.password));
}
slapi_ch_free((void **)&pwdop);
}
int ipapwd_ext_init(void)
{
int ret;
ipapwd_op_ext_list.object_name = SLAPI_EXT_OPERATION;
ret = slapi_register_object_extension(IPAPWD_PLUGIN_NAME,
SLAPI_EXT_OPERATION,
ipapwd_op_ext_constructor,
ipapwd_op_ext_destructor,
&ipapwd_op_ext_list.object_type,
&ipapwd_op_ext_list.handle);
return ret;
}
static char *ipapwd_getIpaConfigAttr(const char *attr)
{
/* check if migrtion is enabled */
Slapi_Entry *entry = NULL;
const char *attrs_list[] = {attr, 0};
char *value = NULL;
char *dn = NULL;
Slapi_DN *sdn = NULL;
int ret;
dn = slapi_ch_smprintf("cn=ipaconfig,cn=etc,%s", ipa_realm_tree);
if (!dn) {
LOG_OOM();
goto done;
}
/* _byref() will take ownership of the dn */
sdn = slapi_sdn_new_dn_byref(dn);
ret = ipapwd_getEntry(sdn, &entry, (char **) attrs_list);
if (ret) {
LOG("failed to retrieve config entry: %s\n", slapi_sdn_get_dn(sdn));
goto done;
}
value = slapi_entry_attr_get_charptr(entry, attr);
done:
slapi_entry_free(entry);
slapi_sdn_free(&sdn);
return value;
}
static bool has_krbprincipalkey(Slapi_Entry *entry) {
int rc;
krb5_key_data *keys = NULL;
int num_keys = 0;
int mkvno = 0;
int hint;
Slapi_Attr *attr;
Slapi_Value *keys_value;
const struct berval *bval;
if (slapi_entry_attr_find(entry, "krbPrincipalKey", &attr)) {
return false;
}
/* It exists a krbPrincipalKey attribute checks it exists a valid value */
for (hint = slapi_attr_first_value(attr, &keys_value);
hint != -1; hint = slapi_attr_next_value(attr, hint, &keys_value)) {
bval = slapi_value_get_berval(keys_value);
if (NULL != bval && NULL != bval->bv_val) {
rc = ber_decode_krb5_key_data(discard_const(bval),
&mkvno, &num_keys, &keys);
if (rc || (num_keys <= 0)) {
/* this one is not valid, ignore it */
if (keys) {
ipa_krb5_free_key_data(keys, num_keys);
keys = NULL;
num_keys = 0;
}
} else {
/* It exists at least this one that is valid, no need to continue */
if (keys) ipa_krb5_free_key_data(keys, num_keys);
return true;
}
}
}
return false;
}
/* PRE ADD Operation:
* Gets the clean text password (fail the operation if the password came
* pre-hashed, unless this is a replicated operation or migration mode is
* enabled).
* Check user is authorized to add it otherwise just returns, operation will
* fail later anyway.
* Run a password policy check.
* Check if krb or smb hashes are required by testing if the krb or smb
* objectclasses are present.
* store information for the post operation
*/
static int ipapwd_pre_add(Slapi_PBlock *pb)
{
struct ipapwd_krbcfg *krbcfg = NULL;
char *errMesg = "Internal operations error\n";
struct slapi_entry *e = NULL;
char *userpw = NULL;
Slapi_DN *sdn = NULL;
struct ipapwd_operation *pwdop = NULL;
void *op;
int is_repl_op, is_root, is_krb, is_smb, is_ipant, is_memberof;
int ret;
int rc = LDAP_SUCCESS;
LOG_TRACE("=>\n");
ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
if (ret != 0) {
LOG_FATAL("slapi_pblock_get failed!?\n");
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
/* pass through if this is a replicated operation */
if (is_repl_op)
return 0;
/* retrieve the entry */
slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
if (NULL == e)
return 0;
/* check this is something interesting for us first */
userpw = slapi_entry_attr_get_charptr(e, SLAPI_USERPWD_ATTR);
if (!userpw) {
/* nothing interesting here */
return 0;
}
/* Ok this is interesting,
* Check this is a clear text password, or refuse operation */
if ('{' == userpw[0]) {
if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) {
char *tmp = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]);
if (NULL == tmp) {
LOG_OOM();
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
slapi_ch_free_string(&userpw);
userpw = tmp;
} else if (slapi_is_encoded(userpw)) {
const char *userpw_clear = NULL;
Slapi_Value **pwvals = NULL;
/* Try to get clear password from an entry extension.
* This function does not return a copy of the values,
* no need to free them. */
rc = slapi_pw_get_entry_ext(e, &pwvals);
if (LDAP_SUCCESS == rc) {
userpw_clear = slapi_value_get_string(pwvals[0]);
}
/* Fail if we did not get a real clear text password from
* the extension. This will happen if the password is hashed. */
if (!userpw_clear || (0 == strcmp(userpw, userpw_clear))) {
rc = LDAP_CONSTRAINT_VIOLATION;
slapi_ch_free_string(&userpw);
} else {
rc = ipapwd_check_max_pwd_len(strlen(userpw_clear), &errMesg);
if (rc) {
goto done;
}
userpw = slapi_ch_strdup(userpw_clear);
}
if (rc != LDAP_SUCCESS) {
/* we don't have access to the clear text password;
* let it slide if migration is enabled, but don't
* generate kerberos keys */
char *enabled = ipapwd_getIpaConfigAttr("ipamigrationenabled");
if (NULL == enabled) {
LOG("no ipaMigrationEnabled in config, assuming FALSE\n");
} else if (0 == strcmp(enabled, "TRUE")) {
return 0;
}
/* With User Life Cycle, it could be a stage user that is activated.
* The userPassword and krb keys were set while the user was a stage user.
* Accept hashed userPassword and krb keys at the condition, it already contains
* a valid krbPrincipalKey
*/
if (has_krbprincipalkey(e)) {
slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
LOG("User Life Cycle: %s is a activated stage user "
"(with prehashed password and krb keys)\n",
sdn ? slapi_sdn_get_dn(sdn) : "unknown");
return 0;
}
LOG("pre-hashed passwords are not valid\n");
errMesg = "pre-hashed passwords are not valid\n";
goto done;
}
}
}
rc = ipapwd_entry_checks(pb, e,
&is_root, &is_krb, &is_smb, &is_ipant,
&is_memberof,
NULL, SLAPI_ACL_ADD);
if (rc != LDAP_SUCCESS) {
goto done;
}
rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
if (rc != LDAP_SUCCESS) {
goto done;
}
/* Get target DN */
ret = slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
if (ret) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
/* time to get the operation handler */
ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
if (ret != 0) {
LOG_FATAL("slapi_pblock_get failed!?\n");
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type,
op, ipapwd_op_ext_list.handle);
if (NULL == pwdop) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
pwdop->pwd_op = IPAPWD_OP_ADD;
pwdop->pwdata.password = slapi_ch_strdup(userpw);
pwdop->is_memberof = is_memberof;
if (is_root) {
pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
} else {
char *binddn;
int i;
pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN;
/* Check Bind DN */
slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn);
/* if it is a passsync manager we also need to skip resets */
for (i = 0; i < krbcfg->num_passsync_mgrs; i++) {
if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) {
pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
break;
}
}
}
pwdop->pwdata.dn = slapi_ch_strdup(slapi_sdn_get_dn(sdn));
pwdop->pwdata.timeNow = time(NULL);
pwdop->pwdata.target = e;
ret = ipapwd_CheckPolicy(&pwdop->pwdata);
/* For accounts created by cn=Directory Manager or a passsync
* managers, ignore result of a policy check */
if ((pwdop->pwdata.changetype != IPA_CHANGETYPE_DSMGR) &&
(ret != 0) ) {
errMesg = ipapwd_error2string(ret);
rc = LDAP_CONSTRAINT_VIOLATION;
goto done;
}
if (is_krb || is_smb || is_ipant) {
Slapi_Value **svals = NULL;
Slapi_Value **ntvals = NULL;
char *nt = NULL;
pwdop->is_krb = is_krb;
rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata,
userpw, is_krb, is_smb, is_ipant,
&svals, &nt, &ntvals, &errMesg);
if (rc != LDAP_SUCCESS) {
goto done;
}
if (svals) {
/* add/replace values in existing entry */
ret = slapi_entry_attr_replace_sv(e, "krbPrincipalKey", svals);
if (ret) {
LOG_FATAL("failed to set encoded values in entry\n");
rc = LDAP_OPERATIONS_ERROR;
ipapwd_free_slapi_value_array(&svals);
goto done;
}
ipapwd_free_slapi_value_array(&svals);
}
if (nt && is_smb) {
/* set value */
slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt);
slapi_ch_free_string(&nt);
}
if (ntvals && is_ipant) {
slapi_entry_attr_replace_sv(e, "ipaNTHash", ntvals);
ipapwd_free_slapi_value_array(&ntvals);
}
if (is_smb) {
/* with samba integration we need to also set sambaPwdLastSet or
* samba will decide the user has to change the password again */
if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) {
/* if it is an admin change instead we need to let know to
* samba as well that the use rmust change its password */
slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L);
} else {
slapi_entry_attr_set_long(e, "sambaPwdLastset",
(long)pwdop->pwdata.timeNow);
}
}
}
rc = LDAP_SUCCESS;
done:
if (pwdop) pwdop->pwdata.target = NULL;
free_ipapwd_krbcfg(&krbcfg);
slapi_ch_free_string(&userpw);
if (rc != LDAP_SUCCESS) {
slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
return -1;
}
return 0;
}
#define NTHASH_REGEN_VAL "MagicRegen"
#define NTHASH_REGEN_LEN sizeof(NTHASH_REGEN_VAL)
static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods,
const char *dn, struct slapi_entry *entry,
struct ipapwd_krbcfg *krbcfg);
/* PRE MOD Operation:
* Gets the clean text password (fail the operation if the password came
* pre-hashed, unless this is a replicated operation).
* Check user is authorized to add it otherwise just returns, operation will
* fail later anyway.
* Check if krb or smb hashes are required by testing if the krb or smb
* objectclasses are present.
* Run a password policy check.
* store information for the post operation
*/
static int ipapwd_pre_mod(Slapi_PBlock *pb)
{
struct ipapwd_krbcfg *krbcfg = NULL;
char *errMesg = NULL;
LDAPMod **mods;
LDAPMod *lmod;
Slapi_Mods *smods = NULL;
char *userpw = NULL;
char *unhashedpw = NULL;
Slapi_DN *sdn = NULL;
Slapi_DN *tmp_sdn;
struct slapi_entry *e = NULL;
struct ipapwd_operation *pwdop = NULL;
void *op;
int is_repl_op, is_pwd_op, is_root, is_krb, is_smb, is_ipant, is_memberof;
int has_krb_keys = 0;
int has_history = 0;
int gen_krb_keys = 0;
int is_magic_regen = 0;
int ret, rc;
LOG_TRACE( "=>\n");
ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
if (ret != 0) {
LOG_FATAL("slapi_pblock_get failed!?\n");
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
/* pass through if this is a replicated operation */
if (is_repl_op) {
rc = LDAP_SUCCESS;
goto done;
}
/* grab the mods - we'll put them back later with
* our modifications appended
*/
slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
smods = slapi_mods_new();
slapi_mods_init_passin(smods, mods);
/* In the first pass,
* only check there is anything we are interested in */
is_pwd_op = 0;
lmod = slapi_mods_get_first_mod(smods);
while (lmod) {
struct berval *bv;
if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) {
/* check op filtering out LDAP_MOD_BVALUES */
switch (lmod->mod_op & 0x0f) {
case LDAP_MOD_ADD:
case LDAP_MOD_REPLACE:
is_pwd_op = 1;
default:
break;
}
} else if (slapi_attr_types_equivalent(lmod->mod_type, "ipaNTHash")) {
/* check op filtering out LDAP_MOD_BVALUES */
switch (lmod->mod_op & 0x0f) {
case LDAP_MOD_ADD:
case LDAP_MOD_REPLACE:
if (!lmod->mod_bvalues ||
!lmod->mod_bvalues[0]) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
bv = lmod->mod_bvalues[0];
if ((bv->bv_len >= NTHASH_REGEN_LEN -1) &&
(bv->bv_len <= NTHASH_REGEN_LEN) &&
(strncmp(NTHASH_REGEN_VAL,
bv->bv_val, bv->bv_len) == 0)) {
is_magic_regen = 1;
/* We do not remove the mod from the list due to
* https://pagure.io/389-ds-base/issue/387#comment-120145 */
}
default:
break;
}
} else if (slapi_attr_types_equivalent(lmod->mod_type,
"unhashed#user#password")) {
/* we check for unahsehd password here so that we are sure to
* catch them early, before further checks go on, this helps
* checking LDAP_MOD_DELETE operations in some corner cases later.
* We keep only the last one if multiple are provided for any
* reason */
if (!lmod->mod_bvalues ||
!lmod->mod_bvalues[0]) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
bv = lmod->mod_bvalues[0];
rc = ipapwd_check_max_pwd_len(bv->bv_len, &errMesg);
if (rc) {
goto done;
}
slapi_ch_free_string(&unhashedpw);
unhashedpw = slapi_ch_malloc(bv->bv_len+1);
if (!unhashedpw) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
memcpy(unhashedpw, bv->bv_val, bv->bv_len);
unhashedpw[bv->bv_len] = '\0';
}
lmod = slapi_mods_get_next_mod(smods);
}
/* If userPassword is not modified check if this is a request to generate
* NT hashes otherwise we are done here */
if (!is_pwd_op && !is_magic_regen) {
rc = LDAP_SUCCESS;
goto done;
}
/* OK we have something interesting here, start checking for
* pre-requisites */
/* Get target DN */
ret = slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
if (ret) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
tmp_sdn = slapi_sdn_dup(sdn);
if (tmp_sdn) {
/* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be
* available but it turns out that is only true if you are
* a dbm backend pre-op plugin - lucky dbm backend pre-op
* plugins.
* I think that is wrong since the entry is useful for filter
* tests and schema checks and this plugin shouldn't be limited
* to a single backend type, but I don't want that fight right
* now so we go get the entry here
*
slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e);
*/
ret = slapi_search_internal_get_entry(tmp_sdn, 0, &e, ipapwd_plugin_id);
slapi_sdn_free(&tmp_sdn);
if (ret != LDAP_SUCCESS) {
LOG("Failed to retrieve entry?!\n");
rc = LDAP_NO_SUCH_OBJECT;
goto done;
}
}
rc = ipapwd_entry_checks(pb, e,
&is_root, &is_krb, &is_smb, &is_ipant,
&is_memberof,
is_pwd_op ? SLAPI_USERPWD_ATTR : "ipaNTHash",
SLAPI_ACL_WRITE);
if (rc) {
goto done;
}
rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
if (rc) {
goto done;
}
if (!is_pwd_op) {
/* This may be a magic op to ask us to generate the NT hashes */
if (is_magic_regen) {
/* Make sense to call only if this entry has krb keys to source
* the nthash from */
if (is_krb) {
rc = ipapwd_regen_nthash(pb, smods, slapi_sdn_get_dn(sdn), e, krbcfg);
} else {
rc = LDAP_UNWILLING_TO_PERFORM;
}
} else {
rc = LDAP_OPERATIONS_ERROR;
}
goto done;
}
/* run through the mods again and adjust flags if operations affect them */
lmod = slapi_mods_get_first_mod(smods);
while (lmod) {
struct berval *bv;
if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) {
/* check op filtering out LDAP_MOD_BVALUES */
switch (lmod->mod_op & 0x0f) {
case LDAP_MOD_ADD:
/* FIXME: should we try to track cases where we would end up
* with multiple userPassword entries ?? */
case LDAP_MOD_REPLACE:
is_pwd_op = 1;
if (!lmod->mod_bvalues ||
!lmod->mod_bvalues[0]) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
bv = lmod->mod_bvalues[0];
slapi_ch_free_string(&userpw);
userpw = slapi_ch_malloc(bv->bv_len+1);
if (!userpw) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
memcpy(userpw, bv->bv_val, bv->bv_len);
userpw[bv->bv_len] = '\0';
break;
case LDAP_MOD_DELETE:
/* reset only if we are deleting all values, or the exact
* same value previously set, otherwise we are just trying to
* add a new value and delete an existing one */
if (!lmod->mod_bvalues ||
!lmod->mod_bvalues[0]) {
is_pwd_op = 0;
} else {
bv = lmod->mod_bvalues[0];
if ((userpw &&
strncmp(userpw, bv->bv_val, bv->bv_len) == 0) ||
(unhashedpw &&
strncmp(unhashedpw, bv->bv_val, bv->bv_len) == 0)) {
is_pwd_op = 0;
}
}
default:
break;
}
} else if (slapi_attr_types_equivalent(lmod->mod_type,
SLAPI_ATTR_OBJECTCLASS)) {
int i;
/* check op filtering out LDAP_MOD_BVALUES */
switch (lmod->mod_op & 0x0f) {
case LDAP_MOD_REPLACE:
/* if objectclasses are replaced we need to start clean with
* flags, so we sero them out and see if they get set again */
is_krb = 0;
is_smb = 0;
is_ipant = 0;
/* After examining the output of covscan, we think that this
* fallthrough is intentional.*/
case LDAP_MOD_ADD:
if (!lmod->mod_bvalues ||
!lmod->mod_bvalues[0]) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
for (i = 0; (bv = lmod->mod_bvalues[i]) != NULL; i++) {
if (strncasecmp("krbPrincipalAux",
bv->bv_val, bv->bv_len) == 0) {
is_krb = 1;
} else if (strncasecmp("sambaSamAccount",
bv->bv_val, bv->bv_len) == 0) {
is_smb = 1;
} else if (strncasecmp("ipaNTUserAttrs",
bv->bv_val, bv->bv_len) == 0) {
is_ipant = 1;
}
}
break;
case LDAP_MOD_DELETE:
/* can this happen for objectclasses ? */
is_krb = 0;
is_smb = 0;
is_ipant = 0;
default:
break;
}
} else if (slapi_attr_types_equivalent(lmod->mod_type,
"krbPrincipalKey")) {
/* if we are getting a krbPrincipalKey, also avoid regenerating
* the keys, it means kadmin has alredy done the job and is simply
* keeping userPassword and sambaXXPAssword in sync */
/* we also check we have enough authority */
if (is_root) {
has_krb_keys = 1;
}
} else if (slapi_attr_types_equivalent(lmod->mod_type,
"passwordHistory")) {
/* if we are getting a passwordHistory, also avoid regenerating
* the hashes, it means kadmin has alredy done the job and is
* simply keeping userPassword and sambaXXPAssword in sync */
/* we also check we have enough authority */
if (is_root) {
has_history = 1;
}
}
lmod = slapi_mods_get_next_mod(smods);
}
if (is_krb) {
if (has_krb_keys) {
gen_krb_keys = 0;
} else {
gen_krb_keys = 1;
}
}
/* It seem like we have determined that the end result will be deletion of
* the userPassword attribute, so we have no more business here */
if (! is_pwd_op) {
rc = LDAP_SUCCESS;
goto done;
}
/* Check this is a clear text password, or refuse operation (only if we need
* to comput other hashes */
if (! unhashedpw && (gen_krb_keys || is_smb || is_ipant)) {
if ((userpw != NULL) && ('{' == userpw[0])) {
if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) {
const char *userpw_clear = &userpw[strlen("{CLEAR}")];
rc = ipapwd_check_max_pwd_len(strlen(userpw_clear), &errMesg);
if (rc) {
goto done;
}
unhashedpw = slapi_ch_strdup(userpw_clear);
if (NULL == unhashedpw) {
LOG_OOM();
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
slapi_ch_free_string(&userpw);
} else if (slapi_is_encoded(userpw)) {
LOG("Pre-Encoded passwords are not valid\n");
errMesg = "Pre-Encoded passwords are not valid\n";
rc = LDAP_CONSTRAINT_VIOLATION;
goto done;
}
}
}
/* time to get the operation handler */
ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
if (ret != 0) {
LOG_FATAL("slapi_pblock_get failed!?\n");
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type,
op, ipapwd_op_ext_list.handle);
if (NULL == pwdop) {
rc = LDAP_OPERATIONS_ERROR;
goto done;
}
pwdop->is_krb = is_krb;
pwdop->is_memberof = is_memberof;
pwdop->pwd_op = IPAPWD_OP_MOD;
pwdop->pwdata.password = slapi_ch_strdup(unhashedpw);
pwdop->pwdata.changetype = IPA_CHANGETYPE_NORMAL;
pwdop->skip_history = has_history;
pwdop->skip_keys = has_krb_keys;
if (is_root) {
pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
} else {
char *binddn;
Slapi_DN *bdn, *tdn;
int i;
/* Check Bind DN */
slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn);
bdn = slapi_sdn_new_dn_byref(binddn);
tdn = slapi_sdn_dup(sdn);
/* if the change is performed by someone else,
* it is an admin change that will require a new
* password change immediately as per our IPA policy */
if (slapi_sdn_compare(bdn, tdn)) {
pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN;
/* if it is a passsync manager we also need to skip resets */
for (i = 0; i < krbcfg->num_passsync_mgrs; i++) {
if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) {
pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
break;
}
}
}
slapi_sdn_free(&bdn);
slapi_sdn_free(&tdn);
}
pwdop->pwdata.dn = slapi_ch_strdup(slapi_sdn_get_dn(sdn));
pwdop->pwdata.timeNow = time(NULL);
pwdop->pwdata.target = e;
/* if krb keys are being set by an external agent we assume password
* policies have been properly checked already. We check them only if no
* krb keys are available and raise error if the change is not done by a
* cn=Directory Manager or one of passsync managers */
if (has_krb_keys == 0) {
ret = ipapwd_CheckPolicy(&pwdop->pwdata);
if ((pwdop->pwdata.changetype != IPA_CHANGETYPE_DSMGR) &&
(ret != 0)) {
errMesg = ipapwd_error2string(ret);
rc = LDAP_CONSTRAINT_VIOLATION;
goto done;
}
}
if (gen_krb_keys || is_smb || is_ipant) {
Slapi_Value **svals = NULL;
Slapi_Value **ntvals = NULL;
char *nt = NULL;
rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata, unhashedpw,
gen_krb_keys, is_smb, is_ipant,
&svals, &nt, &ntvals, &errMesg);
if (rc) {
goto done;
}
if (svals) {
/* replace values */
slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
"krbPrincipalKey", svals);
ipapwd_free_slapi_value_array(&svals);
}
if (nt && is_smb) {
/* replace value */
slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
"sambaNTPassword", nt);
slapi_ch_free_string(&nt);
}
if (ntvals && is_ipant) {
slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
"ipaNTHash", ntvals);
ipapwd_free_slapi_value_array(&ntvals);
}
if (is_smb) {
/* with samba integration we need to also set sambaPwdLastSet or
* samba will decide the user has to change the password again */
if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) {
/* if it is an admin change instead we need to let know to
* samba as well that the use rmust change its password */
slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L);
} else {
slapi_entry_attr_set_long(e, "sambaPwdLastset",
(long)pwdop->pwdata.timeNow);
}
}
}
rc = LDAP_SUCCESS;
done:
free_ipapwd_krbcfg(&krbcfg);
slapi_ch_free_string(&userpw); /* just to be sure */
slapi_ch_free_string(&unhashedpw); /* we copied it to pwdop */
if (e) slapi_entry_free(e); /* this is a copy in this function */
if (pwdop) pwdop->pwdata.target = NULL;
/* put back a, possibly modified, set of mods */
if (smods) {
mods = slapi_mods_get_ldapmods_passout(smods);
if (slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods)) {
LOG_FATAL("slapi_pblock_set failed!\n");
rc = LDAP_OPERATIONS_ERROR;
}
slapi_mods_free(&smods);
}
if (rc != LDAP_SUCCESS) {
slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
return -1;
}
return 0;
}
static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods,
const char *dn, struct slapi_entry *entry,
struct ipapwd_krbcfg *krbcfg)
{
Slapi_Attr *attr;
Slapi_Value *value;
const struct berval *val;
struct berval *ntvals[2] = { NULL, NULL };
struct berval bval;
krb5_key_data *keys;
int num_keys;
int mkvno;
int ret;
int i;
ret = slapi_entry_attr_find(entry, "ipaNTHash", &attr);
if (ret == 0) {
/* We refuse to regen if there is already a value */
return LDAP_CONSTRAINT_VIOLATION;
}
/* ok let's see if we can find the RC4 hash in the keys */
ret = slapi_entry_attr_find(entry, "krbPrincipalKey", &attr);
if (ret) {
return LDAP_UNWILLING_TO_PERFORM;
}
ret = slapi_attr_first_value(attr, &value);
if (ret) {
return LDAP_OPERATIONS_ERROR;
}
val = slapi_value_get_berval(value);
if (!val) {
return LDAP_OPERATIONS_ERROR;
}
ret = ber_decode_krb5_key_data((struct berval *)val,
&mkvno, &num_keys, &keys);
if (ret) {
return LDAP_OPERATIONS_ERROR;
}
ret = LDAP_UNWILLING_TO_PERFORM;
for (i = 0; i < num_keys; i++) {
char nthash[16];
krb5_enc_data cipher;
krb5_data plain;
krb5_int16 t;
if (keys[i].key_data_type[0] != ENCTYPE_ARCFOUR_HMAC) {
continue;
}
memcpy(&t, keys[i].key_data_contents[0], 2);
plain.length = le16toh(t);
if (plain.length != 16) {
continue;
}
plain.data = nthash;
memset(&cipher, 0, sizeof(krb5_enc_data));
cipher.enctype = krbcfg->kmkey->enctype;
cipher.ciphertext.length = keys[i].key_data_length[0] - 2;
cipher.ciphertext.data = ((char *)keys[i].key_data_contents[0]) + 2;
ret = krb5_c_decrypt(krbcfg->krbctx, krbcfg->kmkey,
0, NULL, &cipher, &plain);
if (ret) {
ret = LDAP_OPERATIONS_ERROR;
break;
}
bval.bv_val = nthash;
bval.bv_len = 16;
ntvals[0] = &bval;
/* add the change as a replace operation due to
* https://pagure.io/389-ds-base/issue/387#comment-120145 */
slapi_mods_add_modbvps(smods, LDAP_MOD_REPLACE, "ipaNTHash", ntvals);
ret = LDAP_SUCCESS;
break;
}
ipa_krb5_free_key_data(keys, num_keys);
return ret;
}
static int ipapwd_post_updatecfg(Slapi_PBlock *pb)
{
otp_config_update(otp_config, pb);
return 0;
}
static int ipapwd_post_modadd(Slapi_PBlock *pb)
{
void *op;
struct ipapwd_operation *pwdop = NULL;
Slapi_Mods *smods;
Slapi_Value **pwvals;
int ret;
char *errMsg = "Internal operations error\n";
struct ipapwd_krbcfg *krbcfg = NULL;
char *principal = NULL;
Slapi_Value *ipahost;
LOG_TRACE("=>\n");
otp_config_update(otp_config, pb);
/* time to get the operation handler */
ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
if (ret != 0) {
LOG_FATAL("slapi_pblock_get failed!?\n");
return 0;
}
pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type,
op, ipapwd_op_ext_list.handle);
if (NULL == pwdop) {
LOG_FATAL("Internal error, couldn't find pluginextension ?!\n");
return 0;
}
/* not interesting */
if (IPAPWD_OP_NULL == pwdop->pwd_op)
return 0;
if ( !pwdop->is_krb || pwdop->is_memberof) {
LOG("Not a kerberos user, ignore krb attributes\n");
return 0;
}
if (pwdop->skip_keys && pwdop->skip_history) {
/* nothing to do, caller already set all interesting attributes */
return 0;
}
ret = ipapwd_gen_checks(pb, &errMsg, &krbcfg, 0);
if (ret != 0) {
LOG_FATAL("ipapwd_gen_checks failed!?\n");
return 0;
}
/* prepare changes that can be made only as root */
smods = slapi_mods_new();
/* This was a mod operation on an existing entry, make sure we also update
* the password history based on the entry we saved from the pre-op */
if (IPAPWD_OP_MOD == pwdop->pwd_op && !pwdop->skip_history) {
Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(pwdop->pwdata.dn);
if (tmp_dn) {
ret = slapi_search_internal_get_entry(tmp_dn, 0,
&pwdop->pwdata.target,
ipapwd_plugin_id);
slapi_sdn_free(&tmp_dn);
if (ret != LDAP_SUCCESS) {
LOG("Failed to retrieve entry?!\n");
goto done;
}
}
pwvals = ipapwd_setPasswordHistory(smods, &pwdop->pwdata);
if (pwvals) {
slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
"passwordHistory", pwvals);
}
}
/* we assume that krb attributes are properly updated too if keys were
* passed in */
if (!pwdop->skip_keys) {
/* Don't set a last password change or expiration on host passwords.
* krbLastPwdChange is used to tell whether we have a valid keytab.
* If we set it on userPassword it confuses enrollment.
* If krbPasswordExpiration is set on a host entry then the keytab
* will appear to be expired.
*
* When a host is issued a keytab these attributes get set properly by
* ipapwd_setkeytab().
*/
ipahost = slapi_value_new_string("ipaHost");
if (!pwdop->pwdata.target ||
(slapi_entry_attr_has_syntax_value(pwdop->pwdata.target,
SLAPI_ATTR_OBJECTCLASS, ipahost)) == 0) {
/* set Password Expiration date */
ret = ipapwd_setdate(pwdop->pwdata.target, smods,
"krbPasswordExpiration",
pwdop->pwdata.expireTime,
(pwdop->pwdata.expireTime == 0));
if (ret != LDAP_SUCCESS)
goto done;
/* change Last Password Change field with the current date */
ret = ipapwd_setdate(pwdop->pwdata.target, smods,
"krbLastPwdChange",
pwdop->pwdata.timeNow, false);
if (ret != LDAP_SUCCESS)
goto done;
}
slapi_value_free(&ipahost);
}
ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods);
if (ret)
LOG("Failed to set additional password attributes in the post-op!\n");
if (!pwdop->skip_keys) {
if (pwdop->pwdata.changetype == IPA_CHANGETYPE_NORMAL) {
principal = slapi_entry_attr_get_charptr(pwdop->pwdata.target,
"krbPrincipalName");
} else {
principal = slapi_ch_smprintf("root/admin@%s", krbcfg->realm);
}
ipapwd_set_extradata(pwdop->pwdata.dn, principal, pwdop->pwdata.timeNow);
}
done:
if (pwdop && pwdop->pwdata.target) slapi_entry_free(pwdop->pwdata.target);
slapi_mods_free(&smods);
slapi_ch_free_string(&principal);
free_ipapwd_krbcfg(&krbcfg);
return 0;
}
/*
* 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.
*
* NOTE: When successful, this function truncates creds to remove the token
* value at the end. This leaves only the password in creds for later
* validation.
*/
static bool ipapwd_pre_bind_otp(const char *bind_dn, Slapi_Entry *entry,
struct berval *creds, bool otpreq)
{
uint32_t auth_types;
/* Get the configured authentication types. */
auth_types = otp_config_auth_types(otp_config, entry);
/*
* IMPORTANT SECTION!
*
* This section handles authentication logic, so be careful!
*
* The basic idea of this section is:
* 1. If OTP is enabled, validate OTP.
* 2. If PWD is enabled or OTP succeeded, fall through to PWD validation.
*/
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);
/* 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;
}
/* With no tokens, succeed if tokens aren't required. */
if (tokens[0] == NULL) {
otp_token_free_array(tokens);
return !otpreq;
}
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) && !otpreq;
}
static int ipapwd_authenticate(const char *dn, Slapi_Entry *entry,
const struct berval *credentials)
{
Slapi_Value **pwd_values = NULL; /* values of userPassword attribute */
Slapi_Value *value = NULL;
Slapi_Attr *attr = NULL;
int ret;
/* retrieve userPassword attribute */
ret = slapi_entry_attr_find(entry, SLAPI_USERPWD_ATTR, &attr);
if (ret) {
LOG("no " SLAPI_USERPWD_ATTR " in user entry: %s\n", dn);
return ret;
}
/* get the number of userPassword values and allocate enough memory */
slapi_attr_get_numvalues(attr, &ret);
ret = (ret + 1) * sizeof (Slapi_Value *);
pwd_values = (Slapi_Value **) slapi_ch_malloc(ret);
if (!pwd_values) {
/* probably not required: should terminate the server anyway */
LOG_OOM();
return ret;
}
/* zero-fill the allocated memory; we need the array ending with NULL */
memset(pwd_values, 0, ret);
/* retrieve userPassword values */
ret = slapi_attr_first_value(attr, &value);
while (ret != -1) {
pwd_values[ret] = value;
ret = slapi_attr_next_value(attr, ret, &value);
}
/* check if BIND password and userPassword match */
value = slapi_value_new_berval(credentials);
ret = slapi_pw_find_sv(pwd_values, value);
/* free before checking ret; we might not get a chance later */
slapi_ch_free((void **) &pwd_values);
slapi_value_free(&value);
if (ret)
LOG("invalid BIND password for user entry: %s\n", dn);
return ret;
}
static void ipapwd_write_krb_keys(Slapi_PBlock *pb, char *dn,
Slapi_Entry *entry,
const struct berval *credentials)
{
char *errMesg = "Internal operations error\n";
struct ipapwd_krbcfg *krbcfg = NULL;
struct ipapwd_data pwdata;
Slapi_Value *objectclass;
Slapi_Attr *attr = NULL;
char *principal = NULL;
struct tm expire_tm;
char *expire = NULL;
int ret;
/* check the krbPrincipalName attribute is present */
ret = slapi_entry_attr_find(entry, "krbprincipalname", &attr);
if (ret) {
LOG("no krbPrincipalName in user entry: %s\n", dn);
goto done;
}
/* we aren't interested in host principals */
objectclass = slapi_value_new_string("ipaHost");
if ((slapi_entry_attr_has_syntax_value(entry, SLAPI_ATTR_OBJECTCLASS,
objectclass)) == 1) {
slapi_value_free(&objectclass);
goto done;
}
slapi_value_free(&objectclass);
/* check the krbPrincipalKey attribute is NOT present */
ret = slapi_entry_attr_find(entry, "krbprincipalkey", &attr);
if (!ret) {
LOG("kerberos key already present in user entry: %s\n", dn);
goto done;
}
/* general checks */
ret = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
if (ret) {
LOG_FATAL("Generic checks failed: %s", errMesg);
goto done;
}
/* delete userPassword - a new one will be generated later */
/* this is needed, otherwise ipapwd_CheckPolicy will think
* we're changing the password to its previous value
* and force a password change on next login */
ret = slapi_entry_attr_delete(entry, SLAPI_USERPWD_ATTR);
if (ret) {
LOG_FATAL("failed to delete " SLAPI_USERPWD_ATTR "\n");
goto done;
}
/* prepare data for kerberos key generation */
memset(&pwdata, 0, sizeof (pwdata));
pwdata.dn = dn;
pwdata.target = entry;
pwdata.password = credentials->bv_val;
pwdata.timeNow = time(NULL);
pwdata.changetype = IPA_CHANGETYPE_NORMAL;
/* keep password expiration time from DS, if possible */
expire = slapi_entry_attr_get_charptr(entry, "passwordexpirationtime");
if (expire) {
memset(&expire_tm, 0, sizeof (expire_tm));
if (strptime(expire, "%Y%m%d%H%M%SZ", &expire_tm))
pwdata.expireTime = timegm(&expire_tm);
}
/* check password policy */
ret = ipapwd_CheckPolicy(&pwdata);
if (ret) {
/* Password fails to meet IPA password policy,
* force user to change his password next time he logs in. */
LOG("password policy check failed on user entry: %s"
" (force password change on next login)\n", dn);
pwdata.expireTime = time(NULL);
}
/* generate kerberos keys */
ret = ipapwd_SetPassword(krbcfg, &pwdata, 1);
if (ret) {
LOG("failed to set kerberos key for user entry: %s\n", dn);
goto done;
}
/* we need to make sure the ExtraData is set, otherwise kadmin
* will not like the object */
principal = slapi_entry_attr_get_charptr(entry, "krbPrincipalName");
if (!principal) {
LOG_OOM();
goto done;
}
ipapwd_set_extradata(pwdata.dn, principal, pwdata.timeNow);
LOG("kerberos key generated for user entry: %s\n", dn);
done:
slapi_ch_free_string(&principal);
slapi_ch_free_string(&expire);
free_ipapwd_krbcfg(&krbcfg);
}
/* PRE BIND Operation
*
* Used for:
* 1. Password migration from DS to IPA -- Gets the clean text password,
* authenticates the user and generates a kerberos key if missing.
* 2. OTP validation
* 3. OTP synchronization
*/
static int ipapwd_pre_bind(Slapi_PBlock *pb)
{
static const char *attrs_list[] = {
SLAPI_USERPWD_ATTR, "ipaUserAuthType", "krbprincipalkey", "uid",
"krbprincipalname", "objectclass", "passwordexpirationtime",
"passwordhistory", "krbprincipalexpiration", "krbcanonicalname",
"krbPasswordExpiration", "krblastpwchange",
NULL
};
struct berval *credentials = NULL;
Slapi_Entry *entry = NULL;
Slapi_DN *target_sdn = NULL;
Slapi_DN *sdn = NULL;
const char *dn = NULL;
int method = 0;
bool syncreq;
bool otpreq;
int ret = 0;
time_t current_time;
time_t expire_time;
char *principal_expire = NULL;
struct tm expire_tm;
int rc = LDAP_INVALID_CREDENTIALS;
char *errMesg = NULL;
/* get BIND parameters */
ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET_SDN, &target_sdn);
ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
ret |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &credentials);
if (ret) {
LOG_FATAL("slapi_pblock_get failed!?\n");
return 0;
}
/* We're only interested in simple authentication. */
if (method != LDAP_AUTH_SIMPLE || credentials->bv_len == 0)
return 0;
/* Retrieve the user's entry. */
sdn = slapi_sdn_dup(target_sdn);
dn = slapi_sdn_get_dn(sdn);
ret = ipapwd_getEntry(sdn, &entry, (char **) attrs_list);
if (ret) {
LOG("failed to retrieve user entry: %s\n", dn);
slapi_sdn_free(&sdn);
return 0;
}
/* Check if the principal is not expired */
principal_expire = slapi_entry_attr_get_charptr(entry, "krbPrincipalExpiration");
if (principal_expire) {
/* if it is set, check whether the principal has not expired */
memset(&expire_tm, 0, sizeof (expire_tm));
if (strptime(principal_expire, "%Y%m%d%H%M%SZ", &expire_tm)) {
expire_time = timegm(&expire_tm);
current_time = time(NULL);
/* timegm returns -1 if the tm struct cannot be represented as
* as calendar time (seconds since the Epoch). This might
* happen with tm structs that are ill-formated or on 32-bit
* platforms with dates that would cause overflow
* (year 2038 and later).
* In such cases, skip the expiration check. */
if (current_time > expire_time && expire_time > 0) {
LOG_FATAL("kerberos principal in %s is expired\n", dn);
slapi_entry_free(entry);
slapi_sdn_free(&sdn);
slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL,
"Account (Kerberos principal) is expired",
0, NULL);
return -1;
}
}
}
/* Try to do OTP first. */
syncreq = otpctrl_present(pb, OTP_SYNC_REQUEST_OID);
otpreq = otpctrl_present(pb, OTP_REQUIRED_OID);
if (!syncreq && !ipapwd_pre_bind_otp(dn, entry, credentials, otpreq))
goto invalid_creds;
/* Ensure that there is a password. */
if (credentials->bv_len == 0) {
goto invalid_creds;
} else {
rc = ipapwd_check_max_pwd_len(credentials->bv_len, &errMesg);
if (rc) {
goto invalid_creds;
}
}
/* Authenticate the user. */
ret = ipapwd_authenticate(dn, entry, credentials);
if (ret) {
slapi_entry_free(entry);
slapi_sdn_free(&sdn);
return 0;
}
/* Attempt to handle a token synchronization request. */
if (syncreq && !otpctrl_sync_handle(otp_config, pb, dn))
goto invalid_creds;
/* Attempt to write out kerberos keys for the user. */
ipapwd_write_krb_keys(pb, discard_const(dn), entry, credentials);
slapi_entry_free(entry);
slapi_sdn_free(&sdn);
return 0;
invalid_creds:
slapi_entry_free(entry);
slapi_sdn_free(&sdn);
slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
return 1;
}
/* Init pre ops */
int ipapwd_pre_init(Slapi_PBlock *pb)
{
int ret;
slapi_register_supported_control(OTP_SYNC_REQUEST_OID, SLAPI_OPERATION_BIND);
slapi_register_supported_control(OTP_REQUIRED_OID, SLAPI_OPERATION_BIND);
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *)ipapwd_pre_bind);
return ret;
}
int ipapwd_pre_init_betxn(Slapi_PBlock *pb)
{
int ret;
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN, (void *)ipapwd_pre_add);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod);
return ret;
}
/* Init post ops */
int ipapwd_post_init(Slapi_PBlock *pb)
{
int ret;
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *)ipapwd_post_updatecfg);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *)ipapwd_post_updatecfg);
return ret;
}
int ipapwd_intpost_init(Slapi_PBlock *pb)
{
int ret;
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *)ipapwd_post_updatecfg);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *)ipapwd_post_updatecfg);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *)ipapwd_post_updatecfg);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *)ipapwd_post_updatecfg);
return ret;
}
int ipapwd_post_init_betxn(Slapi_PBlock *pb)
{
int ret;
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_modadd);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_modadd);
return ret;
}