From e30cd6ba42c256d2016db45146d616f329455e86 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 29 Jan 2009 16:26:07 -0500 Subject: Mass tree reorganization for IPAv2. To view previous history of files use: % git log --follow -- renamed: ipa-server/autogen.sh -> autogen.sh renamed: ipa-server/ipa-kpasswd/Makefile.am -> daemons/ipa-kpasswd/Makefile.am renamed: ipa-server/ipa-kpasswd/README -> daemons/ipa-kpasswd/README renamed: ipa-server/ipa-kpasswd/ipa_kpasswd.c -> daemons/ipa-kpasswd/ipa_kpasswd.c renamed: ipa-server/ipa-kpasswd/ipa_kpasswd.init -> daemons/ipa-kpasswd/ipa_kpasswd.init renamed: ipa-server/ipa-slapi-plugins/Makefile.am -> daemons/ipa-slapi-plugins/Makefile.am renamed: ipa-server/ipa-slapi-plugins/README -> daemons/ipa-slapi-plugins/README renamed: ipa-server/ipa-slapi-plugins/dna/Makefile.am -> daemons/ipa-slapi-plugins/dna/Makefile.am renamed: ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif -> daemons/ipa-slapi-plugins/dna/dna-conf.ldif renamed: ipa-server/ipa-slapi-plugins/dna/dna.c -> daemons/ipa-slapi-plugins/dna/dna.c renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile.am -> daemons/ipa-slapi-plugins/ipa-memberof/Makefile.am renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c -> daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h -> daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c -> daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif -> daemons/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am -> daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README -> daemons/ipa-slapi-plugins/ipa-pwd-extop/README renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c -> daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif -> daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/Makefile.am -> daemons/ipa-slapi-plugins/ipa-winsync/Makefile.am renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/README -> daemons/ipa-slapi-plugins/ipa-winsync/README renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h renamed: ipa-server/xmlrpc-server/ipa-rewrite.conf -> install/conf/ipa-rewrite.conf renamed: ipa-server/xmlrpc-server/ipa.conf -> install/conf/ipa.conf renamed: ipa-server/xmlrpc-server/ssbrowser.html -> install/html/ssbrowser.html renamed: ipa-server/xmlrpc-server/unauthorized.html -> install/html/unauthorized.html renamed: ipa-server/ipa-install/share/60ipaconfig.ldif -> install/share/60ipaconfig.ldif renamed: ipa-server/ipa-install/share/60kerberos.ldif -> install/share/60kerberos.ldif renamed: ipa-server/ipa-install/share/60radius.ldif -> install/share/60radius.ldif renamed: ipa-server/ipa-install/share/60samba.ldif -> install/share/60samba.ldif renamed: ipa-server/ipa-install/share/Makefile.am -> install/share/Makefile.am renamed: ipa-server/ipa-install/share/bind.named.conf.template -> install/share/bind.named.conf.template renamed: ipa-server/ipa-install/share/bind.zone.db.template -> install/share/bind.zone.db.template renamed: ipa-server/ipa-install/share/bootstrap-template.ldif -> install/share/bootstrap-template.ldif renamed: ipa-server/ipa-install/share/certmap.conf.template -> install/share/certmap.conf.template renamed: ipa-server/ipa-install/share/default-aci.ldif -> install/share/default-aci.ldif renamed: ipa-server/ipa-install/share/default-keytypes.ldif -> install/share/default-keytypes.ldif renamed: ipa-server/ipa-install/share/dna-posix.ldif -> install/share/dna-posix.ldif renamed: ipa-server/ipa-install/share/encrypted_attribute.ldif -> install/share/encrypted_attribute.ldif renamed: ipa-server/ipa-install/share/fedora-ds.init.patch -> install/share/fedora-ds.init.patch renamed: ipa-server/ipa-install/share/indices.ldif -> install/share/indices.ldif renamed: ipa-server/ipa-install/share/kdc.conf.template -> install/share/kdc.conf.template renamed: ipa-server/ipa-install/share/kerberos.ldif -> install/share/kerberos.ldif renamed: ipa-server/ipa-install/share/krb.con.template -> install/share/krb.con.template renamed: ipa-server/ipa-install/share/krb5.conf.template -> install/share/krb5.conf.template renamed: ipa-server/ipa-install/share/krb5.ini.template -> install/share/krb5.ini.template renamed: ipa-server/ipa-install/share/krbrealm.con.template -> install/share/krbrealm.con.template renamed: ipa-server/ipa-install/share/master-entry.ldif -> install/share/master-entry.ldif renamed: ipa-server/ipa-install/share/memberof-task.ldif -> install/share/memberof-task.ldif renamed: ipa-server/ipa-install/share/ntp.conf.server.template -> install/share/ntp.conf.server.template renamed: ipa-server/ipa-install/share/ntpd.sysconfig.template -> install/share/ntpd.sysconfig.template renamed: ipa-server/ipa-install/share/preferences.html.template -> install/share/preferences.html.template renamed: ipa-server/ipa-install/share/referint-conf.ldif -> install/share/referint-conf.ldif renamed: ipa-server/ipa-install/share/schema_compat.uldif -> install/share/schema_compat.uldif renamed: ipa-server/ipa-install/share/unique-attributes.ldif -> install/share/unique-attributes.ldif renamed: ipa-server/ipa-install/Makefile.am -> install/tools/Makefile.am renamed: ipa-server/ipa-install/README -> install/tools/README renamed: ipa-server/ipa-compat-manage -> install/tools/ipa-compat-manage renamed: ipa-server/ipa-fix-CVE-2008-3274 -> install/tools/ipa-fix-CVE-2008-3274 renamed: ipa-server/ipa-ldap-updater -> install/tools/ipa-ldap-updater renamed: ipa-server/ipa-install/ipa-replica-install -> install/tools/ipa-replica-install renamed: ipa-server/ipa-install/ipa-replica-manage -> install/tools/ipa-replica-manage renamed: ipa-server/ipa-install/ipa-replica-prepare -> install/tools/ipa-replica-prepare renamed: ipa-server/ipa-install/ipa-server-certinstall -> install/tools/ipa-server-certinstall renamed: ipa-server/ipa-install/ipa-server-install -> install/tools/ipa-server-install renamed: ipa-server/ipa-upgradeconfig -> install/tools/ipa-upgradeconfig renamed: ipa-server/ipa-install/ipactl -> install/tools/ipactl renamed: ipa-server/man/Makefile.am -> install/tools/man/Makefile.am renamed: ipa-server/man/ipa-compat-manage.1 -> install/tools/man/ipa-compat-manage.1 renamed: ipa-server/man/ipa-ldap-updater.1 -> install/tools/man/ipa-ldap-updater.1 renamed: ipa-server/man/ipa-replica-install.1 -> install/tools/man/ipa-replica-install.1 renamed: ipa-server/man/ipa-replica-manage.1 -> install/tools/man/ipa-replica-manage.1 renamed: ipa-server/man/ipa-replica-prepare.1 -> install/tools/man/ipa-replica-prepare.1 renamed: ipa-server/man/ipa-server-certinstall.1 -> install/tools/man/ipa-server-certinstall.1 renamed: ipa-server/man/ipa-server-install.1 -> install/tools/man/ipa-server-install.1 renamed: ipa-server/man/ipa_kpasswd.8 -> install/tools/man/ipa_kpasswd.8 renamed: ipa-server/man/ipa_webgui.8 -> install/tools/man/ipa_webgui.8 renamed: ipa-server/man/ipactl.8 -> install/tools/man/ipactl.8 renamed: ipa-server/ipa-install/updates/Makefile.am -> install/updates/Makefile.am renamed: ipa-server/ipa-install/updates/RFC2307bis.update -> install/updates/RFC2307bis.update renamed: ipa-server/ipa-install/updates/RFC4876.update -> install/updates/RFC4876.update renamed: ipa-server/ipa-install/updates/indices.update -> install/updates/indices.update renamed: ipa-server/ipa-install/updates/nss_ldap.update -> install/updates/nss_ldap.update renamed: ipa-server/ipa-install/updates/replication.update -> install/updates/replication.update renamed: ipa-server/ipa-install/updates/winsync_index.update -> install/updates/winsync_index.update renamed: ipa-server/ipaserver/Makefile.am -> ipaserver/install/Makefile.am renamed: ipa-server/ipaserver/__init__.py -> ipaserver/install/__init__.py renamed: ipa-server/ipaserver/bindinstance.py -> ipaserver/install/bindinstance.py renamed: ipa-server/ipaserver/certs.py -> ipaserver/install/certs.py renamed: ipa-server/ipaserver/dsinstance.py -> ipaserver/install/dsinstance.py renamed: ipa-server/ipaserver/httpinstance.py -> ipaserver/install/httpinstance.py renamed: ipa-server/ipaserver/installutils.py -> ipaserver/install/installutils.py renamed: ipa-server/ipaserver/ipaldap.py -> ipaserver/install/ipaldap.py renamed: ipa-server/ipaserver/krbinstance.py -> ipaserver/install/krbinstance.py renamed: ipa-server/ipaserver/ldapupdate.py -> ipaserver/install/ldapupdate.py renamed: ipa-server/ipaserver/ntpinstance.py -> ipaserver/install/ntpinstance.py renamed: ipa-server/ipaserver/replication.py -> ipaserver/install/replication.py renamed: ipa-server/ipaserver/service.py -> ipaserver/install/service.py renamed: ipa-server/selinux/Makefile -> selinux/Makefile renamed: ipa-server/selinux/ipa-server-selinux.spec.in -> selinux/ipa-server-selinux.spec.in renamed: ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.fc -> selinux/ipa_kpasswd/ipa_kpasswd.fc renamed: ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.te -> selinux/ipa_kpasswd/ipa_kpasswd.te renamed: ipa-server/selinux/ipa_webgui/ipa_webgui.fc -> selinux/ipa_webgui/ipa_webgui.fc renamed: ipa-server/selinux/ipa_webgui/ipa_webgui.te -> selinux/ipa_webgui/ipa_webgui.te renamed: ipa-server/version.m4.in -> version.m4.in --- .../ipa-slapi-plugins/ipa-pwd-extop/Makefile.am | 46 + daemons/ipa-slapi-plugins/ipa-pwd-extop/README | 0 .../ipa-pwd-extop/ipa_pwd_extop.c | 4058 ++++++++++++++++++++ .../ipa-pwd-extop/pwd-extop-conf.ldif | 16 + 4 files changed, 4120 insertions(+) create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/README create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif (limited to 'daemons/ipa-slapi-plugins/ipa-pwd-extop') diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am new file mode 100644 index 000000000..540646f06 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am @@ -0,0 +1,46 @@ +NULL = + +INCLUDES = \ + -I. \ + -I$(srcdir) \ + -DPREFIX=\""$(prefix)"\" \ + -DBINDIR=\""$(bindir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DLIBEXECDIR=\""$(libexecdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + $(MOZLDAP_CFLAGS) \ + $(KRB5_CFLAGS) \ + $(SSL_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NULL) + +plugindir = $(libdir)/dirsrv/plugins +plugin_LTLIBRARIES = \ + libipa_pwd_extop.la \ + $(NULL) + +libipa_pwd_extop_la_SOURCES = \ + ipa_pwd_extop.c \ + $(NULL) + +libipa_pwd_extop_la_LDFLAGS = -avoid-version + +libipa_pwd_extop_la_LIBADD = \ + $(KRB5_LIBS) \ + $(SSL_LIBS) \ + $(MOZLDAP_LIBS) \ + $(NULL) + +appdir = $(IPA_DATA_DIR) +app_DATA = \ + pwd-extop-conf.ldif \ + $(NULL) + +EXTRA_DIST = \ + README \ + $(app_DATA) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/README b/daemons/ipa-slapi-plugins/ipa-pwd-extop/README new file mode 100644 index 000000000..e69de29bb 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 new file mode 100644 index 000000000..24acc8875 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c @@ -0,0 +1,4058 @@ +/** 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 + * + * Copyright (C) 2005 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#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 +#include +#include +#include +#include +#include + +#include +#include +#include +#define KRB5_PRIVATE 1 +#include +#include +#include +#include +#include +#include + +/* 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 +#define LDAP_EXTOP_PASSMOD_TAG_NEWPWD 0x82U + +/* ber tags for the PasswdModifyResponseValue sequence */ +#define LDAP_EXTOP_PASSMOD_TAG_GENPWD 0x80U + +/* OID of the extended operation handled by this plug-in */ +#define EXOP_PASSWD_OID "1.3.6.1.4.1.4203.1.11.1" + +/* OID to retrieve keytabs */ +#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); + +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; +}; + +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" + +static Slapi_PluginDesc pdesc = { + 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; +} + +static int filter_keys(struct ipapwd_krbcfg *krbcfg, struct ipapwd_keyset *kset) +{ + int i, j; + + for (i = 0; i < kset->num_keys; i++) { + for (j = 0; j < krbcfg->num_supp_encsalts; j++) { + if (kset->keys[i].ekey->type == + krbcfg->supp_encsalts[j].enc_type) { + break; + } + } + if (j == krbcfg->num_supp_encsalts) { /* not valid */ + + /* free key */ + if (kset->keys[i].ekey) { + free(kset->keys[i].ekey->value.bv_val); + free(kset->keys[i].ekey); + } + if (kset->keys[i].salt) { + free(kset->keys[i].salt->value.bv_val); + free(kset->keys[i].salt); + } + free(kset->keys[i].s2kparams.bv_val); + + /* move all remaining keys up by one */ + kset->num_keys -= 1; + + for (j = i; j < kset->num_keys; j++) { + kset->keys[j] = kset->keys[j + 1]; + } + + /* new key has been moved to this position, make sure + * we do not skip it, by neutralizing next increment */ + i--; + } + } + + 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) +{ + 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) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "no krbPrincipalName present in this entry\n"); + 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: + 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; +} + +/* 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; + + 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; + } + + krbPwdPolicyReference = slapi_entry_attr_get_charptr(target, "krbPwdPolicyReference"); + if (krbPwdPolicyReference) { + pdn = krbPwdPolicyReference; + scope = LDAP_SCOPE_BASE; + } 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 (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"); + } + + /* 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\n"); + 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 ret = 0, i = 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; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_SetPassword\n"); + + smods = slapi_mods_new(); + + /* generate kerberos keys to be put into krbPrincipalKey */ + svals = encrypt_encode_key(krbcfg, data); + 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) +{ + char *bindDN = NULL; + char *authmethod = NULL; + char *dn = NULL; + char *oldPasswd = NULL; + char *newPasswd = NULL; + char *errMesg = NULL; + int ret=0, rc=0, is_root=0; + ber_tag_t tag=0; + ber_len_t len=-1; + struct berval *extop_value = NULL; + BerElement *ber = NULL; + Slapi_Entry *targetEntry=NULL; + char *attrlist[] = {"*", "passwordHistory", NULL }; + struct ipapwd_data pwdata; + + /* 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_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_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, attrlist); + /* 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, "krbPrincipalKey", 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"); + } + + memset(&pwdata, 0, sizeof(pwdata)); + pwdata.target = targetEntry; + pwdata.dn = dn; + pwdata.password = newPasswd; + pwdata.timeNow = time(NULL); + pwdata.changetype = IPA_CHANGETYPE_NORMAL; + + /* + * (technically strcasecmp to compare DNs is not absolutely correct, + * but it should work for the cases we care about here) + */ + + /* determine type of password change */ + /* special cases */ + if ((strcasecmp(dn, bindDN) != 0) && + (strcasecmp(ipa_changepw_principal_dn, bindDN) != 0)) { + int i; + + pwdata.changetype = IPA_CHANGETYPE_ADMIN; + + for (i = 0; i < krbcfg->num_passsync_mgrs; i++) { + if (strcasecmp(krbcfg->passsync_mgrs[i], bindDN) == 0) { + pwdata.changetype = IPA_CHANGETYPE_DSMGR; + break; + } + } + } + + /* check the policy */ + ret = ipapwd_CheckPolicy(&pwdata); + if (ret) { + errMesg = "Password Fails to meet minimum strength criteria"; + if (ret & IPAPWD_POLICY_ERROR) { + slapi_pwpolicy_make_response_control(pb, -1, -1, ret & IPAPWD_POLICY_MASK); + rc = LDAP_CONSTRAINT_VIOLATION; + } else { + errMesg = "Internal error"; + rc = ret; + } + goto free_and_return; + } + + /* Now we're ready to set the kerberos key material */ + ret = ipapwd_SetPassword(krbcfg, &pwdata); + if (ret != LDAP_SUCCESS) { + /* Failed to modify the password, + * e.g. because insufficient access allowed */ + errMesg = "Failed to update password"; + if (ret > 0) { + rc = ret; + } else { + rc = LDAP_OPERATIONS_ERROR; + } + goto free_and_return; + } + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "<= ipapwd_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) slapi_entry_free(targetEntry); + if (ber) ber_free(ber, 1); + + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", errMesg ? errMesg : "success"); + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + + return SLAPI_PLUGIN_EXTENDED_SENT_RESULT; + +} + +/* Password Modify Extended operation plugin function */ +static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg) +{ + char *bindDN = NULL; + char *serviceName = NULL; + char *errMesg = NULL; + int ret=0, rc=0, is_root=0; + struct berval *extop_value = NULL; + BerElement *ber = NULL; + Slapi_PBlock *pbte = NULL; + Slapi_Entry *targetEntry=NULL; + struct berval *bval = NULL; + Slapi_Value **svals = NULL; + const char *bdn; + const Slapi_DN *bsdn; + Slapi_DN *sdn; + Slapi_Backend *be; + Slapi_Entry **es = NULL; + int scope, res; + char *filter; + char *attrlist[] = {"krbPrincipalKey", "krbLastPwdChange", NULL }; + krb5_context krbctx = NULL; + krb5_principal krbname = NULL; + krb5_error_code krberr; + int i, kvno; + Slapi_Mods *smods; + ber_tag_t rtag, ttmp; + ber_int_t tint; + ber_len_t tlen; + struct ipapwd_keyset *kset = NULL; + struct tm utctime; + char timestr[GENERALIZED_TIME_LENGTH+1]; + time_t time_now = time(NULL); + + svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *)); + if (!svals) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "memory allocation failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + + krberr = krb5_init_context(&krbctx); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_init_context failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + + /* 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; + } + + /* 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 = "KeytabGet Request decode failed.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + /* Format of request to parse + * + * KeytabGetRequest ::= SEQUENCE { + * serviceIdentity OCTET STRING + * keys SEQUENCE OF KrbKey, + * ... + * } + * + * KrbKey ::= SEQUENCE { + * key [0] EncryptionKey, + * salt [1] KrbSalt OPTIONAL, + * s2kparams [2] OCTET STRING OPTIONAL, + * ... + * } + * + * EncryptionKey ::= SEQUENCE { + * keytype [0] Int32, + * keyvalue [1] OCTET STRING + * } + * + * KrbSalt ::= SEQUENCE { + * type [0] Int32, + * salt [1] OCTET STRING OPTIONAL + * } + */ + + /* ber parse code */ + rtag = ber_scanf(ber, "{a{", &serviceName); + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + /* make sure it is a valid name */ + krberr = krb5_parse_name(krbctx, serviceName, &krbname); + if (krberr) { + slapi_ch_free_string(&serviceName); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_parse_name failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } else { + /* invert so that we get the canonical form + * (add REALM if not present for example) */ + char *canonname; + krberr = krb5_unparse_name(krbctx, krbname, &canonname); + if (krberr) { + slapi_ch_free_string(&serviceName); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_unparse_name failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + slapi_ch_free_string(&serviceName); + serviceName = canonname; + } + + /* check entry before doing any other decoding */ + + /* Find ancestor base DN */ + sdn = slapi_sdn_new_dn_byval(ipa_realm_dn); + be = slapi_be_select(sdn); + slapi_sdn_free(&sdn); + bsdn = slapi_be_getsuffix(be, 0); + if (bsdn == NULL) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Search for Base DN failed\n"); + errMesg = "PrincipalName not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + bdn = slapi_sdn_get_dn(bsdn); + scope = LDAP_SCOPE_SUBTREE; + + /* get Entry by krbPrincipalName */ + filter = slapi_ch_smprintf("(krbPrincipalName=%s)", serviceName); + + pbte = slapi_pblock_new(); + slapi_search_internal_set_pb(pbte, + bdn, scope, filter, attrlist, 0, + NULL, /* Controls */ + NULL, /* UniqueID */ + ipapwd_plugin_id, + 0); /* Flags */ + + /* do search the tree */ + ret = slapi_search_internal_pb(pbte); + slapi_pblock_get(pbte, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (ret == -1 || res != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Search for Principal failed, err (%d)\n", + res?res:ret); + errMesg = "PrincipalName not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + + /* get entries */ + slapi_pblock_get(pbte, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es); + if (!es) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "No entries ?!"); + errMesg = "PrincipalName not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + + /* count entries */ + for (i = 0; es[i]; i++) /* count */ ; + + /* if there is none or more than one, freak out */ + if (i != 1) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Too many entries, or entry no found (%d)", i); + errMesg = "PrincipalName not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + targetEntry = es[0]; + + /* 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_pblock_set(pb, SLAPI_BACKEND, be); + + /* Access Strategy: + * If the user has WRITE-ONLY access, a new keytab is set on the entry. + */ + + ret = slapi_access_allowed(pb, targetEntry, "krbPrincipalKey", NULL, SLAPI_ACL_WRITE); + if (ret != LDAP_SUCCESS) { + errMesg = "Insufficient access rights\n"; + rc = LDAP_INSUFFICIENT_ACCESS; + goto free_and_return; + } + + /* increment kvno (will be 1 if this is a new entry) */ + kvno = ipapwd_get_cur_kvno(targetEntry) + 1; + + /* ok access allowed, init kset and continue to parse ber buffer */ + + errMesg = "Unable to set key\n"; + rc = LDAP_OPERATIONS_ERROR; + + kset = malloc(sizeof(struct ipapwd_keyset)); + if (!kset) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + + /* this encoding assumes all keys have the same kvno */ + /* major-vno = 1 and minor-vno = 1 */ + kset->major_vno = 1; + kset->minor_vno = 1; + kset->kvno = kvno; + /* we also assum mkvno is 0 */ + kset->mkvno = 0; + + kset->keys = NULL; + kset->num_keys = 0; + + rtag = ber_peek_tag(ber, &tlen); + while (rtag == LBER_SEQUENCE) { + krb5_data plain; + krb5_enc_data cipher; + struct berval tval; + krb5_octet *kdata; + size_t klen; + + i = kset->num_keys; + + if (kset->keys) { + struct ipapwd_krbkey *newset; + + newset = realloc(kset->keys, sizeof(struct ipapwd_krbkey) * (i + 1)); + if (!newset) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + kset->keys = newset; + } else { + kset->keys = malloc(sizeof(struct ipapwd_krbkey)); + if (!kset->keys) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + } + kset->num_keys += 1; + + kset->keys[i].salt = NULL; + kset->keys[i].ekey = NULL; + kset->keys[i].s2kparams.bv_len = 0; + kset->keys[i].s2kparams.bv_val = NULL; + + /* EncryptionKey */ + rtag = ber_scanf(ber, "{t[{t[i]t[o]}]", &ttmp, &ttmp, &tint, &ttmp, &tval); + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + kset->keys[i].ekey = calloc(1, sizeof(struct ipapwd_krbkeydata)); + if (!kset->keys[i].ekey) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + + kset->keys[i].ekey->type = tint; + + plain.length = tval.bv_len; + plain.data = tval.bv_val; + + krberr = krb5_c_encrypt_length(krbctx, krbcfg->kmkey->enctype, plain.length, &klen); + if (krberr) { + free(tval.bv_val); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "krb encryption failed!\n"); + goto free_and_return; + } + + kdata = malloc(2 + klen); + if (!kdata) { + free(tval.bv_val); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + encode_int16(plain.length, kdata); + + kset->keys[i].ekey->value.bv_len = 2 + klen; + kset->keys[i].ekey->value.bv_val = (char *)kdata; + + cipher.ciphertext.length = klen; + cipher.ciphertext.data = (char *)kdata + 2; + + krberr = krb5_c_encrypt(krbctx, krbcfg->kmkey, 0, 0, &plain, &cipher); + if (krberr) { + free(tval.bv_val); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "krb encryption failed!\n"); + goto free_and_return; + } + + free(tval.bv_val); + + rtag = ber_peek_tag(ber, &tlen); + + /* KrbSalt */ + if (rtag == (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1)) { + + rtag = ber_scanf(ber, "t[{t[i]", &ttmp, &ttmp, &tint); + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + kset->keys[i].salt = calloc(1, sizeof(struct ipapwd_krbkeydata)); + if (!kset->keys[i].salt) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + + kset->keys[i].salt->type = tint; + + rtag = ber_peek_tag(ber, &tlen); + if (rtag == (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1)) { + + rtag = ber_scanf(ber, "t[o]}]", &ttmp, &tval); + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + kset->keys[i].salt->value = tval; + + rtag = ber_peek_tag(ber, &tlen); + } + } + + /* FIXME: s2kparams - NOT implemented yet */ + if (rtag == (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 2)) { + rtag = ber_scanf(ber, "t[x]}", &ttmp); + } else { + rtag = ber_scanf(ber, "}", &ttmp); + } + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + rtag = ber_peek_tag(ber, &tlen); + } + + ber_free(ber, 1); + ber = NULL; + + /* filter un-supported encodings */ + ret = filter_keys(krbcfg, kset); + if (ret) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "keyset filtering failed\n"); + goto free_and_return; + } + + /* check if we have any left */ + if (kset->num_keys == 0) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "keyset filtering rejected all proposed keys\n"); + errMesg = "All enctypes provided are unsupported"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto free_and_return; + } + + smods = slapi_mods_new(); + + /* change Last Password Change field with the current date */ + if (!gmtime_r(&(time_now), &utctime)) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "failed to retrieve current date (buggy gmtime_r ?)\n"); + slapi_mods_free(&smods); + 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); + + /* FIXME: set Password Expiration date ? */ +#if 0 + if (!gmtime_r(&(data->expireTime), &utctime)) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "failed to convert expiration date\n"); + slapi_ch_free_string(&randPasswd); + slapi_mods_free(&smods); + rc = 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); +#endif + + bval = encode_keys(kset); + if (!bval) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "encoding asn1 KrbSalt failed\n"); + slapi_mods_free(&smods); + goto free_and_return; + } + + 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"); + slapi_mods_free(&smods); + goto free_and_return; + } + + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbPrincipalKey", svals); + + /* commit changes */ + ret = ipapwd_apply_mods(slapi_entry_get_dn_const(targetEntry), smods); + + if (ret != LDAP_SUCCESS) { + slapi_mods_free(&smods); + goto free_and_return; + + } + slapi_mods_free(&smods); + + /* Format of response + * + * KeytabGetRequest ::= SEQUENCE { + * new_kvno Int32 + * SEQUENCE OF KeyTypes + * } + * + * * List of accepted enctypes * + * KeyTypes ::= SEQUENCE { + * enctype Int32 + * } + */ + + errMesg = "Internal Error\n"; + rc = LDAP_OPERATIONS_ERROR; + + ber = ber_alloc(); + if (!ber) { + goto free_and_return; + } + + ret = ber_printf(ber, "{i{", (ber_int_t)kvno); + if (ret == -1) { + goto free_and_return; + } + + for (i = 0; i < kset->num_keys; i++) { + ret = ber_printf(ber, "{i}", (ber_int_t)kset->keys[i].ekey->type); + if (ret == -1) { + goto free_and_return; + } + } + ret = ber_printf(ber, "}}"); + if (ret == -1) { + goto free_and_return; + } + + if (ret != -1) { + struct berval *bvp; + LDAPControl new_ctrl = {0}; + + ret = ber_flatten(ber, &bvp); + if (ret == -1) { + goto free_and_return; + } + + new_ctrl.ldctl_oid = KEYTAB_RET_OID; + new_ctrl.ldctl_value = *bvp; + new_ctrl.ldctl_iscritical = 0; + rc= slapi_pblock_set(pb, SLAPI_ADD_RESCONTROL, &new_ctrl); + ber_bvfree(bvp); + } + + /* Free anything that we allocated above */ +free_and_return: + free(serviceName); + if (kset) ipapwd_keyset_free(&kset); + + if (bval) ber_bvfree(bval); + if (ber) ber_free(ber, 1); + + if (pbte) { + slapi_free_search_results_internal(pbte); + slapi_pblock_destroy(pbte); + } + if (svals) { + for (i = 0; svals[i]; i++) { + slapi_value_free(&svals[i]); + } + free(svals); + } + + if (krbname) krb5_free_principal(krbctx, krbname); + if (krbctx) krb5_free_context(krbctx); + + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", errMesg ? errMesg : "success"); + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + + 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:""); + *errMesg = "Invalid DN"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + sdn = slapi_sdn_new_dn_byref(dn); + if (!sdn) { + *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; + char *errMesg = NULL; + char *oid = NULL; + int rc, ret; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_extop\n"); + + rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_CONN_SECURE); + if (rc) { + goto free_and_return; + } + + /* 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 OIDs: + * EXOP_PASSWD_OID or KEYTAB_SET_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) { + ret = ipapwd_chpwop(pb, krbcfg); + free_ipapwd_krbcfg(&krbcfg); + return ret; + } + if (strcasecmp(oid, KEYTAB_SET_OID) == 0) { + ret = ipapwd_setkeytab(pb, krbcfg); + free_ipapwd_krbcfg(&krbcfg); + return ret; + } + + errMesg = "Request OID does not match supported OIDs.\n"; + rc = LDAP_OPERATIONS_ERROR; + +free_and_return: + if (krbcfg) free_ipapwd_krbcfg(&krbcfg); + + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", errMesg); + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + + 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) +{ + int rc; + + if (is_krb) { + + pwdop->is_krb = 1; + + *svals = encrypt_encode_key(krbcfg, &pwdop->pwdata); + + if (!*svals) { + 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) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "Failed to generate NT/LM hashes\n"); + 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 ADD 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. + * 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, rc; + + 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)) { + + slapi_ch_free_string(&userpw); + + /* check if we have access to the unhashed user password */ + userpw = slapi_entry_attr_get_charptr(e, "unhashed#user#password"); + if (!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; + } + } + } + + rc = ipapwd_entry_checks(pb, e, + &is_root, &is_krb, &is_smb, + NULL, SLAPI_ACL_ADD); + if (rc) { + goto done; + } + + rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN); + if (rc) { + 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); + if (rc) { + 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); + 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) +{ + char *errMesg = "Internal operations error\n"; + 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_add\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() + * + * For some reason slapi_str2filter writes to its input + * which means you cannot pass in a string constant + * so this is a fix up function for that + */ +Slapi_Filter *ipapwd_string2filter(char *strfilter) +{ + Slapi_Filter *ret = NULL; + char *idontbelieveit = slapi_ch_strdup(strfilter); + + ret = slapi_str2filter(idontbelieveit); + + slapi_ch_free_string(&idontbelieveit); + + return ret; +} + +/* Init data structs */ +static int ipapwd_start( Slapi_PBlock *pb ) +{ + krb5_context krbctx; + krb5_error_code krberr; + char *realm = NULL; + char *config_dn; + char *partition_dn; + Slapi_Entry *config_entry = NULL; + int ret; + + krberr = krb5_init_context(&krbctx); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_start", "krb5_init_context failed\n"); + return LDAP_OPERATIONS_ERROR; + } + + if (slapi_pblock_get(pb, SLAPI_TARGET_DN, &config_dn) != 0) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "No config DN?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (ipapwd_getEntry(config_dn, &config_entry, NULL) != LDAP_SUCCESS) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "No config Entry?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + partition_dn = slapi_entry_attr_get_charptr(config_entry, "nsslapd-realmtree"); + if (!partition_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Missing partition configuration entry (nsslapd-realmTree)!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = krb5_get_default_realm(krbctx, &realm); + if (ret) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Failed to get default realm?!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + ipa_realm_dn = slapi_ch_smprintf("cn=%s,cn=kerberos,%s", realm, partition_dn); + if (!ipa_realm_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Out of memory ?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ipa_pwd_config_dn = slapi_ch_strdup(config_dn); + if (!ipa_pwd_config_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Out of memory ?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + ipa_changepw_principal_dn = + slapi_ch_smprintf("krbprincipalname=kadmin/changepw@%s,%s", + realm, ipa_realm_dn); + if (!ipa_changepw_principal_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Out of memory ?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = LDAP_SUCCESS; + +done: + free(realm); + krb5_free_context(krbctx); + if (config_entry) slapi_entry_free(config_entry); + return ret; +} + + +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, + KEYTAB_SET_OID, + NULL +}; + + +static char *ipapwd_name_list[] = { + "Password Change Extended Operation", + "Keytab Retrieval Extended Operation", + 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_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 ) +{ + int ret; + + /* 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. */ + + ret = slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &ipapwd_plugin_id); + if ((ret != 0) || (NULL == ipapwd_plugin_id)) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_init", + "Could not get identity or identity was NULL\n"); + return -1; + } + + if (ipapwd_ext_init() != 0) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Object Extension Operation failed\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 */ + 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_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); + if (ret) { + slapi_log_error( SLAPI_LOG_PLUGIN, "ipapwd_init", + "Failed to set plug-in version, function, and OID.\n" ); + return -1; + } + + slapi_register_plugin("preoperation", 1, + "ipapwd_pre_init", ipapwd_pre_init, + "IPA pwd pre ops", NULL, + ipapwd_plugin_id); + + slapi_register_plugin("postoperation", 1, + "ipapwd_post_init", ipapwd_post_init, + "IPA pwd post ops", NULL, + ipapwd_plugin_id); + + return 0; +} diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif b/daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif new file mode 100644 index 000000000..e31a8e79b --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif @@ -0,0 +1,16 @@ +dn: cn=ipa_pwd_extop,cn=plugins,cn=config +changetype: add +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: ipa_pwd_extop +nsslapd-pluginpath: libipa_pwd_extop +nsslapd-plugininitfunc: ipapwd_init +nsslapd-plugintype: extendedop +nsslapd-pluginenabled: on +nsslapd-pluginid: ipa_pwd_extop +nsslapd-pluginversion: 1.0 +nsslapd-pluginvendor: RedHat +nsslapd-plugindescription: Support saving passwords in multiple formats for different consumers (krb5, samba, freeradius, etc.) +nsslapd-plugin-depends-on-type: database +nsslapd-realmTree: $SUFFIX -- cgit