diff options
author | Karl MacMillan <kmacmillan@mentalrootkit.com> | 2007-07-27 13:42:02 -0400 |
---|---|---|
committer | Karl MacMillan <kmacmillan@mentalrootkit.com> | 2007-07-27 13:42:02 -0400 |
commit | 9d5b946fdafa77b7aca360d2d1e8ce48980c559f (patch) | |
tree | 993e6ffc6ff823af3ffe91e1428a23bb992a2c0f /ipa-server/ipa-slapi-plugins/ipa-pwd-extop | |
parent | a471ebe7517a04d67b788b3cfd59cb9aa451da0a (diff) | |
download | freeipa-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')
4 files changed, 1378 insertions, 0 deletions
diff --git a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile new file mode 100644 index 000000000..2a5646432 --- /dev/null +++ b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile @@ -0,0 +1,5 @@ +all: + gcc ipa_pwd_extop.c -I/usr/include -I/usr/include/nss3 -I/usr/include/mozldap -I/usr/include/nspr4 -I/usr/include/fedora-ds -lkrb5 -lmhash -llber -lssl -shared -g -fPIC -DPIC -Wl,-soname -Wl,libipa_pwd_extop.so -o libipa_pwd_extop.so + +install: + cp -f libipa_pwd_extop.so /usr/lib/fedora-ds/plugins/ diff --git a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README 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 ); +} diff --git a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/plugin-conf.ldif b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/plugin-conf.ldif new file mode 100644 index 000000000..738ef7ab5 --- /dev/null +++ b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/plugin-conf.ldif @@ -0,0 +1,14 @@ +dn: cn=ipa_pwd_extop,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: ipa_pwd_extop +nsslapd-pluginpath: /usr/lib/fedora-ds/plugins/libipa_pwd_extop.so +nsslapd-plugininitfunc: ipapwd_init +nsslapd-plugintype: extendedop +nsslapd-pluginenabled: on +nsslapd-pluginid: Multi-hash Change Password Extended Operation +nsslapd-pluginversion: 1.0 +nsslapd-pluginvendor: RedHat +nsslapd-plugindescription: Support saving passwords in multiple fornmats for different consumers like krb5, samba, freeradius, etc. +nsslapd-pluginarg0: /var/kerberos/krb5kdc/.k5.$REALM |