summaryrefslogtreecommitdiffstats
path: root/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
diff options
context:
space:
mode:
authorKarl MacMillan <kmacmillan@mentalrootkit.com>2007-07-27 13:42:02 -0400
committerKarl MacMillan <kmacmillan@mentalrootkit.com>2007-07-27 13:42:02 -0400
commit9d5b946fdafa77b7aca360d2d1e8ce48980c559f (patch)
tree993e6ffc6ff823af3ffe91e1428a23bb992a2c0f /ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
parenta471ebe7517a04d67b788b3cfd59cb9aa451da0a (diff)
downloadfreeipa-9d5b946fdafa77b7aca360d2d1e8ce48980c559f.tar.gz
freeipa-9d5b946fdafa77b7aca360d2d1e8ce48980c559f.tar.xz
freeipa-9d5b946fdafa77b7aca360d2d1e8ce48980c559f.zip
Reorganized repo to reflect packaging.
Diffstat (limited to 'ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c')
-rw-r--r--ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c1359
1 files changed, 1359 insertions, 0 deletions
diff --git a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
new file mode 100644
index 000000000..f871ee4f6
--- /dev/null
+++ b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
@@ -0,0 +1,1359 @@
+/** 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) 2005 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/*
+ * Password Modify - LDAP Extended Operation.
+ * RFC 3062
+ *
+ *
+ * This plugin implements the "Password Modify - LDAP3"
+ * extended operation for LDAP. The plugin function is called by
+ * the server if an LDAP client request contains the OID:
+ * "1.3.6.1.4.1.4203.1.11.1".
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <prio.h>
+#include <ssl.h>
+#include <slapi-plugin.h>
+#include <krb5.h>
+#include <lber.h>
+#include <time.h>
+#include <iconv.h>
+#include <mhash.h>
+#include <openssl/des.h>
+
+/* Type of connection for this operation;*/
+#define LDAP_EXTOP_PASSMOD_CONN_SECURE
+
+/* Uncomment the following line 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
+#define LDAP_EXTOP_PASSMOD_TAG_NEWPWD 0x82U
+
+/* ber tags for the PasswdModifyResponseValue sequence */
+#define LDAP_EXTOP_PASSMOD_TAG_GENPWD 0x80U
+
+/* number of bytes used for random password generation */
+#define LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN 8
+
+/* number of random bytes needed to generate password */
+#define LDAP_EXTOP_PASSMOD_RANDOM_BYTES 6
+
+/* OID of the extended operation handled by this plug-in */
+#define EXOP_PASSWD_OID "1.3.6.1.4.1.4203.1.11.1"
+
+/* These are thye default enc:salt ypes 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 */
+
+#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
+
+struct krb5p_keysalt {
+ krb5_int32 enc_type;
+ krb5_int32 salt_type;
+};
+
+static void *ipapwd_plugin_id;
+
+krb5_keyblock kmkey;
+
+struct krb5p_keysalt *keysalts;
+int n_keysalts;
+
+/* 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 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(krb5_context krbctx, Slapi_Entry *e, const char *newPasswd)
+{
+ struct berval *bval = NULL;
+ Slapi_Value **svals = NULL;
+ BerElement *be = NULL;
+ int num_versions;
+ int krbTicketFlags;
+ const char *krbPrincipalName;
+ krb5_principal princ;
+ krb5_error_code krberr;
+ krb5_data pwd;
+ int ret, i;
+
+ krbPrincipalName = slapi_entry_attr_get_charptr(e, "krbPrincipalName");
+ if (!krbPrincipalName) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "no krbPrincipalName present in this entry\n");
+ return NULL;
+ }
+
+ /* TODO: retrieve current kvno and increment it */
+ /* TODO: keep previous version */
+ num_versions = 1;
+
+ svals = (Slapi_Value **)calloc(num_versions + 1, sizeof(Slapi_Value *));
+ if (!svals) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "memory allocation failed\n");
+ return NULL;
+ }
+
+ svals[1] = 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;
+ }
+
+ krbTicketFlags = slapi_entry_attr_get_int(e, "krbTicketFlags");
+
+ pwd.data = (char *)newPasswd;
+ pwd.length = strlen(newPasswd);
+
+ be = ber_alloc_t( LBER_USE_DER );
+
+ if (!be) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
+ "memory allocation failed\n");
+ goto enc_error;
+ }
+
+ /* major-vno = 1 and minor-von = 1 */
+ /* this encoding assumes all keys have the same kvno (currently set at 1) */
+ /* we also assum mkvno is 0 */
+ ret = ber_printf(be, "{t[i]t[i]t[i]t[i]t[{",
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), 1,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), 1,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 2), 1,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 3), 0,
+ (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 enc_error;
+ }
+
+ for (i = 0; i < n_keysalts; 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 (keysalts[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 (krbTicketFlags & KTF_REQUIRES_PRE_AUTH) {
+ salt.length = KRB5P_SALT_SIZE;
+ 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 {
+ 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;
+ }
+ }
+ 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", keysalts[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, keysalts[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, 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 = key.contents;
+
+ cipher.ciphertext.length = len;
+ cipher.ciphertext.data = ptr+2;
+
+ krberr = krb5_c_encrypt(krbctx, &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 */
+ if (salt.length) {
+ ret = ber_printf(be, "{t[{t[i]t[o]}]",
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0),
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), keysalts[i].salt_type,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), salt.data, salt.length);
+ } else {
+ 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), keysalts[i].salt_type);
+ }
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
+ "encoding asn1 KrbSalt failed\n");
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ krb5_free_data_contents(krbctx, &salt);
+ free(ptr);
+ goto enc_error;
+ }
+
+ /* EncryptionKey */
+ 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), key.enctype,
+ (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), ptr, len+2);
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
+ "encoding asn1 EncryptionKey failed\n");
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ krb5_free_data_contents(krbctx, &salt);
+ free(ptr);
+ goto enc_error;
+ }
+
+ /* make sure we free the memory used now that we are done with it */
+ krb5int_c_free_keyblock_contents(krbctx, &key);
+ krb5_free_data_contents(krbctx, &salt);
+ free(ptr);
+ }
+
+ ret = ber_printf(be, "}]}");
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
+ "encoding asn1 end of sequences failed\n");
+ goto enc_error;
+ }
+
+ ret = ber_flatten(be, &bval);
+ if (ret == -1) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop",
+ "flattening asn1 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;
+ }
+
+ krb5_free_principal(krbctx, princ);
+ ber_bvfree(bval);
+ ber_free(be, 1);
+ return svals;
+
+enc_error:
+ krb5_free_principal(krbctx, princ);
+ if (bval) ber_bvfree(bval);
+ if (svals) free(svals);
+ if (be) ber_free(be, 1);
+ return NULL;
+}
+
+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(char *out, char *in)
+{
+ uint8_t *outb = (uint8_t *)out;
+ uint8_t *inb = (uint8_t *)in;
+
+ outb[0] = parity_table[inb[0]>>1];
+ outb[1] = parity_table[((inb[0]<<6)|(in[1]>>2)) & 0x7F];
+ outb[2] = parity_table[((inb[1]<<5)|(in[2]>>3)) & 0x7F];
+ outb[3] = parity_table[((inb[2]<<4)|(in[3]>>4)) & 0x7F];
+ outb[4] = parity_table[((inb[3]<<3)|(in[4]>>5)) & 0x7F];
+ outb[5] = parity_table[((inb[4]<<2)|(in[5]>>6)) & 0x7F];
+ outb[6] = parity_table[((inb[5]<<1)|(in[6]>>7)) & 0x7F];
+ outb[7] = parity_table[inb[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 = slapi_utf8StrToUpper(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, asciiPasswd);
+
+ DES_set_key_unchecked(&deskey, &schedule);
+ DES_ecb_encrypt(&magic, (DES_cblock *)keys->lm, &schedule, DES_ENCRYPT);
+
+ /* second half */
+ lm_shuffle(deskey, &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;
+ MHASH td;
+
+ /* 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;
+ }
+
+ td = mhash_init(MHASH_MD4);
+ if (td == MHASH_FAILED) {
+ ret = -1;
+ free(ucs2Passwd);
+ goto done;
+ }
+
+ mhash(td, ucs2Passwd, sl);
+ mhash_deinit(td, keys->nt);
+
+ } else {
+ memset(keys->nt, 0, 16);
+ }
+
+done:
+ return ret;
+}
+
+/* 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 ) {
+ int search_result = 0;
+ Slapi_DN *sdn;
+ 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, NULL, 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=0;
+
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_apply_mods\n");
+
+ if (mods && (slapi_mods_get_num_mods(mods) > 0))
+ {
+ 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);
+
+ 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);
+ }
+
+ slapi_pblock_destroy(pb);
+ }
+
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "<= ipapwd_apply_mods: %d\n", ret);
+
+ 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 userPassword attribute field of the entry */
+static int ipapwd_userpassword(Slapi_Entry *targetEntry, const char *newPasswd)
+{
+ char *dn = NULL;
+ int ret = 0, i = 0;
+ Slapi_Mods *smods;
+ Slapi_Mod *keymod;
+ Slapi_Value **svals;
+ time_t curtime;
+ struct tm utctime;
+ char timestr[16];
+ krb5_context krbctx;
+ krb5_error_code krberr;
+ char lm[33], nt[33];
+ struct ntlm_keys ntlm;
+ int ntlm_flags = 0;
+ Slapi_Value *sambaSamAccount;
+
+ krberr = krb5_init_context(&krbctx);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "krb5_init_context failed\n");
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_userpassword\n");
+
+ smods = slapi_mods_new();
+ dn = slapi_entry_get_ndn( targetEntry );
+
+ /* generate kerberos keys to be put into krbPrincipalKey */
+ svals = encrypt_encode_key(krbctx, targetEntry, newPasswd);
+ if (!svals) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "key encryption/encoding failed\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+ /* done with it */
+ krb5_free_context(krbctx);
+
+ slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbPrincipalKey", svals);
+
+ /* change Last Password Change field with the current date */
+ curtime = time(NULL);
+ if (!gmtime_r(&curtime, &utctime)) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "failed to retrieve current date (buggy gmtime_r ?)\n");
+ return LDAP_OPERATIONS_ERROR;
+ }
+ if (utctime.tm_year > 8099 || utctime.tm_mon > 11 || utctime.tm_mday > 31 ||
+ utctime.tm_hour > 23 || utctime.tm_min > 59 || utctime.tm_sec > 59) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "retrieved a bad date (buggy gmtime_r ?)\n");
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ snprintf(timestr, 16, "%04d%02d%02d%02d%02d%02dZ", utctime.tm_year+1900, utctime.tm_mon+1,
+ utctime.tm_mday, utctime.tm_hour, utctime.tm_min, utctime.tm_sec);
+
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbLastPwdChange", timestr);
+ /* TODO: krbPasswordExpiration, (krbMaxTicketLife, krbMaxRenewableAge, krbTicketFlags ?) */
+
+ sambaSamAccount = slapi_value_new_string("sambaSamAccount");
+ if (slapi_entry_attr_has_syntax_value(targetEntry, "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) {
+ if (encode_ntlm_keys((char *)newPasswd, ntlm_flags, &ntlm) != 0) {
+ return LDAP_OPERATIONS_ERROR;
+ }
+ 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);
+ }
+ }
+
+ /* commit changes */
+ ret = ipapwd_apply_mods(dn, smods);
+
+ slapi_mods_free(&smods);
+
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "<= ipapwd_userpassword: %d\n", ret);
+
+
+mod_done:
+ for (i = 0; svals[i]; i++) {
+ slapi_value_free(&svals[i]);
+ }
+ free(svals);
+ return ret;
+}
+
+/* Generate a new, basic random password */
+static int ipapwd_generate_basic_passwd( int passlen, char **genpasswd )
+{
+ unsigned char *data = NULL;
+ char *enc = NULL;
+ int datalen = LDAP_EXTOP_PASSMOD_RANDOM_BYTES;
+ int enclen = LDAP_EXTOP_PASSMOD_GEN_PASSWD_LEN + 1;
+
+ if ( genpasswd == NULL ) {
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ if ( passlen > 0 ) {
+ datalen = passlen * 3 / 4 + 1;
+ enclen = datalen * 4; /* allocate the large enough space */
+ }
+
+ data = (unsigned char *)slapi_ch_calloc( datalen, 1 );
+ enc = (char *)slapi_ch_calloc( enclen, 1 );
+
+ /* get random bytes from NSS */
+ PK11_GenerateRandom( data, datalen );
+
+ /* b64 encode the random bytes to get a password made up
+ * of printable characters. ldif_base64_encode() will
+ * zero-terminate the string */
+ (void)ldif_base64_encode( data, enc, passlen, -1 );
+
+ /* This will get freed by the caller */
+ *genpasswd = slapi_ch_malloc( 1 + passlen );
+
+ /* trim the password to the proper length */
+ PL_strncpyz( *genpasswd, enc, passlen + 1 );
+
+ slapi_ch_free( (void **)&data );
+ slapi_ch_free_string( &enc );
+
+ return LDAP_SUCCESS;
+}
+
+
+/* Password Modify Extended operation plugin function */
+int
+ipapwd_extop( Slapi_PBlock *pb )
+{
+ char *oid = NULL;
+ char *bindDN = NULL;
+ char *authmethod = NULL;
+ char *dn = NULL;
+ char *oldPasswd = NULL;
+ char *newPasswd = NULL;
+ char *errMesg = NULL;
+ int ret=0, rc=0, sasl_ssf=0, is_ssl=0, is_root=0;
+ ber_tag_t tag=0;
+ ber_len_t len=-1;
+ struct berval *extop_value = NULL;
+ BerElement *ber = NULL;
+ BerElement *response_ber = NULL;
+ Slapi_Entry *targetEntry=NULL;
+ /* Slapi_DN sdn; */
+
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipa_pwd_extop\n");
+
+ /* Before going any further, we'll make sure that the right extended operation plugin
+ * has been called: i.e., the OID shipped whithin the extended operation request must
+ * match this very plugin's OID: EXOP_PASSWD_OID. */
+ if ( slapi_pblock_get( pb, SLAPI_EXT_OP_REQ_OID, &oid ) != 0 ) {
+ errMesg = "Could not get OID value from request.\n";
+ rc = LDAP_OPERATIONS_ERROR;
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
+ errMesg );
+ goto free_and_return;
+ } else {
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
+ "Received extended operation request with OID %s\n", oid );
+ }
+
+ if ( strcasecmp( oid, EXOP_PASSWD_OID ) != 0) {
+ errMesg = "Request OID does not match Passwd OID.\n";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto free_and_return;
+ } else {
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
+ "Password Modify extended operation request confirmed.\n" );
+ }
+
+ /* Now , at least we know that the request was indeed a Password Modify one. */
+
+#ifdef LDAP_EXTOP_PASSMOD_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) {
+ errMesg = "Could not get SASL SSF from connection\n";
+ rc = LDAP_OPERATIONS_ERROR;
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
+ errMesg );
+ goto free_and_return;
+ }
+
+ if (slapi_pblock_get(pb, SLAPI_CONN_IS_SSL_SESSION, &is_ssl) != 0) {
+ errMesg = "Could not get IS SSL from connection\n";
+ rc = LDAP_OPERATIONS_ERROR;
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
+ errMesg );
+ goto free_and_return;
+ }
+
+ if ( (is_ssl <=1) && (sasl_ssf <= 1) ) {
+ errMesg = "Operation requires a secure connection.\n";
+ rc = LDAP_CONFIDENTIALITY_REQUIRED;
+ goto free_and_return;
+ }
+#endif
+
+ /* Get the ber value of the extended operation */
+ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value);
+
+ if ((ber = ber_init(extop_value)) == NULL)
+ {
+ errMesg = "PasswdModify Request decode failed.\n";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto free_and_return;
+ }
+
+ /* Format of request to parse
+ *
+ * PasswdModifyRequestValue ::= SEQUENCE {
+ * userIdentity [0] OCTET STRING OPTIONAL
+ * oldPasswd [1] OCTET STRING OPTIONAL
+ * newPasswd [2] OCTET STRING OPTIONAL }
+ *
+ * The request value field is optional. If it is
+ * provided, at least one field must be filled in.
+ */
+
+ /* ber parse code */
+ if ( ber_scanf( ber, "{") == LBER_ERROR )
+ {
+ /* The request field wasn't provided. We'll
+ * now try to determine the userid and verify
+ * knowledge of the old password via other
+ * means.
+ */
+ goto parse_req_done;
+ } else {
+ tag = ber_peek_tag( ber, &len);
+ }
+
+
+ /* identify userID field by tags */
+ if (tag == LDAP_EXTOP_PASSMOD_TAG_USERID )
+ {
+ if ( ber_scanf( ber, "a", &dn) == LBER_ERROR )
+ {
+ slapi_ch_free_string(&dn);
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed :{\n");
+ errMesg = "ber_scanf failed at userID parse.\n";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto free_and_return;
+ }
+
+ tag = ber_peek_tag( ber, &len);
+ }
+
+
+ /* identify oldPasswd field by tags */
+ if (tag == LDAP_EXTOP_PASSMOD_TAG_OLDPWD )
+ {
+ if ( ber_scanf( ber, "a", &oldPasswd ) == LBER_ERROR )
+ {
+ slapi_ch_free_string(&oldPasswd);
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed :{\n");
+ errMesg = "ber_scanf failed at oldPasswd parse.\n";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto free_and_return;
+ }
+ tag = ber_peek_tag( ber, &len);
+ }
+
+ /* identify newPasswd field by tags */
+ if (tag == LDAP_EXTOP_PASSMOD_TAG_NEWPWD )
+ {
+ if ( ber_scanf( ber, "a", &newPasswd ) == LBER_ERROR )
+ {
+ slapi_ch_free_string(&newPasswd);
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed :{\n");
+ errMesg = "ber_scanf failed at newPasswd parse.\n";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto free_and_return;
+ }
+ }
+
+parse_req_done:
+ /* Uncomment for debugging, otherwise we don't want to leak the password values into the log... */
+ /* LDAPDebug( LDAP_DEBUG_ARGS, "passwd: dn (%s), oldPasswd (%s) ,newPasswd (%s)\n",
+ dn, oldPasswd, newPasswd); */
+
+
+ /* Get Bind DN */
+ slapi_pblock_get( pb, SLAPI_CONN_DN, &bindDN );
+
+ /* If the connection is bound anonymously, we must refuse to process this operation. */
+ if (bindDN == NULL || *bindDN == '\0') {
+ /* Refuse the operation because they're bound anonymously */
+ errMesg = "Anonymous Binds are not allowed.\n";
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ goto free_and_return;
+ }
+
+ /* A new password was not supplied in the request, and we do not support
+ * password generation yet.
+ */
+ if (newPasswd == NULL || *newPasswd == '\0') {
+ errMesg = "Password generation not implemented.\n";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto free_and_return;
+ }
+
+ if (oldPasswd == NULL || *oldPasswd == '\0') {
+ /* If user is authenticated, they already gave their password during
+ the bind operation (or used sasl or client cert auth or OS creds) */
+ slapi_pblock_get(pb, SLAPI_CONN_AUTHMETHOD, &authmethod);
+ if (!authmethod || !strcmp(authmethod, SLAPD_AUTH_NONE)) {
+ errMesg = "User must be authenticated to the directory server.\n";
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ goto free_and_return;
+ }
+ }
+
+ /* Determine the target DN for this operation */
+ /* Did they give us a DN ? */
+ if (dn == NULL || *dn == '\0') {
+ /* Get the DN from the bind identity on this connection */
+ dn = slapi_ch_strdup(bindDN);
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop",
+ "Missing userIdentity in request, using the bind DN instead.\n");
+ }
+
+ slapi_pblock_set( pb, SLAPI_ORIGINAL_TARGET, dn );
+
+ /* Now we have the DN, look for the entry */
+ ret = ipapwd_getEntry(dn, &targetEntry);
+ /* If we can't find the entry, then that's an error */
+ if (ret) {
+ /* Couldn't find the entry, fail */
+ errMesg = "No such Entry exists.\n" ;
+ rc = LDAP_NO_SUCH_OBJECT ;
+ goto free_and_return;
+ }
+
+ /* First thing to do is to ask access control if the bound identity has
+ rights to modify the userpassword attribute on this entry. If not, then
+ we fail immediately with insufficient access. This means that we don't
+ leak any useful information to the client such as current password
+ wrong, etc.
+ */
+
+ is_root = slapi_dn_isroot(bindDN);
+ slapi_pblock_set(pb, SLAPI_REQUESTOR_ISROOT, &is_root);
+
+ /* In order to perform the access control check , we need to select a backend (even though
+ * we don't actually need it otherwise).
+ */
+ {
+ Slapi_Backend *be = NULL;
+
+ be = slapi_be_select(slapi_entry_get_sdn(targetEntry));
+ if (NULL == be) {
+ errMesg = "Failed to find backend for target entry";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto free_and_return;
+ }
+ slapi_pblock_set(pb, SLAPI_BACKEND, be);
+ }
+
+ ret = slapi_access_allowed ( pb, targetEntry, SLAPI_USERPWD_ATTR, NULL, SLAPI_ACL_WRITE );
+ if ( ret != LDAP_SUCCESS ) {
+ errMesg = "Insufficient access rights\n";
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ goto free_and_return;
+ }
+
+ /* Now we have the entry which we want to modify
+ * They gave us a password (old), check it against the target entry
+ * Is the old password valid ?
+ */
+ if (oldPasswd && *oldPasswd) {
+ /* If user is authenticated, they already gave their password during
+ the bind operation (or used sasl or client cert auth or OS creds) */
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "oldPasswd provided, but we will ignore it");
+ }
+
+
+ /* Now we're ready to make actual password change */
+ ret = ipapwd_userpassword(targetEntry, newPasswd);
+ if (ret != LDAP_SUCCESS) {
+ /* Failed to modify the password, e.g. because insufficient access allowed */
+ errMesg = "Failed to update password\n";
+ rc = ret;
+ goto free_and_return;
+ }
+
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "<= ipa_pwd_extop: %d\n", rc);
+
+ /* Free anything that we allocated above */
+ free_and_return:
+ slapi_ch_free_string(&oldPasswd);
+ slapi_ch_free_string(&newPasswd);
+ /* Either this is the same pointer that we allocated and set above,
+ * or whoever used it should have freed it and allocated a new
+ * value that we need to free here */
+ slapi_pblock_get( pb, SLAPI_ORIGINAL_TARGET, &dn );
+ slapi_ch_free_string(&dn);
+ slapi_pblock_set( pb, SLAPI_ORIGINAL_TARGET, NULL );
+ slapi_ch_free_string(&authmethod);
+
+ if ( targetEntry != NULL ){
+ slapi_entry_free (targetEntry);
+ }
+
+ if ( ber != NULL ){
+ ber_free(ber, 1);
+ ber = NULL;
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
+ errMesg ? errMesg : "success" );
+ send_ldap_result( pb, rc, NULL, errMesg, 0, NULL );
+
+
+ return( SLAPI_PLUGIN_EXTENDED_SENT_RESULT );
+
+}/* ipa_pwd_extop */
+
+
+static char *ipapwd_oid_list[] = {
+ EXOP_PASSWD_OID,
+ NULL
+};
+
+
+static char *ipapwd_name_list[] = {
+ "ipa_pwd_extop",
+ NULL
+};
+
+/* will read this from the krbSupportedEncSaltTypes in the krbRealmContainer later on */
+const char *krb_sup_encs[] = {
+ "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
+};
+
+#define KRBCHECK(ctx, err, fname) do { \
+ if (err) { \
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_start", \
+ "%s failed [%s]\n", fname, \
+ krb5_get_error_message(ctx, err)); \
+ return LDAP_OPERATIONS_ERROR; \
+ } } while(0)
+
+/* Init data structs */
+/* TODO: read input from tree */
+int ipapwd_start( Slapi_PBlock *pb )
+{
+ int krberr, i;
+ krb5_context krbctx;
+ krb5_data pwd, salt;
+ krb5_enctype etype;
+ char *config_dn;
+ Slapi_Entry *config_entry;
+ const char *stash_file;
+ int fd;
+ ssize_t r;
+ uint16_t e;
+ unsigned int l;
+ char *o;
+
+ krberr = krb5_init_context(&krbctx);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "krb5_init_context failed\n");
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; krb_sup_encs[i]; i++) /* count */ ;
+ keysalts = (struct krb5p_keysalt *)malloc(sizeof(struct krb5p_keysalt) * (i + 1));
+ if (!keysalts) {
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ for (i = 0, n_keysalts = 0; krb_sup_encs[i]; i++) {
+ char *enc, *salt;
+ krb5_int32 tmpenc;
+ krb5_int32 tmpsalt;
+ krb5_boolean similar;
+ int j;
+
+ enc = strdup(krb_sup_encs[i]);
+ if (!enc) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipapwd_start", "Allocation error\n");
+ krb5_free_context(krbctx);
+ 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 < n_keysalts; j++) {
+ krb5_c_enctype_compare(krbctx, keysalts[j].enc_type, tmpenc, &similar);
+ if (similar && (keysalts[j].salt_type == tmpsalt)) {
+ break;
+ }
+ }
+
+ if (j == n_keysalts) {
+ /* not found */
+ keysalts[j].enc_type = tmpenc;
+ keysalts[j].salt_type = tmpsalt;
+ n_keysalts++;
+ }
+
+ free(enc);
+ }
+
+ /*retrieve the master key from the stash file */
+ if (slapi_pblock_get(pb, SLAPI_TARGET_DN, &config_dn) != 0) {
+ slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "No config DN?\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ if (ipapwd_getEntry(config_dn, &config_entry) != LDAP_SUCCESS) {
+ slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "No config Entry?\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ stash_file = slapi_entry_attr_get_charptr(config_entry, "nsslapd-pluginarg0");
+ if (!stash_file) {
+ slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Missing Master key stash file path configuration entry (nsslapd-pluginarg0)!\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ fd = open(stash_file, O_RDONLY);
+ if (fd == -1) {
+ slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Missing Master key stash file!\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ r = read(fd, &e, 2); /* read enctype a local endian 16bit value */
+ if (r != 2) {
+ slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Error reading Master key stash file!\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ r = read(fd, &l, sizeof(l)); /* read the key length, a horrible sizeof(int) local endian value */
+ if (r != sizeof(l)) {
+ slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Error reading Master key stash file!\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ if (l == 0 || l > 1024) { /* the maximum key size should be 32 bytes, lets's not accept more than 1k anyway */
+ slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Invalid key lenght, Master key stash file corrupted?\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ o = malloc(l);
+ if (!o) {
+ slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Memory allocation problem!\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ r = read(fd, o, l);
+ if (r != l) {
+ slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Error reading Master key stash file!\n");
+ krb5_free_context(krbctx);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ close(fd);
+
+ kmkey.magic = KV5M_KEYBLOCK;
+ kmkey.enctype = e;
+ kmkey.length = l;
+ kmkey.contents = o;
+
+ krb5_free_context(krbctx);
+ return LDAP_SUCCESS;
+}
+
+/* Initialization function */
+int ipapwd_init( Slapi_PBlock *pb )
+{
+ char **argv;
+ char *oid;
+
+ /* Get the arguments appended to the plugin extendedop directive. The first argument
+ * (after the standard arguments for the directive) should contain the OID of the
+ * extended operation.
+ */
+
+ if ((slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &ipapwd_plugin_id) != 0)
+ || (ipapwd_plugin_id == NULL)) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipapwd_init", "Could not get identity or identity was NULL\n");
+ return( -1 );
+ }
+
+ /* Register the plug-in function as an extended operation
+ * plug-in function that handles the operation identified by
+ * OID 1.3.6.1.4.1.4203.1.11.1 . Also specify the version of the server
+ * plug-in */
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) ipapwd_start ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *) ipapwd_extop ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipapwd_oid_list ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipapwd_name_list ) != 0 ) {
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipapwd_init",
+ "Failed to set plug-in version, function, and OID.\n" );
+ return( -1 );
+ }
+
+ return( 0 );
+}