summaryrefslogtreecommitdiffstats
path: root/daemons
diff options
context:
space:
mode:
Diffstat (limited to 'daemons')
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am3
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c3224
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h162
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c1427
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c809
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c1077
6 files changed, 3487 insertions, 3215 deletions
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
index 540646f06..6358eceb2 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
@@ -20,6 +20,9 @@ plugin_LTLIBRARIES = \
$(NULL)
libipa_pwd_extop_la_SOURCES = \
+ ipapwd_common.c \
+ ipapwd_encoding.c \
+ ipapwd_prepost.c \
ipa_pwd_extop.c \
$(NULL)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
index c2d0373e6..cbf572188 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
@@ -33,13 +33,11 @@
* Authors:
* Simo Sorce <ssorce@redhat.com>
*
- * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2007-2010 Red Hat, Inc.
* All rights reserved.
* END COPYRIGHT BLOCK **/
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
+#include "ipapwd.h"
/*
* Password Modify - LDAP Extended Operation.
@@ -53,31 +51,6 @@
*
*/
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <prio.h>
-#include <ssl.h>
-#include <dirsrv/slapi-plugin.h>
-#define KRB5_PRIVATE 1
-#include <krb5.h>
-#include <lber.h>
-#include <time.h>
-#include <iconv.h>
-#include <openssl/des.h>
-#include <openssl/md4.h>
-
-/* Type of connection for this operation;*/
-#define LDAP_EXTOP_PASSMOD_CONN_SECURE
-
-/* Uncomment the following #undef FOR TESTING:
- * allows non-SSL connections to use the password change extended op */
-/* #undef LDAP_EXTOP_PASSMOD_CONN_SECURE */
-
/* ber tags for the PasswdModifyRequestValue sequence */
#define LDAP_EXTOP_PASSMOD_TAG_USERID 0x80U
#define LDAP_EXTOP_PASSMOD_TAG_OLDPWD 0x81U
@@ -93,164 +66,23 @@
#define KEYTAB_SET_OID "2.16.840.1.113730.3.8.3.1"
#define KEYTAB_RET_OID "2.16.840.1.113730.3.8.3.2"
-/* krbTicketFlags */
-#define KTF_DISALLOW_POSTDATED 0x00000001
-#define KTF_DISALLOW_FORWARDABLE 0x00000002
-#define KTF_DISALLOW_TGT_BASED 0x00000004
-#define KTF_DISALLOW_RENEWABLE 0x00000008
-#define KTF_DISALLOW_PROXIABLE 0x00000010
-#define KTF_DISALLOW_DUP_SKEY 0x00000020
-#define KTF_DISALLOW_ALL_TIX 0x00000040
-#define KTF_REQUIRES_PRE_AUTH 0x00000080
-#define KTF_REQUIRES_HW_AUTH 0x00000100
-#define KTF_REQUIRES_PWCHANGE 0x00000200
-#define KTF_DISALLOW_SVR 0x00001000
-#define KTF_PWCHANGE_SERVICE 0x00002000
-
-/* These are the default enc:salt types if nothing is defined.
- * TODO: retrieve the configure set of ecntypes either from the
- * kfc.conf file or by synchronizing the the file content into
- * the directory */
-
-/* Salt types */
-#define KRB5_KDB_SALTTYPE_NORMAL 0
-#define KRB5_KDB_SALTTYPE_V4 1
-#define KRB5_KDB_SALTTYPE_NOREALM 2
-#define KRB5_KDB_SALTTYPE_ONLYREALM 3
-#define KRB5_KDB_SALTTYPE_SPECIAL 4
-#define KRB5_KDB_SALTTYPE_AFS3 5
-
-#define KRB5P_SALT_SIZE 16
-
-void krb5int_c_free_keyblock_contents(krb5_context context, register krb5_keyblock *key);
-
-/* Forward definition */
-static int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e,
- int *is_root, int *is_krb, int *is_smb,
- char *attr, int access);
-
-static const char *ipapwd_def_encsalts[] = {
- "des3-hmac-sha1:normal",
-/* "arcfour-hmac:normal",
- "des-hmac-sha1:normal",
- "des-cbc-md5:normal", */
- "des-cbc-crc:normal",
-/* "des-cbc-crc:v4",
- "des-cbc-crc:afs3", */
- NULL
-};
-struct ipapwd_encsalt {
- krb5_int32 enc_type;
- krb5_int32 salt_type;
-};
/* base DN of IPA realm tree */
-static const char *ipa_realm_tree;
+const char *ipa_realm_tree;
/* dn of Kerberos realm entry */
-static const char *ipa_realm_dn;
-static const char *ipa_pwd_config_dn;
-static const char *ipa_changepw_principal_dn;
-
-#define IPAPWD_PLUGIN_NAME "ipa-pwd-extop"
-#define IPAPWD_FEATURE_DESC "IPA Password Manager"
-#define IPAPWD_PLUGIN_DESC "IPA Password Extended Operation plugin"
+const char *ipa_realm_dn;
+const char *ipa_pwd_config_dn;
+const char *ipa_changepw_principal_dn;
-static Slapi_PluginDesc pdesc = {
+Slapi_PluginDesc ipapwd_plugin_desc = {
IPAPWD_FEATURE_DESC,
"FreeIPA project",
"FreeIPA/1.0",
IPAPWD_PLUGIN_DESC
};
-static void *ipapwd_plugin_id;
-
-#define IPA_CHANGETYPE_NORMAL 0
-#define IPA_CHANGETYPE_ADMIN 1
-#define IPA_CHANGETYPE_DSMGR 2
-
-struct ipapwd_krbcfg {
- krb5_context krbctx;
- char *realm;
- krb5_keyblock *kmkey;
- int num_supp_encsalts;
- struct ipapwd_encsalt *supp_encsalts;
- int num_pref_encsalts;
- struct ipapwd_encsalt *pref_encsalts;
- char **passsync_mgrs;
- int num_passsync_mgrs;
-};
-
-static void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg)
-{
- struct ipapwd_krbcfg *c = *cfg;
-
- if (!c) return;
-
- krb5_free_default_realm(c->krbctx, c->realm);
- krb5_free_context(c->krbctx);
- free(c->kmkey->contents);
- free(c->kmkey);
- free(c->supp_encsalts);
- free(c->pref_encsalts);
- slapi_ch_array_free(c->passsync_mgrs);
- free(c);
- *cfg = NULL;
-};
-
-struct ipapwd_data {
- Slapi_Entry *target;
- char *dn;
- char *password;
- time_t timeNow;
- time_t lastPwChange;
- time_t expireTime;
- int changetype;
- int pwHistoryLen;
-};
-
-struct ipapwd_krbkeydata {
- int32_t type;
- struct berval value;
-};
-
-struct ipapwd_krbkey {
- struct ipapwd_krbkeydata *salt;
- struct ipapwd_krbkeydata *ekey;
- struct berval s2kparams;
-};
-
-struct ipapwd_keyset {
- uint16_t major_vno;
- uint16_t minor_vno;
- uint32_t kvno;
- uint32_t mkvno;
- struct ipapwd_krbkey *keys;
- int num_keys;
-};
-
-static void ipapwd_keyset_free(struct ipapwd_keyset **pkset)
-{
- struct ipapwd_keyset *kset = *pkset;
- int i;
-
- if (!kset) return;
-
- for (i = 0; i < kset->num_keys; i++) {
- if (kset->keys[i].salt) {
- free(kset->keys[i].salt->value.bv_val);
- free(kset->keys[i].salt);
- }
- if (kset->keys[i].ekey) {
- free(kset->keys[i].ekey->value.bv_val);
- free(kset->keys[i].ekey);
- }
- free(kset->keys[i].s2kparams.bv_val);
- }
- free(kset->keys);
- free(kset);
- *pkset = NULL;
-}
+void *ipapwd_plugin_id;
static int filter_keys(struct ipapwd_krbcfg *krbcfg, struct ipapwd_keyset *kset)
{
@@ -292,1563 +124,6 @@ static int filter_keys(struct ipapwd_krbcfg *krbcfg, struct ipapwd_keyset *kset)
return 0;
}
-/* Novell key-format scheme:
-
- KrbKeySet ::= SEQUENCE {
- attribute-major-vno [0] UInt16,
- attribute-minor-vno [1] UInt16,
- kvno [2] UInt32,
- mkvno [3] UInt32 OPTIONAL,
- keys [4] SEQUENCE OF KrbKey,
- ...
- }
-
- KrbKey ::= SEQUENCE {
- salt [0] KrbSalt OPTIONAL,
- key [1] EncryptionKey,
- s2kparams [2] OCTET STRING OPTIONAL,
- ...
- }
-
- KrbSalt ::= SEQUENCE {
- type [0] Int32,
- salt [1] OCTET STRING OPTIONAL
- }
-
- EncryptionKey ::= SEQUENCE {
- keytype [0] Int32,
- keyvalue [1] OCTET STRING
- }
-
- */
-
-static struct berval *encode_keys(struct ipapwd_keyset *kset)
-{
- BerElement *be = NULL;
- struct berval *bval = NULL;
- int ret, i;
-
- be = ber_alloc_t(LBER_USE_DER);
-
- if (!be) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "memory allocation failed\n");
- return NULL;
- }
-
- ret = ber_printf(be, "{t[i]t[i]t[i]t[i]t[{",
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), kset->major_vno,
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), kset->minor_vno,
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 2), kset->kvno,
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 3), kset->mkvno,
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 4));
- if (ret == -1) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "encoding asn1 vno info failed\n");
- goto done;
- }
-
- for (i = 0; i < kset->num_keys; i++) {
-
- ret = ber_printf(be, "{");
- if (ret == -1) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "encoding asn1 EncryptionKey failed\n");
- goto done;
- }
-
- if (kset->keys[i].salt) {
- ret = ber_printf(be, "t[{t[i]",
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0),
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0),
- kset->keys[i].salt->type);
- if ((ret != -1) && kset->keys[i].salt->value.bv_len) {
- ret = ber_printf(be, "t[o]",
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1),
- kset->keys[i].salt->value.bv_val,
- kset->keys[i].salt->value.bv_len);
- }
- if (ret != -1) {
- ret = ber_printf(be, "}]");
- }
- if (ret == -1) {
- goto done;
- }
- }
-
- ret = ber_printf(be, "t[{t[i]t[o]}]",
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1),
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0),
- kset->keys[i].ekey->type,
- (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1),
- kset->keys[i].ekey->value.bv_val,
- kset->keys[i].ekey->value.bv_len);
- if (ret == -1) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "encoding asn1 EncryptionKey failed\n");
- goto done;
- }
-
- /* FIXME: s2kparams not supported yet */
-
- ret = ber_printf(be, "}");
- if (ret == -1) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "encoding asn1 EncryptionKey failed\n");
- goto done;
- }
- }
-
- ret = ber_printf(be, "}]}");
- if (ret == -1) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "encoding asn1 end of sequences failed\n");
- goto done;
- }
-
- ret = ber_flatten(be, &bval);
- if (ret == -1) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "flattening asn1 failed\n");
- goto done;
- }
-done:
- ber_free(be, 1);
-
- return bval;
-}
-
-static int ipapwd_get_cur_kvno(Slapi_Entry *target)
-{
- Slapi_Attr *krbPrincipalKey = NULL;
- Slapi_ValueSet *svs;
- Slapi_Value *sv;
- BerElement *be = NULL;
- const struct berval *cbval;
- ber_tag_t tag, tmp;
- ber_int_t tkvno;
- int hint;
- int kvno;
- int ret;
-
- /* retrieve current kvno and and keys */
- ret = slapi_entry_attr_find(target, "krbPrincipalKey", &krbPrincipalKey);
- if (ret != 0) {
- return 0;
- }
-
- kvno = 0;
-
- slapi_attr_get_valueset(krbPrincipalKey, &svs);
- hint = slapi_valueset_first_value(svs, &sv);
- while (hint != -1) {
- cbval = slapi_value_get_berval(sv);
- if (!cbval) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "Error retrieving berval from Slapi_Value\n");
- goto next;
- }
- be = ber_init(cbval);
- if (!be) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ber_init() failed!\n");
- goto next;
- }
-
- tag = ber_scanf(be, "{xxt[i]", &tmp, &tkvno);
- if (tag == LBER_ERROR) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "Bad OLD key encoding ?!\n");
- ber_free(be, 1);
- goto next;
- }
-
- if (tkvno > kvno) {
- kvno = tkvno;
- }
-
- ber_free(be, 1);
-next:
- hint = slapi_valueset_next_value(svs, hint, &sv);
- }
-
- return kvno;
-}
-
-static inline void encode_int16(unsigned int val, unsigned char *p)
-{
- p[1] = (val >> 8) & 0xff;
- p[0] = (val ) & 0xff;
-}
-
-static Slapi_Value **encrypt_encode_key(struct ipapwd_krbcfg *krbcfg,
- struct ipapwd_data *data,
- char **errMesg)
-{
- krb5_context krbctx;
- char *krbPrincipalName = NULL;
- uint32_t krbMaxTicketLife;
- int kvno, i;
- int krbTicketFlags;
- struct berval *bval = NULL;
- Slapi_Value **svals = NULL;
- krb5_principal princ;
- krb5_error_code krberr;
- krb5_data pwd;
- struct ipapwd_keyset *kset = NULL;
-
- krbctx = krbcfg->krbctx;
-
- svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *));
- if (!svals) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "memory allocation failed\n");
- return NULL;
- }
-
- kvno = ipapwd_get_cur_kvno(data->target);
-
- krbPrincipalName = slapi_entry_attr_get_charptr(data->target, "krbPrincipalName");
- if (!krbPrincipalName) {
- *errMesg = "no krbPrincipalName present in this entry\n";
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", *errMesg);
- return NULL;
- }
-
- krberr = krb5_parse_name(krbctx, krbPrincipalName, &princ);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "krb5_parse_name failed [%s]\n",
- krb5_get_error_message(krbctx, krberr));
- goto enc_error;
- }
-
- krbMaxTicketLife = slapi_entry_attr_get_uint(data->target, "krbMaxTicketLife");
- if (krbMaxTicketLife == 0) {
- /* FIXME: retrieve the default from config (max_life from kdc.conf) */
- krbMaxTicketLife = 86400; /* just set the default 24h for now */
- }
-
- krbTicketFlags = slapi_entry_attr_get_int(data->target, "krbTicketFlags");
-
- pwd.data = (char *)data->password;
- pwd.length = strlen(data->password);
-
- kset = malloc(sizeof(struct ipapwd_keyset));
- if (!kset) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n");
- goto enc_error;
- }
-
- /* this encoding assumes all keys have the same kvno */
- /* major-vno = 1 and minor-vno = 1 */
- kset->major_vno = 1;
- kset->minor_vno = 1;
- /* increment kvno (will be 1 if this is a new entry) */
- kset->kvno = kvno + 1;
- /* we also assum mkvno is 0 */
- kset->mkvno = 0;
-
- kset->num_keys = krbcfg->num_pref_encsalts;
- kset->keys = calloc(kset->num_keys, sizeof(struct ipapwd_krbkey));
- if (!kset->keys) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n");
- goto enc_error;
- }
-
- for (i = 0; i < kset->num_keys; i++) {
- krb5_keyblock key;
- krb5_data salt;
- krb5_octet *ptr;
- krb5_data plain;
- krb5_enc_data cipher;
- size_t len;
- const char *p;
-
- salt.data = NULL;
-
- switch (krbcfg->pref_encsalts[i].salt_type) {
-
- case KRB5_KDB_SALTTYPE_ONLYREALM:
-
- p = strchr(krbPrincipalName, '@');
- if (!p) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "Invalid principal name, no realm found!\n");
- goto enc_error;
- }
- p++;
- salt.data = strdup(p);
- if (!salt.data) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "memory allocation failed\n");
- goto enc_error;
- }
- salt.length = strlen(salt.data); /* final \0 omitted on purpose */
- break;
-
- case KRB5_KDB_SALTTYPE_NOREALM:
-
- krberr = krb5_principal2salt_norealm(krbctx, princ, &salt);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "krb5_principal2salt failed [%s]\n",
- krb5_get_error_message(krbctx, krberr));
- goto enc_error;
- }
- break;
-
- case KRB5_KDB_SALTTYPE_NORMAL:
-
- /* If pre auth is required we can set a random salt, otherwise
- * we have to use a more conservative approach and set the salt
- * to be REALMprincipal (the concatenation of REALM and principal
- * name without any separator) */
-#if 0
- if (krbTicketFlags & KTF_REQUIRES_PRE_AUTH) {
- salt.length = KRB5P_SALT_SIZE;
- salt.data = malloc(KRB5P_SALT_SIZE);
- if (!salt.data) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "memory allocation failed\n");
- goto enc_error;
- }
- krberr = krb5_c_random_make_octets(krbctx, &salt);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "krb5_c_random_make_octets failed [%s]\n",
- krb5_get_error_message(krbctx, krberr));
- goto enc_error;
- }
- } else {
-#endif
- krberr = krb5_principal2salt(krbctx, princ, &salt);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "krb5_principal2salt failed [%s]\n",
- krb5_get_error_message(krbctx, krberr));
- goto enc_error;
- }
-#if 0
- }
-#endif
- break;
-
- case KRB5_KDB_SALTTYPE_V4:
- salt.length = 0;
- break;
-
- case KRB5_KDB_SALTTYPE_AFS3:
-
- p = strchr(krbPrincipalName, '@');
- if (!p) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "Invalid principal name, no realm found!\n");
- goto enc_error;
- }
- p++;
- salt.data = strdup(p);
- if (!salt.data) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "memory allocation failed\n");
- goto enc_error;
- }
- salt.length = SALT_TYPE_AFS_LENGTH; /* special value */
- break;
-
- default:
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "Invalid salt type [%d]\n", krbcfg->pref_encsalts[i].salt_type);
- goto enc_error;
- }
-
- /* need to build the key now to manage the AFS salt.length special case */
- krberr = krb5_c_string_to_key(krbctx, krbcfg->pref_encsalts[i].enc_type, &pwd, &salt, &key);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "krb5_c_string_to_key failed [%s]\n",
- krb5_get_error_message(krbctx, krberr));
- krb5_free_data_contents(krbctx, &salt);
- goto enc_error;
- }
- if (salt.length == SALT_TYPE_AFS_LENGTH) {
- salt.length = strlen(salt.data);
- }
-
- krberr = krb5_c_encrypt_length(krbctx, krbcfg->kmkey->enctype, key.length, &len);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "krb5_c_string_to_key failed [%s]\n",
- krb5_get_error_message(krbctx, krberr));
- krb5int_c_free_keyblock_contents(krbctx, &key);
- krb5_free_data_contents(krbctx, &salt);
- goto enc_error;
- }
-
- if ((ptr = (krb5_octet *) malloc(2 + len)) == NULL) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "memory allocation failed\n");
- krb5int_c_free_keyblock_contents(krbctx, &key);
- krb5_free_data_contents(krbctx, &salt);
- goto enc_error;
- }
-
- encode_int16(key.length, ptr);
-
- plain.length = key.length;
- plain.data = (char *)key.contents;
-
- cipher.ciphertext.length = len;
- cipher.ciphertext.data = (char *)ptr+2;
-
- krberr = krb5_c_encrypt(krbctx, krbcfg->kmkey, 0, 0, &plain, &cipher);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "krb5_c_encrypt failed [%s]\n",
- krb5_get_error_message(krbctx, krberr));
- krb5int_c_free_keyblock_contents(krbctx, &key);
- krb5_free_data_contents(krbctx, &salt);
- free(ptr);
- goto enc_error;
- }
-
- /* KrbSalt */
- kset->keys[i].salt = malloc(sizeof(struct ipapwd_krbkeydata));
- if (!kset->keys[i].salt) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n");
- krb5int_c_free_keyblock_contents(krbctx, &key);
- free(ptr);
- goto enc_error;
- }
-
- kset->keys[i].salt->type = krbcfg->pref_encsalts[i].salt_type;
-
- if (salt.length) {
- kset->keys[i].salt->value.bv_len = salt.length;
- kset->keys[i].salt->value.bv_val = salt.data;
- }
-
- /* EncryptionKey */
- kset->keys[i].ekey = malloc(sizeof(struct ipapwd_krbkeydata));
- if (!kset->keys[i].ekey) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n");
- krb5int_c_free_keyblock_contents(krbctx, &key);
- free(ptr);
- goto enc_error;
- }
- kset->keys[i].ekey->type = key.enctype;
- kset->keys[i].ekey->value.bv_len = len+2;
- kset->keys[i].ekey->value.bv_val = malloc(len+2);
- if (!kset->keys[i].ekey->value.bv_val) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n");
- krb5int_c_free_keyblock_contents(krbctx, &key);
- free(ptr);
- goto enc_error;
- }
- memcpy(kset->keys[i].ekey->value.bv_val, ptr, len+2);
-
- /* make sure we free the memory used now that we are done with it */
- krb5int_c_free_keyblock_contents(krbctx, &key);
- free(ptr);
- }
-
- bval = encode_keys(kset);
- if (!bval) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "encoding asn1 KrbSalt failed\n");
- goto enc_error;
- }
-
- svals[0] = slapi_value_new_berval(bval);
- if (!svals[0]) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
- "Converting berval to Slapi_Value\n");
- goto enc_error;
- }
-
- ipapwd_keyset_free(&kset);
- krb5_free_principal(krbctx, princ);
- slapi_ch_free_string(&krbPrincipalName);
- ber_bvfree(bval);
- return svals;
-
-enc_error:
- *errMesg = "key encryption/encoding failed\n";
- if (kset) ipapwd_keyset_free(&kset);
- krb5_free_principal(krbctx, princ);
- slapi_ch_free_string(&krbPrincipalName);
- if (bval) ber_bvfree(bval);
- free(svals);
- return NULL;
-}
-
-static void ipapwd_free_slapi_value_array(Slapi_Value ***svals)
-{
- Slapi_Value **sv = *svals;
- int i;
-
- if (sv) {
- for (i = 0; sv[i]; i++) {
- slapi_value_free(&sv[i]);
- }
- }
-
- slapi_ch_free((void **)sv);
-}
-
-
-struct ntlm_keys {
- uint8_t lm[16];
- uint8_t nt[16];
-};
-
-#define KTF_LM_HASH 0x01
-#define KTF_NT_HASH 0x02
-#define KTF_DOS_CHARSET "CP850" /* same default as samba */
-#define KTF_UTF8 "UTF-8"
-#define KTF_UCS2 "UCS-2LE"
-
-static const uint8_t parity_table[128] = {
- 1, 2, 4, 7, 8, 11, 13, 14, 16, 19, 21, 22, 25, 26, 28, 31,
- 32, 35, 37, 38, 41, 42, 44, 47, 49, 50, 52, 55, 56, 59, 61, 62,
- 64, 67, 69, 70, 73, 74, 76, 79, 81, 82, 84, 87, 88, 91, 93, 94,
- 97, 98,100,103,104,107,109,110,112,115,117,118,121,122,124,127,
- 128,131,133,134,137,138,140,143,145,146,148,151,152,155,157,158,
- 161,162,164,167,168,171,173,174,176,179,181,182,185,186,188,191,
- 193,194,196,199,200,203,205,206,208,211,213,214,217,218,220,223,
- 224,227,229,230,233,234,236,239,241,242,244,247,248,251,253,254};
-
-static void lm_shuffle(uint8_t *out, uint8_t *in)
-{
- out[0] = parity_table[in[0]>>1];
- out[1] = parity_table[((in[0]<<6)|(in[1]>>2)) & 0x7F];
- out[2] = parity_table[((in[1]<<5)|(in[2]>>3)) & 0x7F];
- out[3] = parity_table[((in[2]<<4)|(in[3]>>4)) & 0x7F];
- out[4] = parity_table[((in[3]<<3)|(in[4]>>5)) & 0x7F];
- out[5] = parity_table[((in[4]<<2)|(in[5]>>6)) & 0x7F];
- out[6] = parity_table[((in[5]<<1)|(in[6]>>7)) & 0x7F];
- out[7] = parity_table[in[6] & 0x7F];
-}
-
-/* create the lm and nt hashes
- newPassword: the clear text utf8 password
- flags: KTF_LM_HASH | KTF_NT_HASH
-*/
-static int encode_ntlm_keys(char *newPasswd, unsigned int flags, struct ntlm_keys *keys)
-{
- int ret = 0;
-
- /* do lanman first */
- if (flags & KTF_LM_HASH) {
- iconv_t cd;
- size_t cs, il, ol;
- char *inc, *outc;
- char *upperPasswd;
- char *asciiPasswd;
- DES_key_schedule schedule;
- DES_cblock deskey;
- DES_cblock magic = "KGS!@#$%";
-
- /* TODO: must store the dos charset somewhere in the directory */
- cd = iconv_open(KTF_DOS_CHARSET, KTF_UTF8);
- if (cd == (iconv_t)(-1)) {
- ret = -1;
- goto done;
- }
-
- /* the lanman password is upper case */
- upperPasswd = (char *)slapi_utf8StrToUpper((unsigned char *)newPasswd);
- if (!upperPasswd) {
- ret = -1;
- goto done;
- }
- il = strlen(upperPasswd);
-
- /* an ascii string can only be smaller than or equal to an utf8 one */
- ol = il;
- if (ol < 14) ol = 14;
- asciiPasswd = calloc(ol+1, 1);
- if (!asciiPasswd) {
- slapi_ch_free_string(&upperPasswd);
- ret = -1;
- goto done;
- }
-
- inc = upperPasswd;
- outc = asciiPasswd;
- cs = iconv(cd, &inc, &il, &outc, &ol);
- if (cs == -1) {
- ret = -1;
- slapi_ch_free_string(&upperPasswd);
- free(asciiPasswd);
- iconv_close(cd);
- goto done;
- }
-
- /* done with these */
- slapi_ch_free_string(&upperPasswd);
- iconv_close(cd);
-
- /* we are interested only in the first 14 ASCII chars for lanman */
- if (strlen(asciiPasswd) > 14) {
- asciiPasswd[14] = '\0';
- }
-
- /* first half */
- lm_shuffle(deskey, (uint8_t *)asciiPasswd);
-
- DES_set_key_unchecked(&deskey, &schedule);
- DES_ecb_encrypt(&magic, (DES_cblock *)keys->lm, &schedule, DES_ENCRYPT);
-
- /* second half */
- lm_shuffle(deskey, (uint8_t *)&asciiPasswd[7]);
-
- DES_set_key_unchecked(&deskey, &schedule);
- DES_ecb_encrypt(&magic, (DES_cblock *)&(keys->lm[8]), &schedule, DES_ENCRYPT);
-
- /* done with it */
- free(asciiPasswd);
-
- } else {
- memset(keys->lm, 0, 16);
- }
-
- if (flags & KTF_NT_HASH) {
- iconv_t cd;
- size_t cs, il, ol, sl;
- char *inc, *outc;
- char *ucs2Passwd;
- MD4_CTX md4ctx;
-
- /* TODO: must store the dos charset somewhere in the directory */
- cd = iconv_open(KTF_UCS2, KTF_UTF8);
- if (cd == (iconv_t)(-1)) {
- ret = -1;
- goto done;
- }
-
- il = strlen(newPasswd);
-
- /* an ucs2 string can be at most double than an utf8 one */
- sl = ol = (il+1)*2;
- ucs2Passwd = calloc(ol, 1);
- if (!ucs2Passwd) {
- ret = -1;
- goto done;
- }
-
- inc = newPasswd;
- outc = ucs2Passwd;
- cs = iconv(cd, &inc, &il, &outc, &ol);
- if (cs == -1) {
- ret = -1;
- free(ucs2Passwd);
- iconv_close(cd);
- goto done;
- }
-
- /* done with it */
- iconv_close(cd);
-
- /* get the final ucs2 string length */
- sl -= ol;
- /* we are interested only in the first 14 wchars for the nt password */
- if (sl > 28) {
- sl = 28;
- }
-
- ret = MD4_Init(&md4ctx);
- if (ret == 0) {
- ret = -1;
- free(ucs2Passwd);
- goto done;
- }
- ret = MD4_Update(&md4ctx, ucs2Passwd, sl);
- if (ret == 0) {
- ret = -1;
- free(ucs2Passwd);
- goto done;
- }
- ret = MD4_Final(keys->nt, &md4ctx);
- if (ret == 0) {
- ret = -1;
- free(ucs2Passwd);
- goto done;
- }
-
- } else {
- memset(keys->nt, 0, 16);
- }
-
- ret = 0;
-
-done:
- return ret;
-}
-
-/* Easier handling for virtual attributes. You must call pwd_values_free()
- * to free memory allocated here. It must be called before
- * slapi_free_search_results_internal(entries) or
- * slapi_pblock_destroy(pb)
- */
-static int
-pwd_get_values(const Slapi_Entry *ent, const char *attrname,
- Slapi_ValueSet** results, char** actual_type_name,
- int *buffer_flags)
-{
- int flags=0;
- int type_name_disposition = 0;
-
- int ret = slapi_vattr_values_get((Slapi_Entry *)ent, (char *)attrname, results, &type_name_disposition, actual_type_name, flags, buffer_flags);
-
- return ret;
-}
-
-static void
-pwd_values_free(Slapi_ValueSet** results, char** actual_type_name, int buffer_flags)
-{
- slapi_vattr_values_free(results, actual_type_name, buffer_flags);
-}
-
-/* searches the directory and finds the policy closest to the DN */
-/* return 0 on success, -1 on error or if no policy is found */
-static int ipapwd_getPolicy(const char *dn, Slapi_Entry *target, Slapi_Entry **e)
-{
- const char *krbPwdPolicyReference;
- const char *pdn;
- const Slapi_DN *psdn;
- Slapi_Backend *be;
- Slapi_PBlock *pb = NULL;
- char *attrs[] = { "krbMaxPwdLife", "krbMinPwdLife",
- "krbPwdMinDiffChars", "krbPwdMinLength",
- "krbPwdHistoryLength", NULL};
- Slapi_Entry **es = NULL;
- Slapi_Entry *pe = NULL;
- char **edn;
- int ret, res, dist, rdnc, scope, i;
- Slapi_DN *sdn = NULL;
- int buffer_flags=0;
- Slapi_ValueSet* results = NULL;
- char* actual_type_name = NULL;
-
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_getPolicy: Searching policy for [%s]\n", dn);
-
- sdn = slapi_sdn_new_dn_byref(dn);
- if (sdn == NULL) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_getPolicy: Out of memory on [%s]\n", dn);
- ret = -1;
- goto done;
- }
-
- pwd_get_values(target, "krbPwdPolicyReference", &results, &actual_type_name, &buffer_flags);
- if (results) {
- Slapi_Value *sv;
- slapi_valueset_first_value(results, &sv);
- krbPwdPolicyReference = slapi_value_get_string(sv);
- pdn = krbPwdPolicyReference;
- scope = LDAP_SCOPE_BASE;
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_getPolicy: using policy reference: %s\n", pdn);
- } else {
- /* Find ancestor base DN */
- be = slapi_be_select(sdn);
- psdn = slapi_be_getsuffix(be, 0);
- if (psdn == NULL) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_getPolicy: Invalid DN [%s]\n", dn);
- ret = -1;
- goto done;
- }
- pdn = slapi_sdn_get_dn(psdn);
- scope = LDAP_SCOPE_SUBTREE;
- }
-
- *e = NULL;
-
- pb = slapi_pblock_new();
- slapi_search_internal_set_pb (pb,
- pdn, scope,
- "(objectClass=krbPwdPolicy)",
- attrs, 0,
- NULL, /* Controls */
- NULL, /* UniqueID */
- ipapwd_plugin_id,
- 0); /* Flags */
-
- /* do search the tree */
- ret = slapi_search_internal_pb(pb);
- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
- if (ret == -1 || res != LDAP_SUCCESS) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_getPolicy: Couldn't find policy, err (%d)\n",
- res?res:ret);
- ret = -1;
- goto done;
- }
-
- /* get entries */
- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es);
- if (!es) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_getPolicy: No entries ?!");
- ret = -1;
- goto done;
- }
-
- /* count entries */
- for (i = 0; es[i]; i++) /* count */ ;
-
- /* if there is only one, return that */
- if (i == 1) {
- *e = slapi_entry_dup(es[0]);
-
- ret = 0;
- goto done;
- }
-
- /* count number of RDNs in DN */
- edn = ldap_explode_dn(dn, 0);
- if (!edn) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_getPolicy: ldap_explode_dn(dn) failed ?!");
- ret = -1;
- goto done;
- }
- for (rdnc = 0; edn[rdnc]; rdnc++) /* count */ ;
- ldap_value_free(edn);
-
- pe = NULL;
- dist = -1;
-
- /* find closest entry */
- for (i = 0; es[i]; i++) {
- const Slapi_DN *esdn;
-
- esdn = slapi_entry_get_sdn_const(es[i]);
- if (esdn == NULL) continue;
- if (0 == slapi_sdn_compare(esdn, sdn)) {
- pe = es[i];
- dist = 0;
- break;
- }
- if (slapi_sdn_issuffix(sdn, esdn)) {
- const char *dn1;
- char **e1;
- int c1;
-
- dn1 = slapi_sdn_get_dn(esdn);
- if (!dn1) continue;
- e1 = ldap_explode_dn(dn1, 0);
- if (!e1) continue;
- for (c1 = 0; e1[c1]; c1++) /* count */ ;
- ldap_value_free(e1);
- if ((dist == -1) ||
- ((rdnc - c1) < dist)) {
- dist = rdnc - c1;
- pe = es[i];
- }
- }
- if (dist == 0) break; /* found closest */
- }
-
- if (pe == NULL) {
- ret = -1;
- goto done;
- }
-
- *e = slapi_entry_dup(pe);
- ret = 0;
-done:
- if (results) {
- pwd_values_free(&results, &actual_type_name, buffer_flags);
- }
- if (pb) {
- slapi_free_search_results_internal(pb);
- slapi_pblock_destroy(pb);
- }
- if (sdn) slapi_sdn_free(&sdn);
- return ret;
-}
-
-#define GENERALIZED_TIME_LENGTH 15
-
-static int ipapwd_sv_pw_cmp(const void *pv1, const void *pv2)
-{
- const char *pw1 = slapi_value_get_string(*((Slapi_Value **)pv1));
- const char *pw2 = slapi_value_get_string(*((Slapi_Value **)pv2));
-
- return strncmp(pw1, pw2, GENERALIZED_TIME_LENGTH);
-}
-
-static Slapi_Value **ipapwd_setPasswordHistory(Slapi_Mods *smods, struct ipapwd_data *data)
-{
- Slapi_Value **pH = NULL;
- Slapi_Attr *passwordHistory = NULL;
- char timestr[GENERALIZED_TIME_LENGTH+1];
- char *histr, *old_pw;
- struct tm utctime;
- int ret, pc;
-
- old_pw = slapi_entry_attr_get_charptr(data->target, "userPassword");
- if (!old_pw) {
- /* no old password to store, just return */
- return NULL;
- }
-
- if (!gmtime_r(&(data->timeNow), &utctime)) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "failed to retrieve current date (buggy gmtime_r ?)\n");
- return NULL;
- }
- strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime);
-
- histr = slapi_ch_smprintf("%s%s", timestr, old_pw);
- if (!histr) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "ipapwd_checkPassword: Out of Memory\n");
- return NULL;
- }
-
- /* retrieve current history */
- ret = slapi_entry_attr_find(data->target, "passwordHistory", &passwordHistory);
- if (ret == 0) {
- int ret, hint, count, i;
- const char *pwstr;
- Slapi_Value *pw;
-
- hint = 0;
- count = 0;
- ret = slapi_attr_get_numvalues(passwordHistory, &count);
- /* if we have one */
- if (count > 0 && data->pwHistoryLen > 0) {
- pH = calloc(count + 2, sizeof(Slapi_Value *));
- if (!pH) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "ipapwd_checkPassword: Out of Memory\n");
- free(histr);
- return NULL;
- }
-
- i = 0;
- hint = slapi_attr_first_value(passwordHistory, &pw);
- while (hint != -1) {
- pwstr = slapi_value_get_string(pw);
- /* if shorter than GENERALIZED_TIME_LENGTH, it
- * is garbage, we never set timeless entries */
- if (pwstr &&
- (strlen(pwstr) > GENERALIZED_TIME_LENGTH)) {
- pH[i] = pw;
- i++;
- }
- hint = slapi_attr_next_value(passwordHistory, hint, &pw);
- }
-
- qsort(pH, i, sizeof(Slapi_Value *), ipapwd_sv_pw_cmp);
-
- if (i >= data->pwHistoryLen) {
- i = data->pwHistoryLen;
- pH[i] = NULL;
- i--;
- }
-
- pc = i;
-
- /* copy only interesting entries */
- for (i = 0; i < pc; i++) {
- pH[i] = slapi_value_dup(pH[i]);
- if (pH[i] == NULL) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "ipapwd_checkPassword: Out of Memory\n");
- while (i) {
- i--;
- slapi_value_free(&pH[i]);
- }
- free(pH);
- free(histr);
- return NULL;
- }
- }
- }
- }
-
- if (pH == NULL) {
- pH = calloc(2, sizeof(Slapi_Value *));
- if (!pH) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "ipapwd_checkPassword: Out of Memory\n");
- free(histr);
- return NULL;
- }
- pc = 0;
- }
-
- /* add new history value */
- pH[pc] = slapi_value_new_string(histr);
-
- free(histr);
-
- return pH;
-}
-
-static Slapi_Value *ipapwd_strip_pw_date(Slapi_Value *pw)
-{
- const char *pwstr;
-
- pwstr = slapi_value_get_string(pw);
- return slapi_value_new_string(&pwstr[GENERALIZED_TIME_LENGTH]);
-}
-
-#define IPAPWD_POLICY_MASK 0x0FF
-#define IPAPWD_POLICY_ERROR 0x100
-#define IPAPWD_POLICY_OK 0
-
-/* 90 days default pwd max lifetime */
-#define IPAPWD_DEFAULT_PWDLIFE (90 * 24 *3600)
-#define IPAPWD_DEFAULT_MINLEN 0
-
-/* check password strenght and history */
-static int ipapwd_CheckPolicy(struct ipapwd_data *data)
-{
- char *krbPrincipalExpiration = NULL;
- char *krbLastPwdChange = NULL;
- char *krbPasswordExpiration = NULL;
- int krbMaxPwdLife = IPAPWD_DEFAULT_PWDLIFE;
- int krbPwdMinLength = IPAPWD_DEFAULT_MINLEN;
- int krbPwdMinDiffChars = 0;
- int krbMinPwdLife = 0;
- int pwdCharLen = 0;
- Slapi_Entry *policy = NULL;
- Slapi_Attr *passwordHistory = NULL;
- struct tm tm;
- int tmp, ret;
- char *old_pw;
-
- /* check account is not expired. Ignore unixtime = 0 (Jan 1 1970) */
- krbPrincipalExpiration = slapi_entry_attr_get_charptr(data->target, "krbPrincipalExpiration");
- if (krbPrincipalExpiration &&
- (strcasecmp("19700101000000Z", krbPrincipalExpiration) != 0)) {
- /* if expiration date is set check it */
- memset(&tm, 0, sizeof(struct tm));
- ret = sscanf(krbPrincipalExpiration,
- "%04u%02u%02u%02u%02u%02u",
- &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
- &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
-
- if (ret == 6) {
- tm.tm_year -= 1900;
- tm.tm_mon -= 1;
-
- if (data->timeNow > timegm(&tm)) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "Account Expired");
- return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDMODNOTALLOWED;
- }
- }
- /* FIXME: else error out ? */
- }
- slapi_ch_free_string(&krbPrincipalExpiration);
-
- /* find the entry with the password policy */
- ret = ipapwd_getPolicy(data->dn, data->target, &policy);
- if (ret) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "No password policy");
- goto no_policy;
- }
-
- /* Retrieve Max History Len */
- data->pwHistoryLen = slapi_entry_attr_get_int(policy, "krbPwdHistoryLength");
-
- if (data->changetype != IPA_CHANGETYPE_NORMAL) {
- /* We must skip policy checks (Admin change) but
- * force a password change on the next login.
- * But not if Directory Manager */
- if (data->changetype == IPA_CHANGETYPE_ADMIN) {
- data->expireTime = data->timeNow;
- }
-
- /* skip policy checks */
- slapi_entry_free(policy);
- goto no_policy;
- }
-
- /* first of all check current password, if any */
- old_pw = slapi_entry_attr_get_charptr(data->target, "userPassword");
- if (old_pw) {
- Slapi_Value *cpw[2] = {NULL, NULL};
- Slapi_Value *pw;
-
- cpw[0] = slapi_value_new_string(old_pw);
- pw = slapi_value_new_string(data->password);
- if (!pw) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "ipapwd_checkPassword: Out of Memory\n");
- slapi_entry_free(policy);
- slapi_ch_free_string(&old_pw);
- slapi_value_free(&cpw[0]);
- slapi_value_free(&pw);
- return LDAP_OPERATIONS_ERROR;
- }
-
- ret = slapi_pw_find_sv(cpw, pw);
- slapi_ch_free_string(&old_pw);
- slapi_value_free(&cpw[0]);
- slapi_value_free(&pw);
-
- if (ret == 0) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_checkPassword: Password in history\n");
- slapi_entry_free(policy);
- return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDINHISTORY;
- }
- }
-
- krbPasswordExpiration = slapi_entry_attr_get_charptr(data->target, "krbPasswordExpiration");
- krbLastPwdChange = slapi_entry_attr_get_charptr(data->target, "krbLastPwdChange");
- /* if no previous change, it means this is probably a new account
- * or imported, log and just ignore */
- if (krbLastPwdChange) {
-
- memset(&tm, 0, sizeof(struct tm));
- ret = sscanf(krbLastPwdChange,
- "%04u%02u%02u%02u%02u%02u",
- &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
- &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
-
- if (ret == 6) {
- tm.tm_year -= 1900;
- tm.tm_mon -= 1;
- data->lastPwChange = timegm(&tm);
- }
- /* FIXME: *else* report an error ? */
- } else {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "Warning: Last Password Change Time is not available\n");
- }
-
- /* Check min age */
- krbMinPwdLife = slapi_entry_attr_get_int(policy, "krbMinPwdLife");
- /* if no default then treat it as no limit */
- if (krbMinPwdLife != 0) {
-
- /* check for reset cases */
- if (krbLastPwdChange == NULL ||
- ((krbPasswordExpiration != NULL) &&
- strcmp(krbPasswordExpiration, krbLastPwdChange) == 0)) {
- /* Expiration and last change time are the same or
- * missing this happens only when a password is reset
- * by an admin or the account is new or no expiration
- * policy is set, PASS */
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_checkPolicy: Ignore krbMinPwdLife Expiration, not enough info\n");
-
- } else if (data->timeNow < data->lastPwChange + krbMinPwdLife) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_checkPolicy: Too soon to change password\n");
- slapi_entry_free(policy);
- slapi_ch_free_string(&krbPasswordExpiration);
- slapi_ch_free_string(&krbLastPwdChange);
- return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDTOOYOUNG;
- }
- }
-
- /* free strings or we leak them */
- slapi_ch_free_string(&krbPasswordExpiration);
- slapi_ch_free_string(&krbLastPwdChange);
-
- /* Retrieve min length */
- tmp = slapi_entry_attr_get_int(policy, "krbPwdMinLength");
- if (tmp != 0) {
- krbPwdMinLength = tmp;
- }
-
- /* check complexity */
- /* FIXME: this code is partially based on Directory Server code,
- * the plan is to merge this code later making it available
- * trough a pulic DS API for slapi plugins */
- krbPwdMinDiffChars = slapi_entry_attr_get_int(policy, "krbPwdMinDiffChars");
- if (krbPwdMinDiffChars != 0) {
- int num_digits = 0;
- int num_alphas = 0;
- int num_uppers = 0;
- int num_lowers = 0;
- int num_specials = 0;
- int num_8bit = 0;
- int num_repeated = 0;
- int max_repeated = 0;
- int num_categories = 0;
- char *p, *pwd;
-
- pwd = strdup(data->password);
-
- /* check character types */
- p = pwd;
- while ( p && *p )
- {
- if ( ldap_utf8isdigit( p ) ) {
- num_digits++;
- } else if ( ldap_utf8isalpha( p ) ) {
- num_alphas++;
- if ( slapi_utf8isLower( (unsigned char *)p ) ) {
- num_lowers++;
- } else {
- num_uppers++;
- }
- } else {
- /* check if this is an 8-bit char */
- if ( *p & 128 ) {
- num_8bit++;
- } else {
- num_specials++;
- }
- }
-
- /* check for repeating characters. If this is the
- first char of the password, no need to check */
- if ( pwd != p ) {
- int len = ldap_utf8len( p );
- char *prev_p = ldap_utf8prev( p );
-
- if ( len == ldap_utf8len( prev_p ) )
- {
- if ( memcmp( p, prev_p, len ) == 0 )
- {
- num_repeated++;
- if ( max_repeated < num_repeated ) {
- max_repeated = num_repeated;
- }
- } else {
- num_repeated = 0;
- }
- } else {
- num_repeated = 0;
- }
- }
-
- p = ldap_utf8next( p );
- }
-
- free(pwd);
- p = pwd = NULL;
-
- /* tally up the number of character categories */
- if ( num_digits > 0 )
- ++num_categories;
- if ( num_uppers > 0 )
- ++num_categories;
- if ( num_lowers > 0 )
- ++num_categories;
- if ( num_specials > 0 )
- ++num_categories;
- if ( num_8bit > 0 )
- ++num_categories;
-
- /* FIXME: the kerberos plicy schema does not define separated threshold values,
- * so just treat anything as a category, we will fix this when we merge
- * with DS policies */
-
- if (max_repeated > 1)
- --num_categories;
-
- if (num_categories < krbPwdMinDiffChars) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_checkPassword: Password not complex enough\n");
- slapi_entry_free(policy);
- return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_INVALIDPWDSYNTAX;
- }
- }
-
- /* Check password history */
- ret = slapi_entry_attr_find(data->target, "passwordHistory", &passwordHistory);
- if (ret == 0) {
- int ret, hint, count, i, j;
- const char *pwstr;
- Slapi_Value **pH;
- Slapi_Value *pw;
-
- hint = 0;
- count = 0;
- ret = slapi_attr_get_numvalues(passwordHistory, &count);
- /* check history only if we have one */
- if (count > 0 && data->pwHistoryLen > 0) {
- pH = calloc(count + 2, sizeof(Slapi_Value *));
- if (!pH) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "ipapwd_checkPassword: Out of Memory\n");
- slapi_entry_free(policy);
- return LDAP_OPERATIONS_ERROR;
- }
-
- i = 0;
- hint = slapi_attr_first_value(passwordHistory, &pw);
- while (hint != -1) {
- pwstr = slapi_value_get_string(pw);
- /* if shorter than GENERALIZED_TIME_LENGTH, it
- * is garbage, we never set timeless entries */
- if (pwstr &&
- (strlen(pwstr) > GENERALIZED_TIME_LENGTH)) {
- pH[i] = pw;
- i++;
- }
- hint = slapi_attr_next_value(passwordHistory, hint, &pw);
- }
-
- qsort(pH, i, sizeof(Slapi_Value *), ipapwd_sv_pw_cmp);
-
- if (i > data->pwHistoryLen) {
- i = data->pwHistoryLen;
- pH[i] = NULL;
- }
-
- for (j = 0; pH[j]; j++) {
- pH[j] = ipapwd_strip_pw_date(pH[j]);
- }
-
- pw = slapi_value_new_string(data->password);
- if (!pw) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "ipapwd_checkPassword: Out of Memory\n");
- slapi_entry_free(policy);
- free(pH);
- return LDAP_OPERATIONS_ERROR;
- }
-
- ret = slapi_pw_find_sv(pH, pw);
-
- for (j = 0; pH[j]; j++) {
- slapi_value_free(&pH[j]);
- }
- slapi_value_free(&pw);
- free(pH);
-
- if (ret == 0) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_checkPassword: Password in history\n");
- slapi_entry_free(policy);
- return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDINHISTORY;
- }
- }
- }
-
- /* Calculate max age */
- tmp = slapi_entry_attr_get_int(policy, "krbMaxPwdLife");
- if (tmp != 0) {
- krbMaxPwdLife = tmp;
- }
-
- slapi_entry_free(policy);
-
-no_policy:
-
- /* check min lenght */
- pwdCharLen = ldap_utf8characters(data->password);
-
- if (pwdCharLen < krbPwdMinLength) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_checkPassword: Password too short (%d < %d)\n", pwdCharLen, krbPwdMinLength);
- return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDTOOSHORT;
- }
-
- if (data->expireTime == 0) {
- data->expireTime = data->timeNow + krbMaxPwdLife;
- }
-
- return IPAPWD_POLICY_OK;
-}
-
-
-/* Searches the dn in directory,
- * If found : fills in slapi_entry structure and returns 0
- * If NOT found : returns the search result as LDAP_NO_SUCH_OBJECT
- */
-static int ipapwd_getEntry(const char *dn, Slapi_Entry **e2, char **attrlist)
-{
- Slapi_DN *sdn;
- int search_result = 0;
-
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_getEntry\n");
-
- sdn = slapi_sdn_new_dn_byref(dn);
- if ((search_result = slapi_search_internal_get_entry( sdn, attrlist, e2,
- ipapwd_plugin_id)) != LDAP_SUCCESS ){
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "ipapwd_getEntry: No such entry-(%s), err (%d)\n",
- dn, search_result);
- }
-
- slapi_sdn_free( &sdn );
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "<= ipapwd_getEntry: %d\n", search_result);
- return search_result;
-}
-
-
-/* Construct Mods pblock and perform the modify operation
- * Sets result of operation in SLAPI_PLUGIN_INTOP_RESULT
- */
-static int ipapwd_apply_mods(const char *dn, Slapi_Mods *mods)
-{
- Slapi_PBlock *pb;
- int ret;
-
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_apply_mods\n");
-
- if (!mods || (slapi_mods_get_num_mods(mods) == 0)) {
- return -1;
- }
-
- pb = slapi_pblock_new();
- slapi_modify_internal_set_pb (pb, dn,
- slapi_mods_get_ldapmods_byref(mods),
- NULL, /* Controls */
- NULL, /* UniqueID */
- ipapwd_plugin_id, /* PluginID */
- 0); /* Flags */
-
- ret = slapi_modify_internal_pb (pb);
- if (ret) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "WARNING: modify error %d on entry '%s'\n",
- ret, dn);
- } else {
-
- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
-
- if (ret != LDAP_SUCCESS){
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "WARNING: modify error %d on entry '%s'\n",
- ret, dn);
- } else {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "<= ipapwd_apply_mods: Successful\n");
- }
- }
-
- slapi_pblock_destroy(pb);
-
- return ret;
-}
-
-/* ascii hex output of bytes in "in"
- * out len is 32 (preallocated)
- * in len is 16 */
-static const char hexchars[] = "0123456789ABCDEF";
-static void hexbuf(char *out, const uint8_t *in)
-{
- int i;
-
- for (i = 0; i < 16; i++) {
- out[i*2] = hexchars[in[i] >> 4];
- out[i*2+1] = hexchars[in[i] & 0x0f];
- }
-}
-
-/* Modify the Password attributes of the entry */
-static int ipapwd_SetPassword(struct ipapwd_krbcfg *krbcfg,
- struct ipapwd_data *data,
- int is_krb)
-{
- int ret = 0;
- Slapi_Mods *smods;
- Slapi_Value **svals = NULL;
- Slapi_Value **pwvals = NULL;
- struct tm utctime;
- char timestr[GENERALIZED_TIME_LENGTH+1];
- krb5_context krbctx;
- krb5_error_code krberr;
- char lm[33], nt[33];
- struct ntlm_keys ntlm;
- int ntlm_flags = 0;
- Slapi_Value *sambaSamAccount;
- char *errMesg = NULL;
-
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_SetPassword\n");
-
- smods = slapi_mods_new();
-
- if (is_krb) {
- /* generate kerberos keys to be put into krbPrincipalKey */
- svals = encrypt_encode_key(krbcfg, data, &errMesg);
- if (!svals) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "key encryption/encoding failed\n");
- ret = LDAP_OPERATIONS_ERROR;
- goto free_and_return;
- }
-
- slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbPrincipalKey", svals);
-
- /* change Last Password Change field with the current date */
- if (!gmtime_r(&(data->timeNow), &utctime)) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "failed to retrieve current date (buggy gmtime_r ?)\n");
- ret = LDAP_OPERATIONS_ERROR;
- goto free_and_return;
- }
- strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime);
- slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbLastPwdChange", timestr);
-
- /* set Password Expiration date */
- if (!gmtime_r(&(data->expireTime), &utctime)) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "failed to convert expiration date\n");
- ret = LDAP_OPERATIONS_ERROR;
- goto free_and_return;
- }
- strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime);
- slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbPasswordExpiration", timestr);
- }
-
- sambaSamAccount = slapi_value_new_string("sambaSamAccount");
- if (slapi_entry_attr_has_syntax_value(data->target, "objectClass", sambaSamAccount)) {
- /* TODO: retrieve if we want to store the LM hash or not */
- ntlm_flags = KTF_LM_HASH | KTF_NT_HASH;
- }
- slapi_value_free(&sambaSamAccount);
-
- if (ntlm_flags) {
- char *password = strdup(data->password);
- if (encode_ntlm_keys(password, ntlm_flags, &ntlm) != 0) {
- free(password);
- ret = LDAP_OPERATIONS_ERROR;
- goto free_and_return;
- }
- if (ntlm_flags & KTF_LM_HASH) {
- hexbuf(lm, ntlm.lm);
- lm[32] = '\0';
- slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "sambaLMPassword", lm);
- }
- if (ntlm_flags & KTF_NT_HASH) {
- hexbuf(nt, ntlm.nt);
- nt[32] = '\0';
- slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "sambaNTPassword", nt);
- }
- free(password);
- }
-
- /* let DS encode the password itself, this allows also other plugins to
- * intercept it to perform operations like synchronization with Active
- * Directory domains through the replication plugin */
- slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "userPassword", data->password);
-
- /* set password history */
- pwvals = ipapwd_setPasswordHistory(smods, data);
- if (pwvals) {
- slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "passwordHistory", pwvals);
- }
-
- /* FIXME:
- * instead of replace we should use a delete/add so that we are
- * completely sure nobody else modified the entry meanwhile and
- * fail if that's the case */
-
- /* commit changes */
- ret = ipapwd_apply_mods(data->dn, smods);
-
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "<= ipapwd_SetPassword: %d\n", ret);
-
-free_and_return:
- slapi_mods_free(&smods);
- ipapwd_free_slapi_value_array(&svals);
- ipapwd_free_slapi_value_array(&pwvals);
-
- return ret;
-}
static int ipapwd_chpwop(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
{
@@ -2708,361 +983,6 @@ free_and_return:
return SLAPI_PLUGIN_EXTENDED_SENT_RESULT;
}
-static int new_ipapwd_encsalt(krb5_context krbctx, const char * const *encsalts,
- struct ipapwd_encsalt **es_types, int *num_es_types)
-{
- struct ipapwd_encsalt *es;
- int nes, i;
-
- for (i = 0; encsalts[i]; i++) /* count */ ;
- es = calloc(i + 1, sizeof(struct ipapwd_encsalt));
- if (!es) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_start", "Out of memory!\n");
- return LDAP_OPERATIONS_ERROR;
- }
-
- for (i = 0, nes = 0; encsalts[i]; i++) {
- char *enc, *salt;
- krb5_int32 tmpsalt;
- krb5_enctype tmpenc;
- krb5_boolean similar;
- krb5_error_code krberr;
- int j;
-
- enc = strdup(encsalts[i]);
- if (!enc) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_start",
- "Allocation error\n");
- return LDAP_OPERATIONS_ERROR;
- }
- salt = strchr(enc, ':');
- if (!salt) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_start",
- "Invalid krb5 enc string\n");
- free(enc);
- continue;
- }
- *salt = '\0'; /* null terminate the enc type */
- salt++; /* skip : */
-
- krberr = krb5_string_to_enctype(enc, &tmpenc);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_start",
- "Invalid krb5 enctype\n");
- free(enc);
- continue;
- }
-
- krberr = krb5_string_to_salttype(salt, &tmpsalt);
- for (j = 0; j < nes; j++) {
- krb5_c_enctype_compare(krbctx, es[j].enc_type, tmpenc, &similar);
- if (similar && (es[j].salt_type == tmpsalt)) {
- break;
- }
- }
-
- if (j == nes) {
- /* not found */
- es[j].enc_type = tmpenc;
- es[j].salt_type = tmpsalt;
- nes++;
- }
-
- free(enc);
- }
-
- *es_types = es;
- *num_es_types = nes;
-
- return LDAP_SUCCESS;
-}
-
-static struct ipapwd_krbcfg *ipapwd_getConfig(void)
-{
- krb5_error_code krberr;
- struct ipapwd_krbcfg *config = NULL;
- krb5_keyblock *kmkey = NULL;
- Slapi_Entry *realm_entry = NULL;
- Slapi_Entry *config_entry = NULL;
- Slapi_Attr *a;
- Slapi_Value *v;
- BerElement *be = NULL;
- ber_tag_t tag, tmp;
- ber_int_t ttype;
- const struct berval *bval;
- struct berval *mkey = NULL;
- char **encsalts;
- char *tmpstr;
- int i, ret;
-
- config = calloc(1, sizeof(struct ipapwd_krbcfg));
- if (!config) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "Out of memory!\n");
- goto free_and_error;
- }
- kmkey = calloc(1, sizeof(krb5_keyblock));
- if (!kmkey) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "Out of memory!\n");
- goto free_and_error;
- }
- config->kmkey = kmkey;
-
- krberr = krb5_init_context(&config->krbctx);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "krb5_init_context failed\n");
- goto free_and_error;
- }
-
- ret = krb5_get_default_realm(config->krbctx, &config->realm);
- if (ret) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "Failed to get default realm?!\n");
- goto free_and_error;
- }
-
- /* get the Realm Container entry */
- ret = ipapwd_getEntry(ipa_realm_dn, &realm_entry, NULL);
- if (ret != LDAP_SUCCESS) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "No realm Entry?\n");
- goto free_and_error;
- }
-
- /*** get the Kerberos Master Key ***/
-
- ret = slapi_entry_attr_find(realm_entry, "krbMKey", &a);
- if (ret == -1) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "No master key??\n");
- goto free_and_error;
- }
-
- /* there should be only one value here */
- ret = slapi_attr_first_value(a, &v);
- if (ret == -1) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "No master key??\n");
- goto free_and_error;
- }
-
- bval = slapi_value_get_berval(v);
- if (!bval) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "Error retrieving master key berval\n");
- goto free_and_error;
- }
-
- be = ber_init(bval);
- if (!bval) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "ber_init() failed!\n");
- goto free_and_error;
- }
-
- tag = ber_scanf(be, "{i{iO}}", &tmp, &ttype, &mkey);
- if (tag == LBER_ERROR) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig",
- "Bad Master key encoding ?!\n");
- goto free_and_error;
- }
-
- kmkey->magic = KV5M_KEYBLOCK;
- kmkey->enctype = ttype;
- kmkey->length = mkey->bv_len;
- kmkey->contents = malloc(mkey->bv_len);
- if (!kmkey->contents) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "Out of memory!\n");
- goto free_and_error;
- }
- memcpy(kmkey->contents, mkey->bv_val, mkey->bv_len);
- ber_bvfree(mkey);
- ber_free(be, 1);
- mkey = NULL;
- be = NULL;
-
- /*** get the Supported Enc/Salt types ***/
-
- encsalts = slapi_entry_attr_get_charray(realm_entry, "krbSupportedEncSaltTypes");
- if (encsalts) {
- ret = new_ipapwd_encsalt(config->krbctx,
- (const char * const *)encsalts,
- &config->supp_encsalts,
- &config->num_supp_encsalts);
- slapi_ch_array_free(encsalts);
- } else {
- slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig",
- "No configured salt types use defaults\n");
- ret = new_ipapwd_encsalt(config->krbctx,
- ipapwd_def_encsalts,
- &config->supp_encsalts,
- &config->num_supp_encsalts);
- }
- if (ret) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "Can't get Supported EncSalt Types\n");
- goto free_and_error;
- }
-
- /*** get the Preferred Enc/Salt types ***/
-
- encsalts = slapi_entry_attr_get_charray(realm_entry, "krbDefaultEncSaltTypes");
- if (encsalts) {
- ret = new_ipapwd_encsalt(config->krbctx,
- (const char * const *)encsalts,
- &config->pref_encsalts,
- &config->num_pref_encsalts);
- slapi_ch_array_free(encsalts);
- } else {
- slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig",
- "No configured salt types use defaults\n");
- ret = new_ipapwd_encsalt(config->krbctx,
- ipapwd_def_encsalts,
- &config->pref_encsalts,
- &config->num_pref_encsalts);
- }
- if (ret) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "Can't get Preferred EncSalt Types\n");
- goto free_and_error;
- }
-
- slapi_entry_free(realm_entry);
-
- /* get the Realm Container entry */
- ret = ipapwd_getEntry(ipa_pwd_config_dn, &config_entry, NULL);
- if (ret != LDAP_SUCCESS) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "No config Entry? Impossible!\n");
- goto free_and_error;
- }
- config->passsync_mgrs = slapi_entry_attr_get_charray(config_entry, "passSyncManagersDNs");
- /* now add Directory Manager, it is always added by default */
- tmpstr = slapi_ch_strdup("cn=Directory Manager");
- slapi_ch_array_add(&config->passsync_mgrs, tmpstr);
- if (config->passsync_mgrs == NULL) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
- "Out of memory!\n");
- goto free_and_error;
- }
- for (i = 0; config->passsync_mgrs[i]; i++) /* count */ ;
- config->num_passsync_mgrs = i;
-
- return config;
-
-free_and_error:
- if (mkey) ber_bvfree(mkey);
- if (be) ber_free(be, 1);
- if (kmkey) {
- free(kmkey->contents);
- free(kmkey);
- }
- if (config) {
- if (config->krbctx) {
- if (config->realm)
- krb5_free_default_realm(config->krbctx, config->realm);
- krb5_free_context(config->krbctx);
- }
- free(config->pref_encsalts);
- free(config->supp_encsalts);
- slapi_ch_array_free(config->passsync_mgrs);
- free(config);
- }
- slapi_entry_free(config_entry);
- slapi_entry_free(realm_entry);
- return NULL;
-}
-
-#define IPAPWD_CHECK_CONN_SECURE 0x00000001
-#define IPAPWD_CHECK_DN 0x00000002
-
-static int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg,
- struct ipapwd_krbcfg **config,
- int check_flags)
-{
- int ret, sasl_ssf, is_ssl;
- int rc = LDAP_SUCCESS;
- Slapi_Backend *be;
- const Slapi_DN *psdn;
- Slapi_DN *sdn;
- char *dn = NULL;
-
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_gen_checks\n");
-
-#ifdef LDAP_EXTOP_PASSMOD_CONN_SECURE
- if (check_flags & IPAPWD_CHECK_CONN_SECURE) {
- /* Allow password modify only for SSL/TLS established connections and
- * connections using SASL privacy layers */
- if (slapi_pblock_get(pb, SLAPI_CONN_SASL_SSF, &sasl_ssf) != 0) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "Could not get SASL SSF from connection\n");
- *errMesg = "Operation requires a secure connection.\n";
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
-
- if (slapi_pblock_get(pb, SLAPI_CONN_IS_SSL_SESSION, &is_ssl) != 0) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "Could not get IS SSL from connection\n");
- *errMesg = "Operation requires a secure connection.\n";
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
-
- if ((0 == is_ssl) && (sasl_ssf <= 1)) {
- *errMesg = "Operation requires a secure connection.\n";
- rc = LDAP_CONFIDENTIALITY_REQUIRED;
- goto done;
- }
- }
-#endif
-
- if (check_flags & IPAPWD_CHECK_DN) {
- /* check we have a valid DN in the pblock or just abort */
- ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
- if (ret) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "Tried to change password for an invalid DN [%s]\n",
- dn?dn:"<NULL>");
- *errMesg = "Invalid DN";
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- sdn = slapi_sdn_new_dn_byref(dn);
- if (!sdn) {
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
- "Unable to convert dn to sdn %s", dn?dn:"<NULL>");
- *errMesg = "Internal Error";
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- be = slapi_be_select(sdn);
- slapi_sdn_free(&sdn);
-
- psdn = slapi_be_getsuffix(be, 0);
- if (!psdn) {
- *errMesg = "Invalid DN";
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- }
-
- /* get the kerberos context and master key */
- *config = ipapwd_getConfig();
- if (NULL == *config) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "Error Retrieving Master Key");
- *errMesg = "Fatal Internal Error";
- rc = LDAP_OPERATIONS_ERROR;
- }
-
-done:
- return rc;
-}
-
static int ipapwd_extop(Slapi_PBlock *pb)
{
struct ipapwd_krbcfg *krbcfg = NULL;
@@ -3114,1089 +1034,6 @@ free_and_return:
return SLAPI_PLUGIN_EXTENDED_SENT_RESULT;
}
-/*****************************************************************************
- * pre/post operations to intercept writes to userPassword
- ****************************************************************************/
-
-#define IPAPWD_OP_NULL 0
-#define IPAPWD_OP_ADD 1
-#define IPAPWD_OP_MOD 2
-struct ipapwd_operation {
- struct ipapwd_data pwdata;
- int pwd_op;
- int is_krb;
-};
-
-/* 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 */
-};
-
-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);
-}
-
-static int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e,
- int *is_root, int *is_krb, int *is_smb,
- char *attr, int access)
-{
- Slapi_Value *sval;
- int rc;
-
- /* Check ACIs */
- slapi_pblock_get(pb, SLAPI_REQUESTOR_ISROOT, is_root);
-
- if (!*is_root) {
- /* verify this user is allowed to write a user password */
- rc = slapi_access_allowed(pb, e, attr, NULL, access);
- if (rc != LDAP_SUCCESS) {
- /* we have no business here, the operation will be denied anyway */
- rc = LDAP_SUCCESS;
- goto done;
- }
- }
-
- /* Check if this is a krbPrincial and therefore needs us to generate other
- * hashes */
- sval = slapi_value_new_string("krbPrincipalAux");
- if (!sval) {
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- *is_krb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval);
- slapi_value_free(&sval);
-
- sval = slapi_value_new_string("sambaSamAccount");
- if (!sval) {
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- *is_smb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval);
- slapi_value_free(&sval);
-
- rc = LDAP_SUCCESS;
-
-done:
- return rc;
-}
-
-static int ipapwd_preop_gen_hashes(struct ipapwd_krbcfg *krbcfg,
- struct ipapwd_operation *pwdop,
- char *userpw,
- int is_krb, int is_smb,
- Slapi_Value ***svals,
- char **nthash, char **lmhash,
- char **errMesg)
-{
- int rc;
-
- if (is_krb) {
-
- pwdop->is_krb = 1;
-
- *svals = encrypt_encode_key(krbcfg, &pwdop->pwdata, errMesg);
-
- if (!*svals) {
- /* errMesg should have been set in encrypt_encode_key() */
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "key encryption/encoding failed\n");
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- }
-
- if (is_smb) {
- char lm[33], nt[33];
- struct ntlm_keys ntlm;
- int ntlm_flags = 0;
- int ret;
-
- /* TODO: retrieve if we want to store the LM hash or not */
- ntlm_flags = KTF_LM_HASH | KTF_NT_HASH;
-
- ret = encode_ntlm_keys(userpw, ntlm_flags, &ntlm);
- if (ret) {
- *errMesg = "Failed to generate NT/LM hashes\n";
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- *errMesg);
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- if (ntlm_flags & KTF_LM_HASH) {
- hexbuf(lm, ntlm.lm);
- lm[32] = '\0';
- *lmhash = slapi_ch_strdup(lm);
- }
- if (ntlm_flags & KTF_NT_HASH) {
- hexbuf(nt, ntlm.nt);
- nt[32] = '\0';
- *nthash = slapi_ch_strdup(nt);
- }
- }
-
- rc = LDAP_SUCCESS;
-
-done:
-
- return rc;
-}
-
-/* PRE BIND Operation:
- * Used for password migration from DS to IPA.
- * Gets the clean text password, authenticates the user and generates
- * a kerberos key if missing.
- * Person to blame if anything blows up: Pavel Zuna <pzuna@redhat.com>
- */
-static int ipapwd_pre_bind(Slapi_PBlock *pb)
-{
- struct ipapwd_krbcfg *krbcfg = NULL;
- struct ipapwd_data pwdata;
- struct berval *credentials; /* bind credentials */
- Slapi_Entry *entry = NULL;
- Slapi_Value **pwd_values = NULL; /* values of userPassword attribute */
- Slapi_Value *value = NULL;
- Slapi_Attr *attr = NULL;
- struct tm expire_tm;
- time_t expire_time;
- char *errMesg = "Internal operations error\n"; /* error message */
- char *expire = NULL; /* passwordExpirationTime attribute value */
- char *dn = NULL; /* bind DN */
- Slapi_Value *objectclass;
- int method; /* authentication method */
- int ret = 0;
-
- slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
- "=> ipapwd_pre_bind\n");
-
- /* get BIND parameters */
- ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn);
- ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
- ret |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &credentials);
- if (ret) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_pre_bind",
- "slapi_pblock_get failed!?\n");
- goto done;
- }
-
- /* we're only interested in simple authentication */
- if (method != LDAP_AUTH_SIMPLE)
- goto done;
-
- /* list of attributes to retrieve */
- const char *attrs_list[] = {SLAPI_USERPWD_ATTR, "krbprincipalkey", "uid",
- "krbprincipalname", "objectclass",
- "passwordexpirationtime", "passwordhistory",
- NULL};
-
- /* retrieve user entry */
- ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list);
- if (ret) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
- "failed to retrieve user entry: %s\n", dn);
- goto done;
- }
-
- /* check the krbPrincipalName attribute is present */
- ret = slapi_entry_attr_find(entry, "krbprincipalname", &attr);
- if (ret) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
- "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) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
- "kerberos key already present in user entry: %s\n", dn);
- goto done;
- }
-
- /* retrieve userPassword attribute */
- ret = slapi_entry_attr_find(entry, SLAPI_USERPWD_ATTR, &attr);
- if (ret) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
- "no " SLAPI_USERPWD_ATTR " in user entry: %s\n", dn);
- goto done;
- }
-
- /* 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 */
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "out of memory!?\n");
- goto done;
- }
- /* 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) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
- "invalid BIND password for user entry: %s\n", dn);
- goto done;
- }
-
- /* general checks */
- ret = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
- if (ret) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_pre_bind",
- "ipapwd_gen_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) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
- "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 = mktime(&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. */
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
- "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) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
- "failed to set kerberos key for user entry: %s\n", dn);
- goto done;
- }
-
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
- "kerberos key generated for user entry: %s\n", dn);
-
-done:
- slapi_ch_free_string(&expire);
- if (entry)
- slapi_entry_free(entry);
- free_ipapwd_krbcfg(&krbcfg);
-
- return 0;
-}
-
-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;
- int ret;
-
- dn = slapi_ch_smprintf("cn=ipaconfig,cn=etc,%s", ipa_realm_tree);
- if (!dn) {
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "Out of memory ?\n");
- goto done;
- }
-
- ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list);
- if (ret) {
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "failed to retrieve config entry: %s\n", dn);
- goto done;
- }
-
- value = slapi_entry_attr_get_charptr(entry, attr);
-
-done:
- slapi_entry_free(entry);
- slapi_ch_free_string(&dn);
- return value;
-}
-
-/* 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;
- char *dn = NULL;
- struct ipapwd_operation *pwdop = NULL;
- void *op;
- int is_repl_op, is_root, is_krb, is_smb;
- int ret;
- int rc = LDAP_SUCCESS;
-
- slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME, "=> ipapwd_pre_add\n");
-
- ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
- if (ret != 0) {
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "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) {
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "Strdup failed, Out of memory\n");
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- slapi_ch_free_string(&userpw);
- userpw = tmp;
- } else if (slapi_is_encoded(userpw)) {
- /* check if we have access to the unhashed user password */
- char *userpw_clear =
- slapi_entry_attr_get_charptr(e, "unhashed#user#password");
-
- /* unhashed#user#password doesn't always contain the clear text
- * password, therefore we need to check if its value isn't the same
- * as userPassword to make sure */
- if (!userpw_clear || (0 == strcmp(userpw, userpw_clear))) {
- rc = LDAP_CONSTRAINT_VIOLATION;
- slapi_ch_free_string(&userpw);
- } else {
- userpw = slapi_ch_strdup(userpw_clear);
- }
-
- slapi_ch_free_string(&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) {
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "no ipaMigrationEnabled in config;"
- " assuming FALSE\n");
- } else if (0 == strcmp(enabled, "TRUE")) {
- return 0;
- }
-
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "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,
- 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_DN, &dn);
- 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) {
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "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);
-
- 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(dn);
- pwdop->pwdata.timeNow = time(NULL);
- pwdop->pwdata.target = e;
-
- ret = ipapwd_CheckPolicy(&pwdop->pwdata);
- if (ret) {
- errMesg = "Password Fails to meet minimum strength criteria";
- rc = LDAP_CONSTRAINT_VIOLATION;
- goto done;
- }
-
- if (is_krb || is_smb) {
-
- Slapi_Value **svals = NULL;
- char *nt = NULL;
- char *lm = NULL;
-
- rc = ipapwd_preop_gen_hashes(krbcfg,
- pwdop, userpw,
- is_krb, is_smb,
- &svals, &nt, &lm, &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) {
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "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 (lm) {
- /* set value */
- slapi_entry_attr_set_charptr(e, "sambaLMPassword", lm);
- slapi_ch_free_string(&lm);
- }
- if (nt) {
- /* set value */
- slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt);
- slapi_ch_free_string(&nt);
- }
- }
-
- 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;
-}
-
-/* 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;
- Slapi_Mod *smod, *tmod;
- Slapi_Mods *smods = NULL;
- char *userpw = NULL;
- char *unhashedpw = NULL;
- char *dn = NULL;
- Slapi_DN *tmp_dn;
- 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;
- int ret, rc;
-
- slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME, "=> ipapwd_pre_mod\n");
-
- ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
- if (ret != 0) {
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "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;
- tmod = slapi_mod_new();
- smod = slapi_mods_get_first_smod(smods, tmod);
- while (smod) {
- struct berval *bv;
- const char *type;
- int mop;
-
- type = slapi_mod_get_type(smod);
- if (slapi_attr_types_equivalent(type, SLAPI_USERPWD_ATTR)) {
- mop = slapi_mod_get_operation(smod);
- /* check op filtering out LDAP_MOD_BVALUES */
- switch (mop & 0x0f) {
- case LDAP_MOD_ADD:
- case LDAP_MOD_REPLACE:
- is_pwd_op = 1;
- default:
- break;
- }
- }
-
- /* 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 absurd
- * reason */
- if (slapi_attr_types_equivalent(type, "unhashed#user#password")) {
- bv = slapi_mod_get_first_value(smod);
- if (!bv) {
- slapi_mod_free(&tmod);
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- slapi_ch_free_string(&unhashedpw);
- unhashedpw = slapi_ch_malloc(bv->bv_len+1);
- if (!unhashedpw) {
- slapi_mod_free(&tmod);
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- memcpy(unhashedpw, bv->bv_val, bv->bv_len);
- unhashedpw[bv->bv_len] = '\0';
- }
- slapi_mod_done(tmod);
- smod = slapi_mods_get_next_smod(smods, tmod);
- }
- slapi_mod_free(&tmod);
-
- /* If userPassword is not modified we are done here */
- if (! is_pwd_op) {
- rc = LDAP_SUCCESS;
- goto done;
- }
-
- /* OK swe have something interesting here, start checking for
- * pre-requisites */
-
- /* Get target DN */
- ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
- if (ret) {
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
-
- tmp_dn = slapi_sdn_new_dn_byref(dn);
- if (tmp_dn) {
- /* 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_dn, 0, &e, ipapwd_plugin_id);
- slapi_sdn_free(&tmp_dn);
- if (ret != LDAP_SUCCESS) {
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "Failed tpo retrieve entry?!?\n");
- rc = LDAP_NO_SUCH_OBJECT;
- goto done;
- }
- }
-
- rc = ipapwd_entry_checks(pb, e,
- &is_root, &is_krb, &is_smb,
- SLAPI_USERPWD_ATTR, SLAPI_ACL_WRITE);
- if (rc) {
- goto done;
- }
-
- rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
- if (rc) {
- goto done;
- }
-
- /* run through the mods again and adjust flags if operations affect them */
- tmod = slapi_mod_new();
- smod = slapi_mods_get_first_smod(smods, tmod);
- while (smod) {
- struct berval *bv;
- const char *type;
- int mop;
-
- type = slapi_mod_get_type(smod);
- if (slapi_attr_types_equivalent(type, SLAPI_USERPWD_ATTR)) {
- mop = slapi_mod_get_operation(smod);
- /* check op filtering out LDAP_MOD_BVALUES */
- switch (mop & 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;
- bv = slapi_mod_get_first_value(smod);
- if (!bv) {
- slapi_mod_free(&tmod);
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- slapi_ch_free_string(&userpw);
- userpw = slapi_ch_malloc(bv->bv_len+1);
- if (!userpw) {
- slapi_mod_free(&tmod);
- 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 */
- bv = slapi_mod_get_first_value(smod);
- if (!bv) {
- is_pwd_op = 0;
- } else {
- if (0 == strncmp(userpw, bv->bv_val, bv->bv_len) ||
- 0 == strncmp(unhashedpw, bv->bv_val, bv->bv_len))
- is_pwd_op = 0;
- }
- default:
- break;
- }
- }
-
- if (slapi_attr_types_equivalent(type, SLAPI_ATTR_OBJECTCLASS)) {
- mop = slapi_mod_get_operation(smod);
- /* check op filtering out LDAP_MOD_BVALUES */
- switch (mop & 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;
-
- case LDAP_MOD_ADD:
- bv = slapi_mod_get_first_value(smod);
- if (!bv) {
- slapi_mod_free(&tmod);
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- do {
- if (0 == strncasecmp("krbPrincipalAux", bv->bv_val, bv->bv_len))
- is_krb = 1;
- if (0 == strncasecmp("sambaSamAccount", bv->bv_val, bv->bv_len))
- is_smb = 1;
- } while ((bv = slapi_mod_get_next_value(smod)) != NULL);
-
- break;
-
- case LDAP_MOD_DELETE:
- /* can this happen for objectclasses ? */
- is_krb = 0;
- is_smb = 0;
-
- default:
- break;
- }
- }
-
- slapi_mod_done(tmod);
- smod = slapi_mods_get_next_smod(smods, tmod);
- }
- slapi_mod_free(&tmod);
-
- /* 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) {
- if ('{' == userpw[0]) {
- if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) {
- unhashedpw = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]);
- if (NULL == unhashedpw) {
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "Strdup failed, Out of memory\n");
- rc = LDAP_OPERATIONS_ERROR;
- goto done;
- }
- slapi_ch_free_string(&userpw);
-
- } else if (slapi_is_encoded(userpw)) {
-
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "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) {
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "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_MOD;
- pwdop->pwdata.password = slapi_ch_strdup(unhashedpw);
- pwdop->pwdata.changetype = IPA_CHANGETYPE_NORMAL;
-
- 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_new_dn_byref(dn);
-
- /* 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(dn);
- pwdop->pwdata.timeNow = time(NULL);
- pwdop->pwdata.target = e;
-
- ret = ipapwd_CheckPolicy(&pwdop->pwdata);
- if (ret) {
- errMesg = "Password Fails to meet minimum strength criteria";
- rc = LDAP_CONSTRAINT_VIOLATION;
- goto done;
- }
-
- if (is_krb || is_smb) {
-
- Slapi_Value **svals = NULL;
- char *nt = NULL;
- char *lm = NULL;
-
- rc = ipapwd_preop_gen_hashes(krbcfg,
- pwdop, unhashedpw,
- is_krb, is_smb,
- &svals, &nt, &lm, &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 (lm) {
- /* replace value */
- slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
- "sambaLMPassword", lm);
- slapi_ch_free_string(&lm);
- }
- if (nt) {
- /* replace value */
- slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
- "sambaNTPassword", nt);
- slapi_ch_free_string(&nt);
- }
- }
-
- 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);
- slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods);
- 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_post_op(Slapi_PBlock *pb)
-{
- void *op;
- struct ipapwd_operation *pwdop = NULL;
- Slapi_Mods *smods;
- Slapi_Value **pwvals;
- struct tm utctime;
- char timestr[GENERALIZED_TIME_LENGTH+1];
- int ret;
-
- slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
- "=> ipapwd_post_op\n");
-
- /* time to get the operation handler */
- ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
- if (ret != 0) {
- slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
- "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) {
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "Internal error, couldn't find pluginextension ?!\n");
- return 0;
- }
-
- /* not interesting */
- if (IPAPWD_OP_NULL == pwdop->pwd_op)
- return 0;
-
- if ( ! (pwdop->is_krb)) {
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "Not a kerberos user, ignore krb attributes\n");
- return 0;
- }
-
- /* prepare changes that can be made only as root */
- smods = slapi_mods_new();
-
- /* change Last Password Change field with the current date */
- if (!gmtime_r(&(pwdop->pwdata.timeNow), &utctime)) {
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "failed to parse current date (buggy gmtime_r ?)\n");
- goto done;
- }
- strftime(timestr, GENERALIZED_TIME_LENGTH+1,
- "%Y%m%d%H%M%SZ", &utctime);
- slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
- "krbLastPwdChange", timestr);
-
- /* set Password Expiration date */
- if (!gmtime_r(&(pwdop->pwdata.expireTime), &utctime)) {
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "failed to parse expiration date (buggy gmtime_r ?)\n");
- goto done;
- }
- strftime(timestr, GENERALIZED_TIME_LENGTH+1,
- "%Y%m%d%H%M%SZ", &utctime);
- slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
- "krbPasswordExpiration", timestr);
-
- /* 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) {
- 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) {
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "Failed tpo retrieve entry?!?\n");
- goto done;
- }
- }
- pwvals = ipapwd_setPasswordHistory(smods, &pwdop->pwdata);
- if (pwvals) {
- slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
- "passwordHistory", pwvals);
- }
- }
-
- ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods);
- if (ret)
- slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
- "Failed to set additional password attributes in the post-op!\n");
-
-done:
- if (pwdop && pwdop->pwdata.target) slapi_entry_free(pwdop->pwdata.target);
- slapi_mods_free(&smods);
- return 0;
-}
-
/* Copied from ipamo_string2filter()
*
* ipapwd_string2filter()
@@ -4290,22 +1127,6 @@ done:
}
-static int ipapwd_ext_init()
-{
- 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_oid_list[] = {
EXOP_PASSWD_OID,
@@ -4320,33 +1141,6 @@ static char *ipapwd_name_list[] = {
NULL
};
-/* Init pre ops */
-static int ipapwd_pre_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 *)&pdesc);
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *)ipapwd_pre_bind);
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *)ipapwd_pre_add);
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod);
-
- return ret;
-}
-
-/* Init post ops */
-static 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 *)&pdesc);
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_op);
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op);
-
- return ret;
-}
-
/* Initialization function */
int ipapwd_init( Slapi_PBlock *pb )
{
@@ -4375,7 +1169,7 @@ int ipapwd_init( Slapi_PBlock *pb )
* plug-in */
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)ipapwd_start);
- if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc);
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipapwd_oid_list);
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipapwd_name_list);
if (!ret) slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)ipapwd_extop);
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
new file mode 100644
index 000000000..450e7100e
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
@@ -0,0 +1,162 @@
+/** BEGIN COPYRIGHT BLOCK
+ * This Program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; version 2 of the License.
+ *
+ * This Program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link to the code of this Program
+ * through those well defined interfaces identified in the file named EXCEPTION
+ * found in the source code files (the "Approved Interfaces"). The files of
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
+ * the Approved Interfaces without causing the resulting work to be covered by
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code
+ * used in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish
+ * to provide this exception without modification, you must delete this
+ * exception statement from your version and license this file solely under the
+ * GPL without exception.
+ *
+ * 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
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <prio.h>
+#include <ssl.h>
+#include <dirsrv/slapi-plugin.h>
+#define KRB5_PRIVATE 1
+#include <krb5.h>
+#include <lber.h>
+#include <time.h>
+#include <iconv.h>
+#include <openssl/des.h>
+#include <openssl/md4.h>
+
+#define IPAPWD_PLUGIN_NAME "ipa-pwd-extop"
+#define IPAPWD_FEATURE_DESC "IPA Password Manager"
+#define IPAPWD_PLUGIN_DESC "IPA Password Extended Operation plugin"
+
+#define IPAPWD_CHECK_CONN_SECURE 0x00000001
+#define IPAPWD_CHECK_DN 0x00000002
+
+#define IPA_CHANGETYPE_NORMAL 0
+#define IPA_CHANGETYPE_ADMIN 1
+#define IPA_CHANGETYPE_DSMGR 2
+
+struct ipapwd_data {
+ Slapi_Entry *target;
+ char *dn;
+ char *password;
+ time_t timeNow;
+ time_t lastPwChange;
+ time_t expireTime;
+ int changetype;
+ int pwHistoryLen;
+};
+
+struct ipapwd_operation {
+ struct ipapwd_data pwdata;
+ int pwd_op;
+ int is_krb;
+};
+
+#define GENERALIZED_TIME_LENGTH 15
+
+#define IPAPWD_POLICY_MASK 0x0FF
+#define IPAPWD_POLICY_ERROR 0x100
+#define IPAPWD_POLICY_OK 0
+
+
+/* from ipapwd_common.c */
+struct ipapwd_encsalt {
+ krb5_int32 enc_type;
+ krb5_int32 salt_type;
+};
+
+struct ipapwd_krbcfg {
+ krb5_context krbctx;
+ char *realm;
+ krb5_keyblock *kmkey;
+ int num_supp_encsalts;
+ struct ipapwd_encsalt *supp_encsalts;
+ int num_pref_encsalts;
+ struct ipapwd_encsalt *pref_encsalts;
+ char **passsync_mgrs;
+ int num_passsync_mgrs;
+};
+
+int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e,
+ int *is_root, int *is_krb, int *is_smb,
+ char *attr, int access);
+int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg,
+ struct ipapwd_krbcfg **config, int check_flags);
+int ipapwd_CheckPolicy(struct ipapwd_data *data);
+int ipapwd_getEntry(const char *dn, Slapi_Entry **e2, char **attrlist);
+int ipapwd_get_cur_kvno(Slapi_Entry *target);
+int ipapwd_SetPassword(struct ipapwd_krbcfg *krbcfg,
+ struct ipapwd_data *data, int is_krb);
+Slapi_Value **ipapwd_setPasswordHistory(Slapi_Mods *smods,
+ struct ipapwd_data *data);
+int ipapwd_apply_mods(const char *dn, Slapi_Mods *mods);
+void ipapwd_free_slapi_value_array(Slapi_Value ***svals);
+void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg);
+
+/* from ipapwd_encoding.c */
+struct ipapwd_krbkeydata {
+ int32_t type;
+ struct berval value;
+};
+struct ipapwd_krbkey {
+ struct ipapwd_krbkeydata *salt;
+ struct ipapwd_krbkeydata *ekey;
+ struct berval s2kparams;
+};
+struct ipapwd_keyset {
+ uint16_t major_vno;
+ uint16_t minor_vno;
+ uint32_t kvno;
+ uint32_t mkvno;
+ struct ipapwd_krbkey *keys;
+ int num_keys;
+};
+
+struct berval *encode_keys(struct ipapwd_keyset *kset);
+void ipapwd_keyset_free(struct ipapwd_keyset **pkset);
+
+int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg,
+ struct ipapwd_data *data, char *userpw,
+ int is_krb, int is_smb, Slapi_Value ***svals,
+ char **nthash, char **lmhash, char **errMesg);
+
+/* from ipapwd_prepost.c */
+int ipapwd_ext_init(void);
+int ipapwd_pre_init(Slapi_PBlock *pb);
+int ipapwd_post_init(Slapi_PBlock *pb);
+
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c
new file mode 100644
index 000000000..b1826dbb2
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c
@@ -0,0 +1,1427 @@
+/** BEGIN COPYRIGHT BLOCK
+ * This Program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; version 2 of the License.
+ *
+ * This Program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link to the code of this Program
+ * through those well defined interfaces identified in the file named EXCEPTION
+ * found in the source code files (the "Approved Interfaces"). The files of
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
+ * the Approved Interfaces without causing the resulting work to be covered by
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code
+ * used in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish
+ * to provide this exception without modification, you must delete this
+ * exception statement from your version and license this file solely under the
+ * GPL without exception.
+ *
+ * Authors:
+ * Simo Sorce <ssorce@redhat.com>
+ *
+ * Copyright (C) 2007-2010 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "ipapwd.h"
+
+/* Type of connection for this operation;*/
+#define LDAP_EXTOP_PASSMOD_CONN_SECURE
+
+/* Uncomment the following #undef FOR TESTING:
+ * allows non-SSL connections to use the password change extended op */
+/* #undef LDAP_EXTOP_PASSMOD_CONN_SECURE */
+
+extern void *ipapwd_plugin_id;
+extern const char *ipa_realm_dn;
+extern const char *ipa_pwd_config_dn;
+
+/* These are the default enc:salt types if nothing is defined.
+ * TODO: retrieve the configure set of ecntypes either from the
+ * kfc.conf file or by synchronizing the the file content into
+ * the directory */
+static const char *ipapwd_def_encsalts[] = {
+ "des3-hmac-sha1:normal",
+/* "arcfour-hmac:normal",
+ "des-hmac-sha1:normal",
+ "des-cbc-md5:normal", */
+ "des-cbc-crc:normal",
+/* "des-cbc-crc:v4",
+ "des-cbc-crc:afs3", */
+ NULL
+};
+
+static int new_ipapwd_encsalt(krb5_context krbctx,
+ const char * const *encsalts,
+ struct ipapwd_encsalt **es_types,
+ int *num_es_types)
+{
+ struct ipapwd_encsalt *es;
+ int nes, i;
+
+ for (i = 0; encsalts[i]; i++) /* count */ ;
+ es = calloc(i + 1, sizeof(struct ipapwd_encsalt));
+ if (!es) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Out of memory!\n");
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ for (i = 0, nes = 0; encsalts[i]; i++) {
+ char *enc, *salt;
+ krb5_int32 tmpsalt;
+ krb5_enctype tmpenc;
+ krb5_boolean similar;
+ krb5_error_code krberr;
+ int j;
+
+ enc = strdup(encsalts[i]);
+ if (!enc) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Allocation error\n");
+ return LDAP_OPERATIONS_ERROR;
+ }
+ salt = strchr(enc, ':');
+ if (!salt) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Invalid krb5 enc string\n");
+ free(enc);
+ continue;
+ }
+ *salt = '\0'; /* null terminate the enc type */
+ salt++; /* skip : */
+
+ krberr = krb5_string_to_enctype(enc, &tmpenc);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Invalid krb5 enctype\n");
+ free(enc);
+ continue;
+ }
+
+ krberr = krb5_string_to_salttype(salt, &tmpsalt);
+ for (j = 0; j < nes; j++) {
+ krb5_c_enctype_compare(krbctx, es[j].enc_type, tmpenc, &similar);
+ if (similar && (es[j].salt_type == tmpsalt)) {
+ break;
+ }
+ }
+
+ if (j == nes) {
+ /* not found */
+ es[j].enc_type = tmpenc;
+ es[j].salt_type = tmpsalt;
+ nes++;
+ }
+
+ free(enc);
+ }
+
+ *es_types = es;
+ *num_es_types = nes;
+
+ return LDAP_SUCCESS;
+}
+
+static struct ipapwd_krbcfg *ipapwd_getConfig(void)
+{
+ krb5_error_code krberr;
+ struct ipapwd_krbcfg *config = NULL;
+ krb5_keyblock *kmkey = NULL;
+ Slapi_Entry *realm_entry = NULL;
+ Slapi_Entry *config_entry = NULL;
+ Slapi_Attr *a;
+ Slapi_Value *v;
+ BerElement *be = NULL;
+ ber_tag_t tag, tmp;
+ ber_int_t ttype;
+ const struct berval *bval;
+ struct berval *mkey = NULL;
+ char **encsalts;
+ char *tmpstr;
+ int i, ret;
+
+ config = calloc(1, sizeof(struct ipapwd_krbcfg));
+ if (!config) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Out of memory!\n");
+ goto free_and_error;
+ }
+ kmkey = calloc(1, sizeof(krb5_keyblock));
+ if (!kmkey) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Out of memory!\n");
+ goto free_and_error;
+ }
+ config->kmkey = kmkey;
+
+ krberr = krb5_init_context(&config->krbctx);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "krb5_init_context failed\n");
+ goto free_and_error;
+ }
+
+ ret = krb5_get_default_realm(config->krbctx, &config->realm);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Failed to get default realm?!\n");
+ goto free_and_error;
+ }
+
+ /* get the Realm Container entry */
+ ret = ipapwd_getEntry(ipa_realm_dn, &realm_entry, NULL);
+ if (ret != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "No realm Entry?\n");
+ goto free_and_error;
+ }
+
+ /*** get the Kerberos Master Key ***/
+
+ ret = slapi_entry_attr_find(realm_entry, "krbMKey", &a);
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "No master key??\n");
+ goto free_and_error;
+ }
+
+ /* there should be only one value here */
+ ret = slapi_attr_first_value(a, &v);
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "No master key??\n");
+ goto free_and_error;
+ }
+
+ bval = slapi_value_get_berval(v);
+ if (!bval) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Error retrieving master key berval\n");
+ goto free_and_error;
+ }
+
+ be = ber_init(bval);
+ if (!bval) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "ber_init() failed!\n");
+ goto free_and_error;
+ }
+
+ tag = ber_scanf(be, "{i{iO}}", &tmp, &ttype, &mkey);
+ if (tag == LBER_ERROR) {
+ slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig",
+ "Bad Master key encoding ?!\n");
+ goto free_and_error;
+ }
+
+ kmkey->magic = KV5M_KEYBLOCK;
+ kmkey->enctype = ttype;
+ kmkey->length = mkey->bv_len;
+ kmkey->contents = malloc(mkey->bv_len);
+ if (!kmkey->contents) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Out of memory!\n");
+ goto free_and_error;
+ }
+ memcpy(kmkey->contents, mkey->bv_val, mkey->bv_len);
+ ber_bvfree(mkey);
+ ber_free(be, 1);
+ mkey = NULL;
+ be = NULL;
+
+ /*** get the Supported Enc/Salt types ***/
+
+ encsalts = slapi_entry_attr_get_charray(realm_entry, "krbSupportedEncSaltTypes");
+ if (encsalts) {
+ ret = new_ipapwd_encsalt(config->krbctx,
+ (const char * const *)encsalts,
+ &config->supp_encsalts,
+ &config->num_supp_encsalts);
+ slapi_ch_array_free(encsalts);
+ } else {
+ slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig",
+ "No configured salt types use defaults\n");
+ ret = new_ipapwd_encsalt(config->krbctx,
+ ipapwd_def_encsalts,
+ &config->supp_encsalts,
+ &config->num_supp_encsalts);
+ }
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Can't get Supported EncSalt Types\n");
+ goto free_and_error;
+ }
+
+ /*** get the Preferred Enc/Salt types ***/
+
+ encsalts = slapi_entry_attr_get_charray(realm_entry, "krbDefaultEncSaltTypes");
+ if (encsalts) {
+ ret = new_ipapwd_encsalt(config->krbctx,
+ (const char * const *)encsalts,
+ &config->pref_encsalts,
+ &config->num_pref_encsalts);
+ slapi_ch_array_free(encsalts);
+ } else {
+ slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig",
+ "No configured salt types use defaults\n");
+ ret = new_ipapwd_encsalt(config->krbctx,
+ ipapwd_def_encsalts,
+ &config->pref_encsalts,
+ &config->num_pref_encsalts);
+ }
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Can't get Preferred EncSalt Types\n");
+ goto free_and_error;
+ }
+
+ slapi_entry_free(realm_entry);
+
+ /* get the Realm Container entry */
+ ret = ipapwd_getEntry(ipa_pwd_config_dn, &config_entry, NULL);
+ if (ret != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "No config Entry? Impossible!\n");
+ goto free_and_error;
+ }
+ config->passsync_mgrs = slapi_entry_attr_get_charray(config_entry, "passSyncManagersDNs");
+ /* now add Directory Manager, it is always added by default */
+ tmpstr = slapi_ch_strdup("cn=Directory Manager");
+ slapi_ch_array_add(&config->passsync_mgrs, tmpstr);
+ if (config->passsync_mgrs == NULL) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Out of memory!\n");
+ goto free_and_error;
+ }
+ for (i = 0; config->passsync_mgrs[i]; i++) /* count */ ;
+ config->num_passsync_mgrs = i;
+
+ return config;
+
+free_and_error:
+ if (mkey) ber_bvfree(mkey);
+ if (be) ber_free(be, 1);
+ if (kmkey) {
+ free(kmkey->contents);
+ free(kmkey);
+ }
+ if (config) {
+ if (config->krbctx) {
+ if (config->realm)
+ krb5_free_default_realm(config->krbctx, config->realm);
+ krb5_free_context(config->krbctx);
+ }
+ free(config->pref_encsalts);
+ free(config->supp_encsalts);
+ slapi_ch_array_free(config->passsync_mgrs);
+ free(config);
+ }
+ slapi_entry_free(config_entry);
+ slapi_entry_free(realm_entry);
+ return NULL;
+}
+
+/* Easier handling for virtual attributes. You must call pwd_values_free()
+ * to free memory allocated here. It must be called before
+ * slapi_free_search_results_internal(entries) or
+ * slapi_pblock_destroy(pb)
+ */
+static int pwd_get_values(const Slapi_Entry *ent, const char *attrname,
+ Slapi_ValueSet** results, char** actual_type_name,
+ int *buffer_flags)
+{
+ int flags=0;
+ int type_name_disposition = 0;
+ int ret;
+
+ ret = slapi_vattr_values_get((Slapi_Entry *)ent, (char *)attrname,
+ results, &type_name_disposition,
+ actual_type_name, flags, buffer_flags);
+
+ return ret;
+}
+
+static void pwd_values_free(Slapi_ValueSet** results,
+ char** actual_type_name, int buffer_flags)
+{
+ slapi_vattr_values_free(results, actual_type_name, buffer_flags);
+}
+
+static int ipapwd_getPolicy(const char *dn,
+ Slapi_Entry *target, Slapi_Entry **e)
+{
+ const char *krbPwdPolicyReference;
+ const char *pdn;
+ const Slapi_DN *psdn;
+ Slapi_Backend *be;
+ Slapi_PBlock *pb = NULL;
+ char *attrs[] = { "krbMaxPwdLife", "krbMinPwdLife",
+ "krbPwdMinDiffChars", "krbPwdMinLength",
+ "krbPwdHistoryLength", NULL};
+ Slapi_Entry **es = NULL;
+ Slapi_Entry *pe = NULL;
+ char **edn;
+ int ret, res, dist, rdnc, scope, i;
+ Slapi_DN *sdn = NULL;
+ int buffer_flags=0;
+ Slapi_ValueSet* results = NULL;
+ char* actual_type_name = NULL;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: Searching policy for [%s]\n", dn);
+
+ sdn = slapi_sdn_new_dn_byref(dn);
+ if (sdn == NULL) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: Out of memory on [%s]\n", dn);
+ ret = -1;
+ goto done;
+ }
+
+ pwd_get_values(target, "krbPwdPolicyReference",
+ &results, &actual_type_name, &buffer_flags);
+ if (results) {
+ Slapi_Value *sv;
+ slapi_valueset_first_value(results, &sv);
+ krbPwdPolicyReference = slapi_value_get_string(sv);
+ pdn = krbPwdPolicyReference;
+ scope = LDAP_SCOPE_BASE;
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: using policy reference: %s\n", pdn);
+ } else {
+ /* Find ancestor base DN */
+ be = slapi_be_select(sdn);
+ psdn = slapi_be_getsuffix(be, 0);
+ if (psdn == NULL) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: Invalid DN [%s]\n", dn);
+ ret = -1;
+ goto done;
+ }
+ pdn = slapi_sdn_get_dn(psdn);
+ scope = LDAP_SCOPE_SUBTREE;
+ }
+
+ *e = NULL;
+
+ pb = slapi_pblock_new();
+ slapi_search_internal_set_pb(pb,
+ pdn, scope,
+ "(objectClass=krbPwdPolicy)",
+ attrs, 0,
+ NULL, /* Controls */
+ NULL, /* UniqueID */
+ ipapwd_plugin_id,
+ 0); /* Flags */
+
+ /* do search the tree */
+ ret = slapi_search_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ if (ret == -1 || res != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: Couldn't find policy, err (%d)\n",
+ res ? res : ret);
+ ret = -1;
+ goto done;
+ }
+
+ /* get entries */
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es);
+ if (!es) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: No entries ?!");
+ ret = -1;
+ goto done;
+ }
+
+ /* count entries */
+ for (i = 0; es[i]; i++) /* count */ ;
+
+ /* if there is only one, return that */
+ if (i == 1) {
+ *e = slapi_entry_dup(es[0]);
+
+ ret = 0;
+ goto done;
+ }
+
+ /* count number of RDNs in DN */
+ edn = ldap_explode_dn(dn, 0);
+ if (!edn) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: ldap_explode_dn(dn) failed ?!");
+ ret = -1;
+ goto done;
+ }
+ for (rdnc = 0; edn[rdnc]; rdnc++) /* count */ ;
+ ldap_value_free(edn);
+
+ pe = NULL;
+ dist = -1;
+
+ /* find closest entry */
+ for (i = 0; es[i]; i++) {
+ const Slapi_DN *esdn;
+
+ esdn = slapi_entry_get_sdn_const(es[i]);
+ if (esdn == NULL) continue;
+ if (0 == slapi_sdn_compare(esdn, sdn)) {
+ pe = es[i];
+ dist = 0;
+ break;
+ }
+ if (slapi_sdn_issuffix(sdn, esdn)) {
+ const char *dn1;
+ char **e1;
+ int c1;
+
+ dn1 = slapi_sdn_get_dn(esdn);
+ if (!dn1) continue;
+ e1 = ldap_explode_dn(dn1, 0);
+ if (!e1) continue;
+ for (c1 = 0; e1[c1]; c1++) /* count */ ;
+ ldap_value_free(e1);
+ if ((dist == -1) ||
+ ((rdnc - c1) < dist)) {
+ dist = rdnc - c1;
+ pe = es[i];
+ }
+ }
+ if (dist == 0) break; /* found closest */
+ }
+
+ if (pe == NULL) {
+ ret = -1;
+ goto done;
+ }
+
+ *e = slapi_entry_dup(pe);
+ ret = 0;
+done:
+ if (results) {
+ pwd_values_free(&results, &actual_type_name, buffer_flags);
+ }
+ if (pb) {
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+ }
+ if (sdn) slapi_sdn_free(&sdn);
+ return ret;
+}
+
+static Slapi_Value *ipapwd_strip_pw_date(Slapi_Value *pw)
+{
+ const char *pwstr;
+
+ pwstr = slapi_value_get_string(pw);
+ return slapi_value_new_string(&pwstr[GENERALIZED_TIME_LENGTH]);
+}
+
+/* ascii hex output of bytes in "in"
+ * out len is 32 (preallocated)
+ * in len is 16 */
+static const char hexchars[] = "0123456789ABCDEF";
+void hexbuf(char *out, const uint8_t *in)
+{
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ out[i*2] = hexchars[in[i] >> 4];
+ out[i*2+1] = hexchars[in[i] & 0x0f];
+ }
+}
+
+/* searches the directory and finds the policy closest to the DN */
+/* return 0 on success, -1 on error or if no policy is found */
+static int ipapwd_sv_pw_cmp(const void *pv1, const void *pv2)
+{
+ const char *pw1 = slapi_value_get_string(*((Slapi_Value **)pv1));
+ const char *pw2 = slapi_value_get_string(*((Slapi_Value **)pv2));
+
+ return strncmp(pw1, pw2, GENERALIZED_TIME_LENGTH);
+}
+
+
+/*==Common-public-functions=============================================*/
+
+int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e,
+ int *is_root, int *is_krb, int *is_smb,
+ char *attr, int access)
+{
+ Slapi_Value *sval;
+ int rc;
+
+ /* Check ACIs */
+ slapi_pblock_get(pb, SLAPI_REQUESTOR_ISROOT, is_root);
+
+ if (!*is_root) {
+ /* verify this user is allowed to write a user password */
+ rc = slapi_access_allowed(pb, e, attr, NULL, access);
+ if (rc != LDAP_SUCCESS) {
+ /* we have no business here, the operation will be denied anyway */
+ rc = LDAP_SUCCESS;
+ goto done;
+ }
+ }
+
+ /* Check if this is a krbPrincial and therefore needs us to generate other
+ * hashes */
+ sval = slapi_value_new_string("krbPrincipalAux");
+ if (!sval) {
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ *is_krb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval);
+ slapi_value_free(&sval);
+
+ sval = slapi_value_new_string("sambaSamAccount");
+ if (!sval) {
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ *is_smb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval);
+ slapi_value_free(&sval);
+
+ rc = LDAP_SUCCESS;
+
+done:
+ return rc;
+}
+
+int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg,
+ struct ipapwd_krbcfg **config, int check_flags)
+{
+ int ret, sasl_ssf, is_ssl;
+ int rc = LDAP_SUCCESS;
+ Slapi_Backend *be;
+ const Slapi_DN *psdn;
+ Slapi_DN *sdn;
+ char *dn = NULL;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_gen_checks\n");
+
+#ifdef LDAP_EXTOP_PASSMOD_CONN_SECURE
+ if (check_flags & IPAPWD_CHECK_CONN_SECURE) {
+ /* Allow password modify only for SSL/TLS established connections and
+ * connections using SASL privacy layers */
+ if (slapi_pblock_get(pb, SLAPI_CONN_SASL_SSF, &sasl_ssf) != 0) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Could not get SASL SSF from connection\n");
+ *errMesg = "Operation requires a secure connection.\n";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ if (slapi_pblock_get(pb, SLAPI_CONN_IS_SSL_SESSION, &is_ssl) != 0) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Could not get IS SSL from connection\n");
+ *errMesg = "Operation requires a secure connection.\n";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ if ((0 == is_ssl) && (sasl_ssf <= 1)) {
+ *errMesg = "Operation requires a secure connection.\n";
+ rc = LDAP_CONFIDENTIALITY_REQUIRED;
+ goto done;
+ }
+ }
+#endif
+
+ if (check_flags & IPAPWD_CHECK_DN) {
+ /* check we have a valid DN in the pblock or just abort */
+ ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Tried to change password for an invalid DN "
+ "[%s]\n", dn ? dn : "<NULL>");
+ *errMesg = "Invalid DN";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ sdn = slapi_sdn_new_dn_byref(dn);
+ if (!sdn) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "Unable to convert dn to sdn %s",
+ dn ? dn : "<NULL>");
+ *errMesg = "Internal Error";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ be = slapi_be_select(sdn);
+ slapi_sdn_free(&sdn);
+
+ psdn = slapi_be_getsuffix(be, 0);
+ if (!psdn) {
+ *errMesg = "Invalid DN";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ }
+
+ /* get the kerberos context and master key */
+ *config = ipapwd_getConfig();
+ if (NULL == *config) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Error Retrieving Master Key");
+ *errMesg = "Fatal Internal Error";
+ rc = LDAP_OPERATIONS_ERROR;
+ }
+
+done:
+ return rc;
+}
+
+/* 90 days default pwd max lifetime */
+#define IPAPWD_DEFAULT_PWDLIFE (90 * 24 *3600)
+#define IPAPWD_DEFAULT_MINLEN 0
+
+/* check password strenght and history */
+int ipapwd_CheckPolicy(struct ipapwd_data *data)
+{
+ char *krbPrincipalExpiration = NULL;
+ char *krbLastPwdChange = NULL;
+ char *krbPasswordExpiration = NULL;
+ int krbMaxPwdLife = IPAPWD_DEFAULT_PWDLIFE;
+ int krbPwdMinLength = IPAPWD_DEFAULT_MINLEN;
+ int krbPwdMinDiffChars = 0;
+ int krbMinPwdLife = 0;
+ int pwdCharLen = 0;
+ Slapi_Entry *policy = NULL;
+ Slapi_Attr *passwordHistory = NULL;
+ struct tm tm;
+ int tmp, ret;
+ char *old_pw;
+
+ /* check account is not expired. Ignore unixtime = 0 (Jan 1 1970) */
+ krbPrincipalExpiration =
+ slapi_entry_attr_get_charptr(data->target, "krbPrincipalExpiration");
+ if (krbPrincipalExpiration &&
+ (strcasecmp("19700101000000Z", krbPrincipalExpiration) != 0)) {
+ /* if expiration date is set check it */
+ memset(&tm, 0, sizeof(struct tm));
+ ret = sscanf(krbPrincipalExpiration,
+ "%04u%02u%02u%02u%02u%02u",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+
+ if (ret == 6) {
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+
+ if (data->timeNow > timegm(&tm)) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "Account Expired");
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDMODNOTALLOWED;
+ }
+ }
+ /* FIXME: else error out ? */
+ }
+ slapi_ch_free_string(&krbPrincipalExpiration);
+
+ /* find the entry with the password policy */
+ ret = ipapwd_getPolicy(data->dn, data->target, &policy);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "No password policy");
+ goto no_policy;
+ }
+
+ /* Retrieve Max History Len */
+ data->pwHistoryLen =
+ slapi_entry_attr_get_int(policy, "krbPwdHistoryLength");
+
+ if (data->changetype != IPA_CHANGETYPE_NORMAL) {
+ /* We must skip policy checks (Admin change) but
+ * force a password change on the next login.
+ * But not if Directory Manager */
+ if (data->changetype == IPA_CHANGETYPE_ADMIN) {
+ data->expireTime = data->timeNow;
+ }
+
+ /* skip policy checks */
+ slapi_entry_free(policy);
+ goto no_policy;
+ }
+
+ /* first of all check current password, if any */
+ old_pw = slapi_entry_attr_get_charptr(data->target, "userPassword");
+ if (old_pw) {
+ Slapi_Value *cpw[2] = {NULL, NULL};
+ Slapi_Value *pw;
+
+ cpw[0] = slapi_value_new_string(old_pw);
+ pw = slapi_value_new_string(data->password);
+ if (!pw) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ slapi_entry_free(policy);
+ slapi_ch_free_string(&old_pw);
+ slapi_value_free(&cpw[0]);
+ slapi_value_free(&pw);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ ret = slapi_pw_find_sv(cpw, pw);
+ slapi_ch_free_string(&old_pw);
+ slapi_value_free(&cpw[0]);
+ slapi_value_free(&pw);
+
+ if (ret == 0) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Password in history\n");
+ slapi_entry_free(policy);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDINHISTORY;
+ }
+ }
+
+ krbPasswordExpiration =
+ slapi_entry_attr_get_charptr(data->target, "krbPasswordExpiration");
+ krbLastPwdChange =
+ slapi_entry_attr_get_charptr(data->target, "krbLastPwdChange");
+ /* if no previous change, it means this is probably a new account
+ * or imported, log and just ignore */
+ if (krbLastPwdChange) {
+
+ memset(&tm, 0, sizeof(struct tm));
+ ret = sscanf(krbLastPwdChange,
+ "%04u%02u%02u%02u%02u%02u",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+
+ if (ret == 6) {
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+ data->lastPwChange = timegm(&tm);
+ }
+ /* FIXME: *else* report an error ? */
+ } else {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "Warning: Last Password Change Time is not available\n");
+ }
+
+ /* Check min age */
+ krbMinPwdLife = slapi_entry_attr_get_int(policy, "krbMinPwdLife");
+ /* if no default then treat it as no limit */
+ if (krbMinPwdLife != 0) {
+
+ /* check for reset cases */
+ if (krbLastPwdChange == NULL ||
+ ((krbPasswordExpiration != NULL) &&
+ strcmp(krbPasswordExpiration, krbLastPwdChange) == 0)) {
+ /* Expiration and last change time are the same or
+ * missing this happens only when a password is reset
+ * by an admin or the account is new or no expiration
+ * policy is set, PASS */
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPolicy: Ignore krbMinPwdLife "
+ "Expiration, not enough info\n");
+
+ } else if (data->timeNow < data->lastPwChange + krbMinPwdLife) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPolicy: Too soon to change password\n");
+ slapi_entry_free(policy);
+ slapi_ch_free_string(&krbPasswordExpiration);
+ slapi_ch_free_string(&krbLastPwdChange);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDTOOYOUNG;
+ }
+ }
+
+ /* free strings or we leak them */
+ slapi_ch_free_string(&krbPasswordExpiration);
+ slapi_ch_free_string(&krbLastPwdChange);
+
+ /* Retrieve min length */
+ tmp = slapi_entry_attr_get_int(policy, "krbPwdMinLength");
+ if (tmp != 0) {
+ krbPwdMinLength = tmp;
+ }
+
+ /* check complexity */
+ /* FIXME: this code is partially based on Directory Server code,
+ * the plan is to merge this code later making it available
+ * trough a pulic DS API for slapi plugins */
+ krbPwdMinDiffChars =
+ slapi_entry_attr_get_int(policy, "krbPwdMinDiffChars");
+ if (krbPwdMinDiffChars != 0) {
+ int num_digits = 0;
+ int num_alphas = 0;
+ int num_uppers = 0;
+ int num_lowers = 0;
+ int num_specials = 0;
+ int num_8bit = 0;
+ int num_repeated = 0;
+ int max_repeated = 0;
+ int num_categories = 0;
+ char *p, *pwd;
+
+ pwd = strdup(data->password);
+
+ /* check character types */
+ p = pwd;
+ while (p && *p) {
+ if (ldap_utf8isdigit(p)) {
+ num_digits++;
+ } else if (ldap_utf8isalpha(p)) {
+ num_alphas++;
+ if (slapi_utf8isLower((unsigned char *)p)) {
+ num_lowers++;
+ } else {
+ num_uppers++;
+ }
+ } else {
+ /* check if this is an 8-bit char */
+ if (*p & 128) {
+ num_8bit++;
+ } else {
+ num_specials++;
+ }
+ }
+
+ /* check for repeating characters. If this is the
+ first char of the password, no need to check */
+ if (pwd != p) {
+ int len = ldap_utf8len(p);
+ char *prev_p = ldap_utf8prev(p);
+
+ if (len == ldap_utf8len(prev_p)) {
+ if (memcmp(p, prev_p, len) == 0) {
+ num_repeated++;
+ if (max_repeated < num_repeated) {
+ max_repeated = num_repeated;
+ }
+ } else {
+ num_repeated = 0;
+ }
+ } else {
+ num_repeated = 0;
+ }
+ }
+
+ p = ldap_utf8next(p);
+ }
+
+ free(pwd);
+ p = pwd = NULL;
+
+ /* tally up the number of character categories */
+ if (num_digits > 0) ++num_categories;
+ if (num_uppers > 0) ++num_categories;
+ if (num_lowers > 0) ++num_categories;
+ if (num_specials > 0) ++num_categories;
+ if (num_8bit > 0) ++num_categories;
+
+ /* FIXME: the kerberos plicy schema does not define separated
+ * threshold values, so just treat anything as a category,
+ * we will fix this when we merge with DS policies */
+
+ if (max_repeated > 1) --num_categories;
+
+ if (num_categories < krbPwdMinDiffChars) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Password not complex enough\n");
+ slapi_entry_free(policy);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_INVALIDPWDSYNTAX;
+ }
+ }
+
+ /* Check password history */
+ ret = slapi_entry_attr_find(data->target,
+ "passwordHistory", &passwordHistory);
+ if (ret == 0) {
+ int ret, hint, count, i, j;
+ const char *pwstr;
+ Slapi_Value **pH;
+ Slapi_Value *pw;
+
+ hint = 0;
+ count = 0;
+ ret = slapi_attr_get_numvalues(passwordHistory, &count);
+ /* check history only if we have one */
+ if (count > 0 && data->pwHistoryLen > 0) {
+ pH = calloc(count + 2, sizeof(Slapi_Value *));
+ if (!pH) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ slapi_entry_free(policy);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ i = 0;
+ hint = slapi_attr_first_value(passwordHistory, &pw);
+ while (hint != -1) {
+ pwstr = slapi_value_get_string(pw);
+ /* if shorter than GENERALIZED_TIME_LENGTH, it
+ * is garbage, we never set timeless entries */
+ if (pwstr &&
+ (strlen(pwstr) > GENERALIZED_TIME_LENGTH)) {
+ pH[i] = pw;
+ i++;
+ }
+ hint = slapi_attr_next_value(passwordHistory, hint, &pw);
+ }
+
+ qsort(pH, i, sizeof(Slapi_Value *), ipapwd_sv_pw_cmp);
+
+ if (i > data->pwHistoryLen) {
+ i = data->pwHistoryLen;
+ pH[i] = NULL;
+ }
+
+ for (j = 0; pH[j]; j++) {
+ pH[j] = ipapwd_strip_pw_date(pH[j]);
+ }
+
+ pw = slapi_value_new_string(data->password);
+ if (!pw) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ slapi_entry_free(policy);
+ free(pH);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ ret = slapi_pw_find_sv(pH, pw);
+
+ for (j = 0; pH[j]; j++) {
+ slapi_value_free(&pH[j]);
+ }
+ slapi_value_free(&pw);
+ free(pH);
+
+ if (ret == 0) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Password in history\n");
+ slapi_entry_free(policy);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDINHISTORY;
+ }
+ }
+ }
+
+ /* Calculate max age */
+ tmp = slapi_entry_attr_get_int(policy, "krbMaxPwdLife");
+ if (tmp != 0) {
+ krbMaxPwdLife = tmp;
+ }
+
+ slapi_entry_free(policy);
+
+no_policy:
+
+ /* check min lenght */
+ pwdCharLen = ldap_utf8characters(data->password);
+
+ if (pwdCharLen < krbPwdMinLength) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Password too short "
+ "(%d < %d)\n", pwdCharLen, krbPwdMinLength);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDTOOSHORT;
+ }
+
+ if (data->expireTime == 0) {
+ data->expireTime = data->timeNow + krbMaxPwdLife;
+ }
+
+ return IPAPWD_POLICY_OK;
+}
+
+/* Searches the dn in directory,
+ * If found : fills in slapi_entry structure and returns 0
+ * If NOT found : returns the search result as LDAP_NO_SUCH_OBJECT
+ */
+int ipapwd_getEntry(const char *dn, Slapi_Entry **e2, char **attrlist)
+{
+ Slapi_DN *sdn;
+ int search_result = 0;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_getEntry\n");
+
+ sdn = slapi_sdn_new_dn_byref(dn);
+ search_result = slapi_search_internal_get_entry(sdn, attrlist, e2,
+ ipapwd_plugin_id);
+ if (search_result != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getEntry: No such entry-(%s), err (%d)\n",
+ dn, search_result);
+ }
+
+ slapi_sdn_free(&sdn);
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "<= ipapwd_getEntry: %d\n", search_result);
+ return search_result;
+}
+
+int ipapwd_get_cur_kvno(Slapi_Entry *target)
+{
+ Slapi_Attr *krbPrincipalKey = NULL;
+ Slapi_ValueSet *svs;
+ Slapi_Value *sv;
+ BerElement *be = NULL;
+ const struct berval *cbval;
+ ber_tag_t tag, tmp;
+ ber_int_t tkvno;
+ int hint;
+ int kvno;
+ int ret;
+
+ /* retrieve current kvno and and keys */
+ ret = slapi_entry_attr_find(target, "krbPrincipalKey", &krbPrincipalKey);
+ if (ret != 0) {
+ return 0;
+ }
+
+ kvno = 0;
+
+ slapi_attr_get_valueset(krbPrincipalKey, &svs);
+ hint = slapi_valueset_first_value(svs, &sv);
+ while (hint != -1) {
+ cbval = slapi_value_get_berval(sv);
+ if (!cbval) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "Error retrieving berval from Slapi_Value\n");
+ goto next;
+ }
+ be = ber_init(cbval);
+ if (!be) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ber_init() failed!\n");
+ goto next;
+ }
+
+ tag = ber_scanf(be, "{xxt[i]", &tmp, &tkvno);
+ if (tag == LBER_ERROR) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "Bad OLD key encoding ?!\n");
+ ber_free(be, 1);
+ goto next;
+ }
+
+ if (tkvno > kvno) {
+ kvno = tkvno;
+ }
+
+ ber_free(be, 1);
+next:
+ hint = slapi_valueset_next_value(svs, hint, &sv);
+ }
+
+ return kvno;
+}
+
+/* Modify the Password attributes of the entry */
+int ipapwd_SetPassword(struct ipapwd_krbcfg *krbcfg,
+ struct ipapwd_data *data, int is_krb)
+{
+ int ret = 0;
+ Slapi_Mods *smods;
+ Slapi_Value **svals = NULL;
+ Slapi_Value **pwvals = NULL;
+ struct tm utctime;
+ char timestr[GENERALIZED_TIME_LENGTH+1];
+ krb5_context krbctx;
+ krb5_error_code krberr;
+ char *lm = NULL;
+ char *nt = NULL;
+ int is_smb = 0;
+ Slapi_Value *sambaSamAccount;
+ char *errMesg = NULL;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_SetPassword\n");
+
+ sambaSamAccount = slapi_value_new_string("sambaSamAccount");
+ if (slapi_entry_attr_has_syntax_value(data->target,
+ "objectClass", sambaSamAccount)) {
+ is_smb = 1;;
+ }
+ slapi_value_free(&sambaSamAccount);
+
+ ret = ipapwd_gen_hashes(krbcfg, data,
+ data->password,
+ is_krb, is_smb,
+ &svals, &nt, &lm, &errMesg);
+ if (ret) {
+ goto free_and_return;
+ }
+
+ smods = slapi_mods_new();
+
+ if (svals) {
+ slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+ "krbPrincipalKey", svals);
+
+ /* change Last Password Change field with the current date */
+ if (!gmtime_r(&(data->timeNow), &utctime)) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "failed to retrieve current date (buggy gmtime_r ?)\n");
+ ret = LDAP_OPERATIONS_ERROR;
+ goto free_and_return;
+ }
+ strftime(timestr, GENERALIZED_TIME_LENGTH + 1,
+ "%Y%m%d%H%M%SZ", &utctime);
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "krbLastPwdChange", timestr);
+
+ /* set Password Expiration date */
+ if (!gmtime_r(&(data->expireTime), &utctime)) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "failed to convert expiration date\n");
+ ret = LDAP_OPERATIONS_ERROR;
+ goto free_and_return;
+ }
+ strftime(timestr, GENERALIZED_TIME_LENGTH + 1,
+ "%Y%m%d%H%M%SZ", &utctime);
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "krbPasswordExpiration", timestr);
+ }
+
+ if (lm) {
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "sambaLMPassword", lm);
+ }
+
+ if (nt) {
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "sambaNTPassword", nt);
+ }
+
+ /* let DS encode the password itself, this allows also other plugins to
+ * intercept it to perform operations like synchronization with Active
+ * Directory domains through the replication plugin */
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "userPassword", data->password);
+
+ /* set password history */
+ pwvals = ipapwd_setPasswordHistory(smods, data);
+ if (pwvals) {
+ slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+ "passwordHistory", pwvals);
+ }
+
+ /* FIXME:
+ * instead of replace we should use a delete/add so that we are
+ * completely sure nobody else modified the entry meanwhile and
+ * fail if that's the case */
+
+ /* commit changes */
+ ret = ipapwd_apply_mods(data->dn, smods);
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "<= ipapwd_SetPassword: %d\n", ret);
+
+free_and_return:
+ if (lm) slapi_ch_free((void **)&lm);
+ if (nt) slapi_ch_free((void **)&nt);
+ slapi_mods_free(&smods);
+ ipapwd_free_slapi_value_array(&svals);
+ ipapwd_free_slapi_value_array(&pwvals);
+
+ return ret;
+}
+
+Slapi_Value **ipapwd_setPasswordHistory(Slapi_Mods *smods,
+ struct ipapwd_data *data)
+{
+ Slapi_Value **pH = NULL;
+ Slapi_Attr *passwordHistory = NULL;
+ char timestr[GENERALIZED_TIME_LENGTH+1];
+ char *histr, *old_pw;
+ struct tm utctime;
+ int ret, pc;
+
+ old_pw = slapi_entry_attr_get_charptr(data->target, "userPassword");
+ if (!old_pw) {
+ /* no old password to store, just return */
+ return NULL;
+ }
+
+ if (!gmtime_r(&(data->timeNow), &utctime)) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "failed to retrieve current date (buggy gmtime_r ?)\n");
+ return NULL;
+ }
+ strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime);
+
+ histr = slapi_ch_smprintf("%s%s", timestr, old_pw);
+ if (!histr) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ return NULL;
+ }
+
+ /* retrieve current history */
+ ret = slapi_entry_attr_find(data->target,
+ "passwordHistory", &passwordHistory);
+ if (ret == 0) {
+ int ret, hint, count, i;
+ const char *pwstr;
+ Slapi_Value *pw;
+
+ hint = 0;
+ count = 0;
+ ret = slapi_attr_get_numvalues(passwordHistory, &count);
+ /* if we have one */
+ if (count > 0 && data->pwHistoryLen > 0) {
+ pH = calloc(count + 2, sizeof(Slapi_Value *));
+ if (!pH) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ free(histr);
+ return NULL;
+ }
+
+ i = 0;
+ hint = slapi_attr_first_value(passwordHistory, &pw);
+ while (hint != -1) {
+ pwstr = slapi_value_get_string(pw);
+ /* if shorter than GENERALIZED_TIME_LENGTH, it
+ * is garbage, we never set timeless entries */
+ if (pwstr &&
+ (strlen(pwstr) > GENERALIZED_TIME_LENGTH)) {
+ pH[i] = pw;
+ i++;
+ }
+ hint = slapi_attr_next_value(passwordHistory, hint, &pw);
+ }
+
+ qsort(pH, i, sizeof(Slapi_Value *), ipapwd_sv_pw_cmp);
+
+ if (i >= data->pwHistoryLen) {
+ i = data->pwHistoryLen;
+ pH[i] = NULL;
+ i--;
+ }
+
+ pc = i;
+
+ /* copy only interesting entries */
+ for (i = 0; i < pc; i++) {
+ pH[i] = slapi_value_dup(pH[i]);
+ if (pH[i] == NULL) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ while (i) {
+ i--;
+ slapi_value_free(&pH[i]);
+ }
+ free(pH);
+ free(histr);
+ return NULL;
+ }
+ }
+ }
+ }
+
+ if (pH == NULL) {
+ pH = calloc(2, sizeof(Slapi_Value *));
+ if (!pH) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ free(histr);
+ return NULL;
+ }
+ pc = 0;
+ }
+
+ /* add new history value */
+ pH[pc] = slapi_value_new_string(histr);
+
+ free(histr);
+
+ return pH;
+}
+
+/* Construct Mods pblock and perform the modify operation
+ * Sets result of operation in SLAPI_PLUGIN_INTOP_RESULT
+ */
+int ipapwd_apply_mods(const char *dn, Slapi_Mods *mods)
+{
+ Slapi_PBlock *pb;
+ int ret;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_apply_mods\n");
+
+ if (!mods || (slapi_mods_get_num_mods(mods) == 0)) {
+ return -1;
+ }
+
+ pb = slapi_pblock_new();
+ slapi_modify_internal_set_pb(pb, dn,
+ slapi_mods_get_ldapmods_byref(mods),
+ NULL, /* Controls */
+ NULL, /* UniqueID */
+ ipapwd_plugin_id, /* PluginID */
+ 0); /* Flags */
+
+ ret = slapi_modify_internal_pb(pb);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "WARNING: modify error %d on entry '%s'\n", ret, dn);
+ } else {
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+
+ if (ret != LDAP_SUCCESS){
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "WARNING: modify error %d on entry '%s'\n",
+ ret, dn);
+ } else {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "<= ipapwd_apply_mods: Successful\n");
+ }
+ }
+
+ slapi_pblock_destroy(pb);
+
+ return ret;
+}
+
+void ipapwd_free_slapi_value_array(Slapi_Value ***svals)
+{
+ Slapi_Value **sv = *svals;
+ int i;
+
+ if (sv) {
+ for (i = 0; sv[i]; i++) {
+ slapi_value_free(&sv[i]);
+ }
+ }
+
+ slapi_ch_free((void **)sv);
+}
+
+void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg)
+{
+ struct ipapwd_krbcfg *c = *cfg;
+
+ if (!c) return;
+
+ krb5_free_default_realm(c->krbctx, c->realm);
+ krb5_free_context(c->krbctx);
+ free(c->kmkey->contents);
+ free(c->kmkey);
+ free(c->supp_encsalts);
+ free(c->pref_encsalts);
+ slapi_ch_array_free(c->passsync_mgrs);
+ free(c);
+ *cfg = NULL;
+};
+
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c
new file mode 100644
index 000000000..e6468a515
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c
@@ -0,0 +1,809 @@
+/** BEGIN COPYRIGHT BLOCK
+ * This Program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; version 2 of the License.
+ *
+ * This Program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link to the code of this Program
+ * through those well defined interfaces identified in the file named EXCEPTION
+ * found in the source code files (the "Approved Interfaces"). The files of
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
+ * the Approved Interfaces without causing the resulting work to be covered by
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code
+ * used in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish
+ * to provide this exception without modification, you must delete this
+ * exception statement from your version and license this file solely under the
+ * GPL without exception.
+ *
+ * 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
+
+#include <stdio.h>
+#include <string.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 "ipapwd.h"
+
+/* krbTicketFlags */
+#define KTF_DISALLOW_POSTDATED 0x00000001
+#define KTF_DISALLOW_FORWARDABLE 0x00000002
+#define KTF_DISALLOW_TGT_BASED 0x00000004
+#define KTF_DISALLOW_RENEWABLE 0x00000008
+#define KTF_DISALLOW_PROXIABLE 0x00000010
+#define KTF_DISALLOW_DUP_SKEY 0x00000020
+#define KTF_DISALLOW_ALL_TIX 0x00000040
+#define KTF_REQUIRES_PRE_AUTH 0x00000080
+#define KTF_REQUIRES_HW_AUTH 0x00000100
+#define KTF_REQUIRES_PWCHANGE 0x00000200
+#define KTF_DISALLOW_SVR 0x00001000
+#define KTF_PWCHANGE_SERVICE 0x00002000
+
+/* Salt types */
+#define KRB5_KDB_SALTTYPE_NORMAL 0
+#define KRB5_KDB_SALTTYPE_V4 1
+#define KRB5_KDB_SALTTYPE_NOREALM 2
+#define KRB5_KDB_SALTTYPE_ONLYREALM 3
+#define KRB5_KDB_SALTTYPE_SPECIAL 4
+#define KRB5_KDB_SALTTYPE_AFS3 5
+#define KRB5P_SALT_SIZE 16
+
+void krb5int_c_free_keyblock_contents(krb5_context context,
+ register krb5_keyblock *key);
+
+/* Novell key-format scheme:
+
+ KrbKeySet ::= SEQUENCE {
+ attribute-major-vno [0] UInt16,
+ attribute-minor-vno [1] UInt16,
+ kvno [2] UInt32,
+ mkvno [3] UInt32 OPTIONAL,
+ keys [4] SEQUENCE OF KrbKey,
+ ...
+ }
+
+ KrbKey ::= SEQUENCE {
+ salt [0] KrbSalt OPTIONAL,
+ key [1] EncryptionKey,
+ s2kparams [2] OCTET STRING OPTIONAL,
+ ...
+ }
+
+ KrbSalt ::= SEQUENCE {
+ type [0] Int32,
+ salt [1] OCTET STRING OPTIONAL
+ }
+
+ EncryptionKey ::= SEQUENCE {
+ keytype [0] Int32,
+ keyvalue [1] OCTET STRING
+ }
+
+ */
+
+struct berval *encode_keys(struct ipapwd_keyset *kset)
+{
+ BerElement *be = NULL;
+ struct berval *bval = NULL;
+ int ret, i;
+
+ be = ber_alloc_t(LBER_USE_DER);
+
+ if (!be) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "memory allocation failed\n");
+ return NULL;
+ }
+
+ ret = ber_printf(be, "{t[i]t[i]t[i]t[i]t[{",
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0),
+ kset->major_vno,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1),
+ kset->minor_vno,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 2),
+ kset->kvno,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 3),
+ kset->mkvno,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 4));
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "encoding asn1 vno info failed\n");
+ goto done;
+ }
+
+ for (i = 0; i < kset->num_keys; i++) {
+
+ ret = ber_printf(be, "{");
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "encoding asn1 EncryptionKey failed\n");
+ goto done;
+ }
+
+ if (kset->keys[i].salt) {
+ ret = ber_printf(be, "t[{t[i]",
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0),
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0),
+ kset->keys[i].salt->type);
+ if ((ret != -1) && kset->keys[i].salt->value.bv_len) {
+ ret = ber_printf(be, "t[o]",
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1),
+ kset->keys[i].salt->value.bv_val,
+ kset->keys[i].salt->value.bv_len);
+ }
+ if (ret != -1) {
+ ret = ber_printf(be, "}]");
+ }
+ if (ret == -1) {
+ goto done;
+ }
+ }
+
+ ret = ber_printf(be, "t[{t[i]t[o]}]",
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1),
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0),
+ kset->keys[i].ekey->type,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1),
+ kset->keys[i].ekey->value.bv_val,
+ kset->keys[i].ekey->value.bv_len);
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "encoding asn1 EncryptionKey failed\n");
+ goto done;
+ }
+
+ /* FIXME: s2kparams not supported yet */
+
+ ret = ber_printf(be, "}");
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "encoding asn1 EncryptionKey failed\n");
+ goto done;
+ }
+ }
+
+ ret = ber_printf(be, "}]}");
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "encoding asn1 end of sequences failed\n");
+ goto done;
+ }
+
+ ret = ber_flatten(be, &bval);
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "flattening asn1 failed\n");
+ goto done;
+ }
+done:
+ ber_free(be, 1);
+
+ return bval;
+}
+
+void ipapwd_keyset_free(struct ipapwd_keyset **pkset)
+{
+ struct ipapwd_keyset *kset = *pkset;
+ int i;
+
+ if (!kset) return;
+
+ for (i = 0; i < kset->num_keys; i++) {
+ if (kset->keys[i].salt) {
+ free(kset->keys[i].salt->value.bv_val);
+ free(kset->keys[i].salt);
+ }
+ if (kset->keys[i].ekey) {
+ free(kset->keys[i].ekey->value.bv_val);
+ free(kset->keys[i].ekey);
+ }
+ free(kset->keys[i].s2kparams.bv_val);
+ }
+ free(kset->keys);
+ free(kset);
+ *pkset = NULL;
+}
+
+
+
+static inline void encode_int16(unsigned int val, unsigned char *p)
+{
+ p[1] = (val >> 8) & 0xff;
+ p[0] = (val ) & 0xff;
+}
+
+static Slapi_Value **encrypt_encode_key(struct ipapwd_krbcfg *krbcfg,
+ struct ipapwd_data *data,
+ char **errMesg)
+{
+ krb5_context krbctx;
+ char *krbPrincipalName = NULL;
+ uint32_t krbMaxTicketLife;
+ int kvno, i;
+ int krbTicketFlags;
+ struct berval *bval = NULL;
+ Slapi_Value **svals = NULL;
+ krb5_principal princ;
+ krb5_error_code krberr;
+ krb5_data pwd;
+ struct ipapwd_keyset *kset = NULL;
+
+ krbctx = krbcfg->krbctx;
+
+ svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *));
+ if (!svals) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "memory allocation failed\n");
+ return NULL;
+ }
+
+ kvno = ipapwd_get_cur_kvno(data->target);
+
+ krbPrincipalName = slapi_entry_attr_get_charptr(data->target,
+ "krbPrincipalName");
+ if (!krbPrincipalName) {
+ *errMesg = "no krbPrincipalName present in this entry\n";
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, *errMesg);
+ return NULL;
+ }
+
+ krberr = krb5_parse_name(krbctx, krbPrincipalName, &princ);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "krb5_parse_name failed [%s]\n",
+ krb5_get_error_message(krbctx, krberr));
+ goto enc_error;
+ }
+
+ krbMaxTicketLife = slapi_entry_attr_get_uint(data->target,
+ "krbMaxTicketLife");
+ if (krbMaxTicketLife == 0) {
+ /* FIXME: retrieve the default from config (max_life from kdc.conf) */
+ krbMaxTicketLife = 86400; /* just set the default 24h for now */
+ }
+
+ krbTicketFlags = slapi_entry_attr_get_int(data->target,
+ "krbTicketFlags");
+
+ pwd.data = (char *)data->password;
+ pwd.length = strlen(data->password);
+
+ kset = malloc(sizeof(struct ipapwd_keyset));
+ if (!kset) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "malloc failed!\n");
+ goto enc_error;
+ }
+
+ /* this encoding assumes all keys have the same kvno */
+ /* major-vno = 1 and minor-vno = 1 */
+ kset->major_vno = 1;
+ kset->minor_vno = 1;
+ /* increment kvno (will be 1 if this is a new entry) */
+ kset->kvno = kvno + 1;
+ /* we also assum mkvno is 0 */
+ kset->mkvno = 0;
+
+ kset->num_keys = krbcfg->num_pref_encsalts;
+ kset->keys = calloc(kset->num_keys, sizeof(struct ipapwd_krbkey));
+ if (!kset->keys) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "malloc failed!\n");
+ goto enc_error;
+ }
+
+ for (i = 0; i < kset->num_keys; i++) {
+ krb5_keyblock key;
+ krb5_data salt;
+ krb5_octet *ptr;
+ krb5_data plain;
+ krb5_enc_data cipher;
+ size_t len;
+ const char *p;
+
+ salt.data = NULL;
+
+ switch (krbcfg->pref_encsalts[i].salt_type) {
+
+ case KRB5_KDB_SALTTYPE_ONLYREALM:
+
+ p = strchr(krbPrincipalName, '@');
+ if (!p) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Invalid principal name, no realm found!\n");
+ goto enc_error;
+ }
+ p++;
+ salt.data = strdup(p);
+ if (!salt.data) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "memory allocation failed\n");
+ goto enc_error;
+ }
+ salt.length = strlen(salt.data); /* final \0 omitted on purpose */
+ break;
+
+ case KRB5_KDB_SALTTYPE_NOREALM:
+
+ krberr = krb5_principal2salt_norealm(krbctx, princ, &salt);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "krb5_principal2salt failed [%s]\n",
+ krb5_get_error_message(krbctx, krberr));
+ goto enc_error;
+ }
+ break;
+
+ case KRB5_KDB_SALTTYPE_NORMAL:
+
+ /* If pre auth is required we can set a random salt, otherwise
+ * we have to use a more conservative approach and set the salt
+ * to be REALMprincipal (the concatenation of REALM and principal
+ * name without any separator) */
+#if 0
+ if (krbTicketFlags & KTF_REQUIRES_PRE_AUTH) {
+ salt.length = KRB5P_SALT_SIZE;
+ salt.data = malloc(KRB5P_SALT_SIZE);
+ if (!salt.data) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "memory allocation failed\n");
+ goto enc_error;
+ }
+ krberr = krb5_c_random_make_octets(krbctx, &salt);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "krb5_c_random_make_octets failed [%s]\n",
+ krb5_get_error_message(krbctx, krberr));
+ goto enc_error;
+ }
+ } else {
+#endif
+ krberr = krb5_principal2salt(krbctx, princ, &salt);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "krb5_principal2salt failed [%s]\n",
+ krb5_get_error_message(krbctx, krberr));
+ goto enc_error;
+ }
+#if 0
+ }
+#endif
+ break;
+
+ case KRB5_KDB_SALTTYPE_V4:
+ salt.length = 0;
+ break;
+
+ case KRB5_KDB_SALTTYPE_AFS3:
+
+ p = strchr(krbPrincipalName, '@');
+ if (!p) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Invalid principal name, no realm found!\n");
+ goto enc_error;
+ }
+ p++;
+ salt.data = strdup(p);
+ if (!salt.data) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "memory allocation failed\n");
+ goto enc_error;
+ }
+ salt.length = SALT_TYPE_AFS_LENGTH; /* special value */
+ break;
+
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Invalid salt type [%d]\n",
+ krbcfg->pref_encsalts[i].salt_type);
+ goto enc_error;
+ }
+
+ /* need to build the key now to manage the AFS salt.length
+ * special case */
+ krberr = krb5_c_string_to_key(krbctx,
+ krbcfg->pref_encsalts[i].enc_type,
+ &pwd, &salt, &key);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "krb5_c_string_to_key failed [%s]\n",
+ krb5_get_error_message(krbctx, krberr));
+ krb5_free_data_contents(krbctx, &salt);
+ goto enc_error;
+ }
+ if (salt.length == SALT_TYPE_AFS_LENGTH) {
+ salt.length = strlen(salt.data);
+ }
+
+ krberr = krb5_c_encrypt_length(krbctx,
+ krbcfg->kmkey->enctype,
+ key.length, &len);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "krb5_c_string_to_key failed [%s]\n",
+ krb5_get_error_message(krbctx, krberr));
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ krb5_free_data_contents(krbctx, &salt);
+ goto enc_error;
+ }
+
+ if ((ptr = (krb5_octet *) malloc(2 + len)) == NULL) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "memory allocation failed\n");
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ krb5_free_data_contents(krbctx, &salt);
+ goto enc_error;
+ }
+
+ encode_int16(key.length, ptr);
+
+ plain.length = key.length;
+ plain.data = (char *)key.contents;
+
+ cipher.ciphertext.length = len;
+ cipher.ciphertext.data = (char *)ptr+2;
+
+ krberr = krb5_c_encrypt(krbctx, krbcfg->kmkey, 0, 0, &plain, &cipher);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "krb5_c_encrypt failed [%s]\n",
+ krb5_get_error_message(krbctx, krberr));
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ krb5_free_data_contents(krbctx, &salt);
+ free(ptr);
+ goto enc_error;
+ }
+
+ /* KrbSalt */
+ kset->keys[i].salt = malloc(sizeof(struct ipapwd_krbkeydata));
+ if (!kset->keys[i].salt) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "malloc failed!\n");
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ free(ptr);
+ goto enc_error;
+ }
+
+ kset->keys[i].salt->type = krbcfg->pref_encsalts[i].salt_type;
+
+ if (salt.length) {
+ kset->keys[i].salt->value.bv_len = salt.length;
+ kset->keys[i].salt->value.bv_val = salt.data;
+ }
+
+ /* EncryptionKey */
+ kset->keys[i].ekey = malloc(sizeof(struct ipapwd_krbkeydata));
+ if (!kset->keys[i].ekey) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "malloc failed!\n");
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ free(ptr);
+ goto enc_error;
+ }
+ kset->keys[i].ekey->type = key.enctype;
+ kset->keys[i].ekey->value.bv_len = len+2;
+ kset->keys[i].ekey->value.bv_val = malloc(len+2);
+ if (!kset->keys[i].ekey->value.bv_val) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "malloc failed!\n");
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ free(ptr);
+ goto enc_error;
+ }
+ memcpy(kset->keys[i].ekey->value.bv_val, ptr, len+2);
+
+ /* make sure we free the memory used now that we are done with it */
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ free(ptr);
+ }
+
+ bval = encode_keys(kset);
+ if (!bval) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "encoding asn1 KrbSalt failed\n");
+ goto enc_error;
+ }
+
+ svals[0] = slapi_value_new_berval(bval);
+ if (!svals[0]) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Converting berval to Slapi_Value\n");
+ goto enc_error;
+ }
+
+ ipapwd_keyset_free(&kset);
+ krb5_free_principal(krbctx, princ);
+ slapi_ch_free_string(&krbPrincipalName);
+ ber_bvfree(bval);
+ return svals;
+
+enc_error:
+ *errMesg = "key encryption/encoding failed\n";
+ if (kset) ipapwd_keyset_free(&kset);
+ krb5_free_principal(krbctx, princ);
+ slapi_ch_free_string(&krbPrincipalName);
+ if (bval) ber_bvfree(bval);
+ free(svals);
+ return NULL;
+}
+
+
+#define KTF_LM_HASH 0x01
+#define KTF_NT_HASH 0x02
+#define KTF_DOS_CHARSET "CP850" /* same default as samba */
+#define KTF_UTF8 "UTF-8"
+#define KTF_UCS2 "UCS-2LE"
+
+static const uint8_t parity_table[128] = {
+ 1, 2, 4, 7, 8, 11, 13, 14, 16, 19, 21, 22, 25, 26, 28, 31,
+ 32, 35, 37, 38, 41, 42, 44, 47, 49, 50, 52, 55, 56, 59, 61, 62,
+ 64, 67, 69, 70, 73, 74, 76, 79, 81, 82, 84, 87, 88, 91, 93, 94,
+ 97, 98,100,103,104,107,109,110,112,115,117,118,121,122,124,127,
+ 128,131,133,134,137,138,140,143,145,146,148,151,152,155,157,158,
+ 161,162,164,167,168,171,173,174,176,179,181,182,185,186,188,191,
+ 193,194,196,199,200,203,205,206,208,211,213,214,217,218,220,223,
+ 224,227,229,230,233,234,236,239,241,242,244,247,248,251,253,254
+};
+
+static void lm_shuffle(uint8_t *out, uint8_t *in)
+{
+ out[0] = parity_table[in[0]>>1];
+ out[1] = parity_table[((in[0]<<6)|(in[1]>>2)) & 0x7F];
+ out[2] = parity_table[((in[1]<<5)|(in[2]>>3)) & 0x7F];
+ out[3] = parity_table[((in[2]<<4)|(in[3]>>4)) & 0x7F];
+ out[4] = parity_table[((in[3]<<3)|(in[4]>>5)) & 0x7F];
+ out[5] = parity_table[((in[4]<<2)|(in[5]>>6)) & 0x7F];
+ out[6] = parity_table[((in[5]<<1)|(in[6]>>7)) & 0x7F];
+ out[7] = parity_table[in[6] & 0x7F];
+}
+
+struct ntlm_keys {
+ uint8_t lm[16];
+ uint8_t nt[16];
+};
+
+/* create the lm and nt hashes
+ newPassword: the clear text utf8 password
+ flags: KTF_LM_HASH | KTF_NT_HASH
+*/
+static int encode_ntlm_keys(char *newPasswd,
+ unsigned int flags,
+ struct ntlm_keys *keys)
+{
+ int ret = 0;
+
+ /* do lanman first */
+ if (flags & KTF_LM_HASH) {
+ iconv_t cd;
+ size_t cs, il, ol;
+ char *inc, *outc;
+ char *upperPasswd;
+ char *asciiPasswd;
+ DES_key_schedule schedule;
+ DES_cblock deskey;
+ DES_cblock magic = "KGS!@#$%";
+
+ /* TODO: must store the dos charset somewhere in the directory */
+ cd = iconv_open(KTF_DOS_CHARSET, KTF_UTF8);
+ if (cd == (iconv_t)(-1)) {
+ ret = -1;
+ goto done;
+ }
+
+ /* the lanman password is upper case */
+ upperPasswd = (char *)slapi_utf8StrToUpper((unsigned char *)newPasswd);
+ if (!upperPasswd) {
+ ret = -1;
+ goto done;
+ }
+ il = strlen(upperPasswd);
+
+ /* an ascii string can only be smaller than or equal to an utf8 one */
+ ol = il;
+ if (ol < 14) ol = 14;
+ asciiPasswd = calloc(ol+1, 1);
+ if (!asciiPasswd) {
+ slapi_ch_free_string(&upperPasswd);
+ ret = -1;
+ goto done;
+ }
+
+ inc = upperPasswd;
+ outc = asciiPasswd;
+ cs = iconv(cd, &inc, &il, &outc, &ol);
+ if (cs == -1) {
+ ret = -1;
+ slapi_ch_free_string(&upperPasswd);
+ free(asciiPasswd);
+ iconv_close(cd);
+ goto done;
+ }
+
+ /* done with these */
+ slapi_ch_free_string(&upperPasswd);
+ iconv_close(cd);
+
+ /* we are interested only in the first 14 ASCII chars for lanman */
+ if (strlen(asciiPasswd) > 14) {
+ asciiPasswd[14] = '\0';
+ }
+
+ /* first half */
+ lm_shuffle(deskey, (uint8_t *)asciiPasswd);
+
+ DES_set_key_unchecked(&deskey, &schedule);
+ DES_ecb_encrypt(&magic, (DES_cblock *)keys->lm,
+ &schedule, DES_ENCRYPT);
+
+ /* second half */
+ lm_shuffle(deskey, (uint8_t *)&asciiPasswd[7]);
+
+ DES_set_key_unchecked(&deskey, &schedule);
+ DES_ecb_encrypt(&magic, (DES_cblock *)&(keys->lm[8]),
+ &schedule, DES_ENCRYPT);
+
+ /* done with it */
+ free(asciiPasswd);
+
+ } else {
+ memset(keys->lm, 0, 16);
+ }
+
+ if (flags & KTF_NT_HASH) {
+ iconv_t cd;
+ size_t cs, il, ol, sl;
+ char *inc, *outc;
+ char *ucs2Passwd;
+ MD4_CTX md4ctx;
+
+ /* TODO: must store the dos charset somewhere in the directory */
+ cd = iconv_open(KTF_UCS2, KTF_UTF8);
+ if (cd == (iconv_t)(-1)) {
+ ret = -1;
+ goto done;
+ }
+
+ il = strlen(newPasswd);
+
+ /* an ucs2 string can be at most double than an utf8 one */
+ sl = ol = (il+1)*2;
+ ucs2Passwd = calloc(ol, 1);
+ if (!ucs2Passwd) {
+ ret = -1;
+ goto done;
+ }
+
+ inc = newPasswd;
+ outc = ucs2Passwd;
+ cs = iconv(cd, &inc, &il, &outc, &ol);
+ if (cs == -1) {
+ ret = -1;
+ free(ucs2Passwd);
+ iconv_close(cd);
+ goto done;
+ }
+
+ /* done with it */
+ iconv_close(cd);
+
+ /* get the final ucs2 string length */
+ sl -= ol;
+ /* we are interested only in the first 14 wchars for the nt password */
+ if (sl > 28) {
+ sl = 28;
+ }
+
+ ret = MD4_Init(&md4ctx);
+ if (ret == 0) {
+ ret = -1;
+ free(ucs2Passwd);
+ goto done;
+ }
+ ret = MD4_Update(&md4ctx, ucs2Passwd, sl);
+ if (ret == 0) {
+ ret = -1;
+ free(ucs2Passwd);
+ goto done;
+ }
+ ret = MD4_Final(keys->nt, &md4ctx);
+ if (ret == 0) {
+ ret = -1;
+ free(ucs2Passwd);
+ goto done;
+ }
+
+ } else {
+ memset(keys->nt, 0, 16);
+ }
+
+ ret = 0;
+
+done:
+ return ret;
+}
+
+int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg,
+ struct ipapwd_data *data, char *userpw,
+ int is_krb, int is_smb, Slapi_Value ***svals,
+ char **nthash, char **lmhash, char **errMesg)
+{
+ int rc;
+
+ if (is_krb) {
+
+ *svals = encrypt_encode_key(krbcfg, data, errMesg);
+
+ if (!*svals) {
+ /* errMesg should have been set in encrypt_encode_key() */
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "key encryption/encoding failed\n");
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ }
+
+ if (is_smb) {
+ char lm[33], nt[33];
+ struct ntlm_keys ntlm;
+ int ntlm_flags = 0;
+ int ret;
+
+ /* TODO: retrieve if we want to store the LM hash or not */
+ ntlm_flags = KTF_LM_HASH | KTF_NT_HASH;
+
+ ret = encode_ntlm_keys(userpw, ntlm_flags, &ntlm);
+ if (ret) {
+ *errMesg = "Failed to generate NT/LM hashes\n";
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ *errMesg);
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ if (ntlm_flags & KTF_LM_HASH) {
+ hexbuf(lm, ntlm.lm);
+ lm[32] = '\0';
+ *lmhash = slapi_ch_strdup(lm);
+ }
+ if (ntlm_flags & KTF_NT_HASH) {
+ hexbuf(nt, ntlm.nt);
+ nt[32] = '\0';
+ *nthash = slapi_ch_strdup(nt);
+ }
+ }
+
+ rc = LDAP_SUCCESS;
+
+done:
+
+ return rc;
+}
+
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c
new file mode 100644
index 000000000..7c95ac814
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c
@@ -0,0 +1,1077 @@
+/** BEGIN COPYRIGHT BLOCK
+ * This Program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; version 2 of the License.
+ *
+ * This Program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link to the code of this Program
+ * through those well defined interfaces identified in the file named EXCEPTION
+ * found in the source code files (the "Approved Interfaces"). The files of
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
+ * the Approved Interfaces without causing the resulting work to be covered by
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code
+ * used in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish
+ * to provide this exception without modification, you must delete this
+ * exception statement from your version and license this file solely under the
+ * GPL without exception.
+ *
+ * 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
+
+#include <stdio.h>
+#include <string.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 "ipapwd.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;
+
+/* 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;
+ int ret;
+
+ dn = slapi_ch_smprintf("cn=ipaconfig,cn=etc,%s", ipa_realm_tree);
+ if (!dn) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Out of memory ?\n");
+ goto done;
+ }
+
+ ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "failed to retrieve config entry: %s\n", dn);
+ goto done;
+ }
+
+ value = slapi_entry_attr_get_charptr(entry, attr);
+
+done:
+ slapi_entry_free(entry);
+ slapi_ch_free_string(&dn);
+ return value;
+}
+
+
+/* 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;
+ char *dn = NULL;
+ struct ipapwd_operation *pwdop = NULL;
+ void *op;
+ int is_repl_op, is_root, is_krb, is_smb;
+ int ret;
+ int rc = LDAP_SUCCESS;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME, "=> ipapwd_pre_add\n");
+
+ ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
+ if (ret != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Strdup failed, Out of memory\n");
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ slapi_ch_free_string(&userpw);
+ userpw = tmp;
+ } else if (slapi_is_encoded(userpw)) {
+ /* check if we have access to the unhashed user password */
+ char *userpw_clear =
+ slapi_entry_attr_get_charptr(e, "unhashed#user#password");
+
+ /* unhashed#user#password doesn't always contain the clear text
+ * password, therefore we need to check if its value isn't the same
+ * as userPassword to make sure */
+ if (!userpw_clear || (0 == strcmp(userpw, userpw_clear))) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ slapi_ch_free_string(&userpw);
+ } else {
+ userpw = slapi_ch_strdup(userpw_clear);
+ }
+
+ slapi_ch_free_string(&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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "no ipaMigrationEnabled in config;"
+ " assuming FALSE\n");
+ } else if (0 == strcmp(enabled, "TRUE")) {
+ return 0;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "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,
+ 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_DN, &dn);
+ 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) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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);
+
+ 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(dn);
+ pwdop->pwdata.timeNow = time(NULL);
+ pwdop->pwdata.target = e;
+
+ ret = ipapwd_CheckPolicy(&pwdop->pwdata);
+ if (ret) {
+ errMesg = "Password Fails to meet minimum strength criteria";
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if (is_krb || is_smb) {
+
+ Slapi_Value **svals = NULL;
+ char *nt = NULL;
+ char *lm = NULL;
+
+ pwdop->is_krb = is_krb;
+
+ rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata,
+ userpw, is_krb, is_smb,
+ &svals, &nt, &lm, &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) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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 (lm) {
+ /* set value */
+ slapi_entry_attr_set_charptr(e, "sambaLMPassword", lm);
+ slapi_ch_free_string(&lm);
+ }
+ if (nt) {
+ /* set value */
+ slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt);
+ slapi_ch_free_string(&nt);
+ }
+ }
+
+ 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;
+}
+
+/* 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;
+ Slapi_Mod *smod, *tmod;
+ Slapi_Mods *smods = NULL;
+ char *userpw = NULL;
+ char *unhashedpw = NULL;
+ char *dn = NULL;
+ Slapi_DN *tmp_dn;
+ 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;
+ int ret, rc;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME, "=> ipapwd_pre_mod\n");
+
+ ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
+ if (ret != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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;
+ tmod = slapi_mod_new();
+ smod = slapi_mods_get_first_smod(smods, tmod);
+ while (smod) {
+ struct berval *bv;
+ const char *type;
+ int mop;
+
+ type = slapi_mod_get_type(smod);
+ if (slapi_attr_types_equivalent(type, SLAPI_USERPWD_ATTR)) {
+ mop = slapi_mod_get_operation(smod);
+ /* check op filtering out LDAP_MOD_BVALUES */
+ switch (mop & 0x0f) {
+ case LDAP_MOD_ADD:
+ case LDAP_MOD_REPLACE:
+ is_pwd_op = 1;
+ default:
+ break;
+ }
+ }
+
+ /* 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 absurd
+ * reason */
+ if (slapi_attr_types_equivalent(type, "unhashed#user#password")) {
+ bv = slapi_mod_get_first_value(smod);
+ if (!bv) {
+ slapi_mod_free(&tmod);
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ slapi_ch_free_string(&unhashedpw);
+ unhashedpw = slapi_ch_malloc(bv->bv_len+1);
+ if (!unhashedpw) {
+ slapi_mod_free(&tmod);
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ memcpy(unhashedpw, bv->bv_val, bv->bv_len);
+ unhashedpw[bv->bv_len] = '\0';
+ }
+ slapi_mod_done(tmod);
+ smod = slapi_mods_get_next_smod(smods, tmod);
+ }
+ slapi_mod_free(&tmod);
+
+ /* If userPassword is not modified we are done here */
+ if (! is_pwd_op) {
+ rc = LDAP_SUCCESS;
+ goto done;
+ }
+
+ /* OK swe have something interesting here, start checking for
+ * pre-requisites */
+
+ /* Get target DN */
+ ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+ if (ret) {
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ tmp_dn = slapi_sdn_new_dn_byref(dn);
+ if (tmp_dn) {
+ /* 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_dn, 0, &e, ipapwd_plugin_id);
+ slapi_sdn_free(&tmp_dn);
+ if (ret != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Failed tpo retrieve entry?!?\n");
+ rc = LDAP_NO_SUCH_OBJECT;
+ goto done;
+ }
+ }
+
+ rc = ipapwd_entry_checks(pb, e,
+ &is_root, &is_krb, &is_smb,
+ SLAPI_USERPWD_ATTR, SLAPI_ACL_WRITE);
+ if (rc) {
+ goto done;
+ }
+
+ rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
+ if (rc) {
+ goto done;
+ }
+
+ /* run through the mods again and adjust flags if operations affect them */
+ tmod = slapi_mod_new();
+ smod = slapi_mods_get_first_smod(smods, tmod);
+ while (smod) {
+ struct berval *bv;
+ const char *type;
+ int mop;
+
+ type = slapi_mod_get_type(smod);
+ if (slapi_attr_types_equivalent(type, SLAPI_USERPWD_ATTR)) {
+ mop = slapi_mod_get_operation(smod);
+ /* check op filtering out LDAP_MOD_BVALUES */
+ switch (mop & 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;
+ bv = slapi_mod_get_first_value(smod);
+ if (!bv) {
+ slapi_mod_free(&tmod);
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ slapi_ch_free_string(&userpw);
+ userpw = slapi_ch_malloc(bv->bv_len+1);
+ if (!userpw) {
+ slapi_mod_free(&tmod);
+ 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 */
+ bv = slapi_mod_get_first_value(smod);
+ if (!bv) {
+ is_pwd_op = 0;
+ } else {
+ if (0 == strncmp(userpw, bv->bv_val, bv->bv_len) ||
+ 0 == strncmp(unhashedpw, bv->bv_val, bv->bv_len))
+ is_pwd_op = 0;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (slapi_attr_types_equivalent(type, SLAPI_ATTR_OBJECTCLASS)) {
+ mop = slapi_mod_get_operation(smod);
+ /* check op filtering out LDAP_MOD_BVALUES */
+ switch (mop & 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;
+
+ case LDAP_MOD_ADD:
+ bv = slapi_mod_get_first_value(smod);
+ if (!bv) {
+ slapi_mod_free(&tmod);
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ do {
+ if (0 == strncasecmp("krbPrincipalAux", bv->bv_val, bv->bv_len))
+ is_krb = 1;
+ if (0 == strncasecmp("sambaSamAccount", bv->bv_val, bv->bv_len))
+ is_smb = 1;
+ } while ((bv = slapi_mod_get_next_value(smod)) != NULL);
+
+ break;
+
+ case LDAP_MOD_DELETE:
+ /* can this happen for objectclasses ? */
+ is_krb = 0;
+ is_smb = 0;
+
+ default:
+ break;
+ }
+ }
+
+ slapi_mod_done(tmod);
+ smod = slapi_mods_get_next_smod(smods, tmod);
+ }
+ slapi_mod_free(&tmod);
+
+ /* 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) {
+ if ('{' == userpw[0]) {
+ if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) {
+ unhashedpw = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]);
+ if (NULL == unhashedpw) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Strdup failed, Out of memory\n");
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ slapi_ch_free_string(&userpw);
+
+ } else if (slapi_is_encoded(userpw)) {
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "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) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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_MOD;
+ pwdop->pwdata.password = slapi_ch_strdup(unhashedpw);
+ pwdop->pwdata.changetype = IPA_CHANGETYPE_NORMAL;
+
+ 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_new_dn_byref(dn);
+
+ /* 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(dn);
+ pwdop->pwdata.timeNow = time(NULL);
+ pwdop->pwdata.target = e;
+
+ ret = ipapwd_CheckPolicy(&pwdop->pwdata);
+ if (ret) {
+ errMesg = "Password Fails to meet minimum strength criteria";
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if (is_krb || is_smb) {
+
+ Slapi_Value **svals = NULL;
+ char *nt = NULL;
+ char *lm = NULL;
+
+ rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata, unhashedpw,
+ is_krb, is_smb, &svals, &nt, &lm, &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 (lm) {
+ /* replace value */
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "sambaLMPassword", lm);
+ slapi_ch_free_string(&lm);
+ }
+ if (nt) {
+ /* replace value */
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "sambaNTPassword", nt);
+ slapi_ch_free_string(&nt);
+ }
+ }
+
+ 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);
+ slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods);
+ 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_post_op(Slapi_PBlock *pb)
+{
+ void *op;
+ struct ipapwd_operation *pwdop = NULL;
+ Slapi_Mods *smods;
+ Slapi_Value **pwvals;
+ struct tm utctime;
+ char timestr[GENERALIZED_TIME_LENGTH+1];
+ int ret;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_post_op\n");
+
+ /* time to get the operation handler */
+ ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ if (ret != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Internal error, couldn't find pluginextension ?!\n");
+ return 0;
+ }
+
+ /* not interesting */
+ if (IPAPWD_OP_NULL == pwdop->pwd_op)
+ return 0;
+
+ if ( ! (pwdop->is_krb)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Not a kerberos user, ignore krb attributes\n");
+ return 0;
+ }
+
+ /* prepare changes that can be made only as root */
+ smods = slapi_mods_new();
+
+ /* change Last Password Change field with the current date */
+ if (!gmtime_r(&(pwdop->pwdata.timeNow), &utctime)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "failed to parse current date (buggy gmtime_r ?)\n");
+ goto done;
+ }
+ strftime(timestr, GENERALIZED_TIME_LENGTH+1,
+ "%Y%m%d%H%M%SZ", &utctime);
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "krbLastPwdChange", timestr);
+
+ /* set Password Expiration date */
+ if (!gmtime_r(&(pwdop->pwdata.expireTime), &utctime)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "failed to parse expiration date (buggy gmtime_r ?)\n");
+ goto done;
+ }
+ strftime(timestr, GENERALIZED_TIME_LENGTH+1,
+ "%Y%m%d%H%M%SZ", &utctime);
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "krbPasswordExpiration", timestr);
+
+ /* 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) {
+ 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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Failed tpo retrieve entry?!?\n");
+ goto done;
+ }
+ }
+ pwvals = ipapwd_setPasswordHistory(smods, &pwdop->pwdata);
+ if (pwvals) {
+ slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+ "passwordHistory", pwvals);
+ }
+ }
+
+ ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods);
+ if (ret)
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Failed to set additional password attributes in the post-op!\n");
+
+done:
+ if (pwdop && pwdop->pwdata.target) slapi_entry_free(pwdop->pwdata.target);
+ slapi_mods_free(&smods);
+ return 0;
+}
+
+/* PRE BIND Operation:
+ * Used for password migration from DS to IPA.
+ * Gets the clean text password, authenticates the user and generates
+ * a kerberos key if missing.
+ * Person to blame if anything blows up: Pavel Zuna <pzuna@redhat.com>
+ */
+static int ipapwd_pre_bind(Slapi_PBlock *pb)
+{
+ struct ipapwd_krbcfg *krbcfg = NULL;
+ struct ipapwd_data pwdata;
+ struct berval *credentials; /* bind credentials */
+ Slapi_Entry *entry = NULL;
+ Slapi_Value **pwd_values = NULL; /* values of userPassword attribute */
+ Slapi_Value *value = NULL;
+ Slapi_Attr *attr = NULL;
+ struct tm expire_tm;
+ time_t expire_time;
+ char *errMesg = "Internal operations error\n"; /* error message */
+ char *expire = NULL; /* passwordExpirationTime attribute value */
+ char *dn = NULL; /* bind DN */
+ Slapi_Value *objectclass;
+ int method; /* authentication method */
+ int ret = 0;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_pre_bind\n");
+
+ /* get BIND parameters */
+ ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn);
+ ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
+ ret |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &credentials);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_pre_bind",
+ "slapi_pblock_get failed!?\n");
+ goto done;
+ }
+
+ /* we're only interested in simple authentication */
+ if (method != LDAP_AUTH_SIMPLE)
+ goto done;
+
+ /* list of attributes to retrieve */
+ const char *attrs_list[] = {SLAPI_USERPWD_ATTR, "krbprincipalkey", "uid",
+ "krbprincipalname", "objectclass",
+ "passwordexpirationtime", "passwordhistory",
+ NULL};
+
+ /* retrieve user entry */
+ ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
+ "failed to retrieve user entry: %s\n", dn);
+ goto done;
+ }
+
+ /* check the krbPrincipalName attribute is present */
+ ret = slapi_entry_attr_find(entry, "krbprincipalname", &attr);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
+ "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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
+ "kerberos key already present in user entry: %s\n", dn);
+ goto done;
+ }
+
+ /* retrieve userPassword attribute */
+ ret = slapi_entry_attr_find(entry, SLAPI_USERPWD_ATTR, &attr);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
+ "no " SLAPI_USERPWD_ATTR " in user entry: %s\n", dn);
+ goto done;
+ }
+
+ /* 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 */
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "out of memory!?\n");
+ goto done;
+ }
+ /* 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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
+ "invalid BIND password for user entry: %s\n", dn);
+ goto done;
+ }
+
+ /* general checks */
+ ret = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_pre_bind",
+ "ipapwd_gen_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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
+ "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 = mktime(&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. */
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
+ "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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
+ "failed to set kerberos key for user entry: %s\n", dn);
+ goto done;
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_pre_bind",
+ "kerberos key generated for user entry: %s\n", dn);
+
+done:
+ slapi_ch_free_string(&expire);
+ if (entry)
+ slapi_entry_free(entry);
+ free_ipapwd_krbcfg(&krbcfg);
+
+ return 0;
+}
+
+
+
+/* Init pre ops */
+int ipapwd_pre_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_PRE_BIND_FN, (void *)ipapwd_pre_bind);
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *)ipapwd_pre_add);
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_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_ADD_FN, (void *)ipapwd_post_op);
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op);
+
+ return ret;
+}
+