summaryrefslogtreecommitdiffstats
path: root/daemons
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2009-01-29 16:26:07 -0500
committerRob Crittenden <rcritten@redhat.com>2009-02-03 15:27:14 -0500
commite30cd6ba42c256d2016db45146d616f329455e86 (patch)
treed4c5291095c80c92bc4803fe7f20fc2838124ffa /daemons
parentc4ed025001895bfc65c613cabbbfcb27c19cc29f (diff)
downloadfreeipa-e30cd6ba42c256d2016db45146d616f329455e86.tar.gz
freeipa-e30cd6ba42c256d2016db45146d616f329455e86.tar.xz
freeipa-e30cd6ba42c256d2016db45146d616f329455e86.zip
Mass tree reorganization for IPAv2. To view previous history of files use:
% git log --follow -- <file> 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
Diffstat (limited to 'daemons')
-rw-r--r--daemons/ipa-kpasswd/Makefile.am58
-rw-r--r--daemons/ipa-kpasswd/README2
-rw-r--r--daemons/ipa-kpasswd/ipa_kpasswd.c1388
-rw-r--r--daemons/ipa-kpasswd/ipa_kpasswd.init83
-rw-r--r--daemons/ipa-slapi-plugins/Makefile.am16
-rw-r--r--daemons/ipa-slapi-plugins/README0
-rw-r--r--daemons/ipa-slapi-plugins/dna/Makefile.am42
-rw-r--r--daemons/ipa-slapi-plugins/dna/dna-conf.ldif14
-rw-r--r--daemons/ipa-slapi-plugins/dna/dna.c1462
-rw-r--r--daemons/ipa-slapi-plugins/ipa-memberof/Makefile.am43
-rw-r--r--daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c2244
-rw-r--r--daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h100
-rw-r--r--daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c312
-rw-r--r--daemons/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif14
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am46
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/README0
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c4058
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif16
-rw-r--r--daemons/ipa-slapi-plugins/ipa-winsync/Makefile.am43
-rw-r--r--daemons/ipa-slapi-plugins/ipa-winsync/README0
-rw-r--r--daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif27
-rw-r--r--daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c975
-rw-r--r--daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c1177
-rw-r--r--daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h160
24 files changed, 12280 insertions, 0 deletions
diff --git a/daemons/ipa-kpasswd/Makefile.am b/daemons/ipa-kpasswd/Makefile.am
new file mode 100644
index 000000000..5f95fdef3
--- /dev/null
+++ b/daemons/ipa-kpasswd/Makefile.am
@@ -0,0 +1,58 @@
+NULL =
+
+INCLUDES = \
+ -I. \
+ -I$(srcdir) \
+ -DPREFIX=\""$(prefix)"\" \
+ -DBINDIR=\""$(bindir)"\" \
+ -DLIBDIR=\""$(libdir)"\" \
+ -DLIBEXECDIR=\""$(libexecdir)"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ $(LDAP_CFLAGS) \
+ $(KRB5_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(NULL)
+
+sbin_PROGRAMS = \
+ ipa_kpasswd \
+ $(NULL)
+
+ipa_kpasswd_SOURCES = \
+ ipa_kpasswd.c \
+ $(NULL)
+
+ipa_kpasswd_LDADD = \
+ $(LDAP_LIBS) \
+ $(KRB5_LIBS) \
+ $(NULL)
+
+install-exec-local:
+ mkdir -p $(DESTDIR)$(localstatedir)/cache/ipa/kpasswd
+ chmod 700 $(DESTDIR)$(localstatedir)/cache/ipa/kpasswd
+
+uninstall-local:
+ -rmdir $(DESTDIR)$(localstatedir)/cache/ipa/kpasswd
+ -rmdir $(DESTDIR)$(localstatedir)/cache/ipa
+
+EXTRA_DIST = \
+ README \
+ ipa_kpasswd.init \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
+
+initdir=$(sysconfdir)/rc.d/init.d
+
+install-data-hook: ipa_kpasswd.init
+
+ if test '!' -d $(DESTDIR)$(initdir); then \
+ $(mkinstalldirs) $(DESTDIR)$(initdir); \
+ chmod 755 $(DESTDIR)$(initdir); \
+ fi
+
+ $(INSTALL_SCRIPT) $(srcdir)/ipa_kpasswd.init $(DESTDIR)$(initdir)/ipa_kpasswd
+
+uninstall-hook:
+ rm -f $(DESTDIR)$(initdir)/ipa_kpasswd
diff --git a/daemons/ipa-kpasswd/README b/daemons/ipa-kpasswd/README
new file mode 100644
index 000000000..c0a2767a4
--- /dev/null
+++ b/daemons/ipa-kpasswd/README
@@ -0,0 +1,2 @@
+This is an implementation of the RFC3244 kpasswd protocol.
+It is used to proxy password change operations to Directory Server.
diff --git a/daemons/ipa-kpasswd/ipa_kpasswd.c b/daemons/ipa-kpasswd/ipa_kpasswd.c
new file mode 100644
index 000000000..f2d3490f9
--- /dev/null
+++ b/daemons/ipa-kpasswd/ipa_kpasswd.c
@@ -0,0 +1,1388 @@
+
+/* Kpasswd-LDAP proxy */
+
+/* Authors: Simo Sorce <ssorce@redhat.com>
+ *
+ * Copyright (C) 2007, 2008 Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * 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 only
+ *
+ * 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
+ */
+
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sys/poll.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <time.h>
+#include <krb5.h>
+#ifdef WITH_MOZLDAP
+#include <mozldap/ldap.h>
+#else
+#define LDAP_DEPRECATED 1
+#include <ldap.h>
+#endif
+#include <sasl/sasl.h>
+#include <ifaddrs.h>
+
+#define DEFAULT_KEYTAB "FILE:/var/kerberos/krb5kdc/kpasswd.keytab"
+#define TMP_TEMPLATE "/var/cache/ipa/kpasswd/krb5_cc.XXXXXX"
+#define KPASSWD_PORT 464
+
+#ifdef WITH_MOZLDAP
+/* From OpenLDAP's ldap.h */
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID ((ber_tag_t) 0x80U)
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ((ber_tag_t) 0x82U)
+#endif
+
+/* blacklist entries are released only BLCAKLIST_TIMEOUT seconds
+ * after the children performing the noperation has finished.
+ * this is to avoid races */
+
+#define BLACKLIST_TIMEOUT 5
+
+struct blacklist {
+ struct blacklist *next;
+ char *address;
+ pid_t pid;
+ time_t expire;
+};
+
+static struct blacklist *global_blacklist = NULL;
+
+struct socklist {
+ int fd;
+ int socktype;
+ int dest_addr_len;
+ struct sockaddr_storage dest_addr;
+ struct socklist *next;
+};
+
+int check_blacklist(char *address)
+{
+ struct blacklist *bl, *prev_bl;
+ time_t now = time(NULL);
+
+ if (!global_blacklist) {
+ return 0;
+ }
+
+ prev_bl = NULL;
+ bl = global_blacklist;
+ while (bl) {
+ if (bl->expire && (bl->expire < now)) {
+ if (prev_bl) {
+ prev_bl->next = bl->next;
+ free(bl->address);
+ free(bl);
+ bl = prev_bl->next;
+ } else {
+ global_blacklist = bl->next;
+ free(bl->address);
+ free(bl);
+ bl = global_blacklist;
+ }
+ continue;
+ }
+
+ if (strcmp(address, bl->address) == 0) {
+ return 1;
+ }
+
+ prev_bl = bl;
+ bl = bl->next;
+ }
+
+ return 0;
+}
+
+int add_blacklist(pid_t pid, char *address)
+{
+ struct blacklist *bl, *gbl;
+
+ bl = malloc(sizeof(struct blacklist));
+ if (!bl) return -1;
+
+ bl->next = NULL;
+ bl->pid = pid;
+ bl->expire = 0;
+ bl->address = strdup(address);
+ if (!bl->address) {
+ free(bl);
+ return -1;
+ }
+
+ if (!global_blacklist) {
+ global_blacklist = bl;
+ return 0;
+ }
+
+ gbl = global_blacklist;
+ while (gbl->next) {
+ gbl = gbl->next;
+ }
+ gbl->next = bl;
+ return 0;
+}
+
+int remove_blacklist(pid_t pid)
+{
+ struct blacklist *bl;
+
+ if (!global_blacklist) {
+ return -1;
+ }
+
+ bl = global_blacklist;
+ while (bl) {
+ if (pid == bl->pid) {
+ bl->expire = time(NULL) + BLACKLIST_TIMEOUT;
+ return 0;
+ }
+ bl = bl->next;
+ }
+ return -1;
+}
+
+int debug = 0;
+char *srv_pri_name = "kadmin/changepw";
+char *keytab_name = NULL;
+
+static int get_krb5_ticket(char *tmp_file)
+{
+ char *ccname;
+ char *realm_name = NULL;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_principal kprincpw;
+ krb5_creds my_creds;
+ krb5_get_init_creds_opt options;
+ int krberr, ret;
+
+ krberr = krb5_init_context(&context);
+ if (krberr) {
+ syslog(LOG_ERR, "Failed to init kerberos context");
+ return -1;
+ }
+
+ krberr = krb5_get_default_realm(context, &realm_name);
+ if (krberr) {
+ syslog(LOG_ERR, "Failed to get default realm name: %s",
+ krb5_get_error_message(context, krberr));
+ ret = -1;
+ goto done;
+ }
+
+ krberr = krb5_build_principal(context, &kprincpw,
+ strlen(realm_name), realm_name,
+ "kadmin", "changepw", NULL);
+ if (krberr) {
+ syslog(LOG_ERR, "Unable to build principal: %s",
+ krb5_get_error_message(context, krberr));
+ ret = -1;
+ goto done;
+ }
+
+ krberr = krb5_kt_resolve(context, keytab_name, &keytab);
+ if (krberr) {
+ syslog(LOG_ERR, "Failed to read keytab file: %s",
+ krb5_get_error_message(context, krberr));
+ ret = -1;
+ goto done;
+ }
+
+ ret = asprintf(&ccname, "FILE:%s", tmp_file);
+ if (ret == -1) {
+ syslog(LOG_ERR, "Out of memory!");
+ goto done;
+ }
+
+ ret = setenv("KRB5CCNAME", ccname, 1);
+ if (ret == -1) {
+ syslog(LOG_ERR, "Unable to set env. variable KRB5CCNAME!");
+ goto done;
+ }
+
+ krberr = krb5_cc_resolve(context, ccname, &ccache);
+ if (krberr) {
+ syslog(LOG_ERR, "Failed to set cache name: %s",
+ krb5_get_error_message(context, krberr));
+ ret = -1;
+ goto done;
+ }
+
+ memset(&my_creds, 0, sizeof(my_creds));
+ memset(&options, 0, sizeof(options));
+
+ krb5_get_init_creds_opt_set_address_list(&options, NULL);
+ krb5_get_init_creds_opt_set_forwardable(&options, 0);
+ krb5_get_init_creds_opt_set_proxiable(&options, 0);
+ /* set a very short lifetime, we don't keep the ticket around */
+ krb5_get_init_creds_opt_set_tkt_life(&options, 300);
+
+ krberr = krb5_get_init_creds_keytab(context, &my_creds, kprincpw,
+ keytab, 0, NULL,
+ &options);
+
+ if (krberr) {
+ syslog(LOG_ERR, "Failed to init credentials: %s",
+ krb5_get_error_message(context, krberr));
+ ret = -1;
+ goto done;
+ }
+
+ krb5_cc_initialize(context, ccache, kprincpw);
+ if (krberr) {
+ syslog(LOG_ERR, "Failed to init ccache: %s",
+ krb5_get_error_message(context, krberr));
+ ret = -1;
+ goto done;
+ }
+
+ krberr = krb5_cc_store_cred(context, ccache, &my_creds);
+ if (krberr) {
+ syslog(LOG_ERR, "Failed to store creds: %s",
+ krb5_get_error_message(context, krberr));
+ ret = -1;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ /* TODO: mem cleanup */
+ if (keytab) krb5_kt_close(context, keytab);
+ if (context) krb5_free_context(context);
+ return ret;
+}
+
+int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *sit)
+{
+ sasl_interact_t *in = NULL;
+ int ret = LDAP_OTHER;
+ char *realm_name = (char *)priv_data;
+
+ if (!ld) return LDAP_PARAM_ERROR;
+
+ for (in = sit; in && in->id != SASL_CB_LIST_END; in++) {
+ switch(in->id) {
+ case SASL_CB_USER:
+ in->result = srv_pri_name;
+ in->len = strlen(srv_pri_name);
+ ret = LDAP_SUCCESS;
+ break;
+ case SASL_CB_GETREALM:
+ in->result = realm_name;
+ in->len = strlen(realm_name);
+ ret = LDAP_SUCCESS;
+ break;
+ default:
+ if (debug > 0) {
+ syslog(LOG_ERR,
+ "Unhandled SASL int. option %ld",
+ in->id);
+ }
+ in->result = NULL;
+ in->len = 0;
+ ret = LDAP_OTHER;
+ }
+ }
+ return ret;
+}
+
+/* from DS ldaprot.h */
+#define LDAP_TAG_PWP_WARNING 0xA0 /* context specific + constructed + 0 */
+#define LDAP_TAG_PWP_SECSLEFT 0x80L /* context specific + primitive */
+#define LDAP_TAG_PWP_GRCLOGINS 0x81L /* context specific + primitive + 1 */
+#define LDAP_TAG_PWP_ERROR 0x81L /* context specific + primitive + 1 */
+
+int ldap_pwd_change(char *client_name, char *realm_name, krb5_data pwd, char **errstr)
+{
+ char *tmp_file = NULL;
+ int version;
+ LDAP *ld = NULL;
+ BerElement *ctrl = NULL;
+ BerElement *sctrl = NULL;
+ struct berval *control = NULL;
+ struct berval newpw;
+ char hostname[1024];
+ struct berval **ncvals;
+ char *ldap_base = NULL;
+ char *filter;
+ char *attrs[] = {"krbprincipalname", NULL};
+ char *root_attrs[] = {"namingContexts", NULL};
+ char *userdn = NULL;
+ char *retoid = NULL;
+ struct berval *retdata = NULL;
+ struct timeval tv;
+ LDAPMessage *entry, *res = NULL;
+ LDAPControl **srvctrl = NULL;
+ char *exterr0 = NULL;
+ char *exterr1 = NULL;
+ char *exterr2 = NULL;
+ char *err = NULL;
+ int msgid;
+ int ret, rc;
+ int fd;
+ int kpwd_err = KRB5_KPASSWD_HARDERROR;
+
+ tmp_file = strdup(TMP_TEMPLATE);
+ if (!tmp_file) {
+ syslog(LOG_ERR, "Out of memory!");
+ goto done;
+ }
+
+ fd = mkstemp(tmp_file);
+ if (fd == -1) {
+ syslog(LOG_ERR,
+ "Failed to create tmp file with errno: %d", errno);
+ goto done;
+ }
+ /* close mimmediately, we don't need to keep the file open,
+ * just that it exist and has a unique name */
+ close(fd);
+
+ /* In the long term we may want to do this in the main daemon
+ * and just renew when needed.
+ * Right now do it at every password change for robustness */
+ ret = get_krb5_ticket(tmp_file);
+ if (ret) {
+ syslog(LOG_ERR, "Unable to kinit!");
+ goto done;
+ }
+
+ newpw.bv_len = pwd.length;
+ newpw.bv_val = pwd.data;
+
+ /* retrieve server name and build uri */
+ ret = gethostname(hostname, 1023);
+ if (ret == -1) {
+ syslog(LOG_ERR, "Unable to get the hostname!");
+ goto done;
+ }
+
+ /* connect to ldap server */
+ /* TODO: support referrals ? */
+ ld = ldap_init(hostname, 389);
+ if(ld == NULL) {
+ syslog(LOG_ERR, "Unable to connect to ldap server");
+ goto done;
+ }
+
+ version = LDAP_VERSION3;
+ ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
+ if (ret != LDAP_SUCCESS) {
+ syslog(LOG_ERR, "Unable to set ldap protocol version");
+ goto done;
+ }
+
+ ret = ldap_sasl_interactive_bind_s(ld,
+ NULL, "GSSAPI",
+ NULL, NULL,
+ LDAP_SASL_AUTOMATIC,
+ ldap_sasl_interact, realm_name);
+ if (ret != LDAP_SUCCESS) {
+ syslog(LOG_ERR, "Unable to bind to ldap server");
+ goto done;
+ }
+
+ /* find base dn */
+ /* TODO: address the case where we have multiple naming contexts */
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ ret = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE,
+ "objectclass=*", root_attrs, 0,
+ NULL, NULL, &tv, 0, &res);
+
+ if (ret != LDAP_SUCCESS) {
+ syslog(LOG_ERR,
+ "Search for %s on rootdse failed with error %d",
+ root_attrs[0], ret);
+ goto done;
+ }
+
+ /* for now just use the first result we get */
+ entry = ldap_first_entry(ld, res);
+ ncvals = ldap_get_values_len(ld, entry, root_attrs[0]);
+ if (!ncvals) {
+ syslog(LOG_ERR, "No values for %s", root_attrs[0]);
+ goto done;
+ }
+
+ ldap_base = strdup(ncvals[0]->bv_val);
+
+ ldap_value_free_len(ncvals);
+ ldap_msgfree(res);
+
+ /* find user dn */
+ ret = asprintf(&filter, "krbPrincipalName=%s", client_name);
+ if (ret == -1) {
+ syslog(LOG_ERR, "Out of memory!");
+ goto done;
+ }
+
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ ret = ldap_search_ext_s(ld, ldap_base, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 1, NULL, NULL, &tv, 0, &res);
+
+ if (ret != LDAP_SUCCESS) {
+ syslog(LOG_ERR, "Search for %s failed with error %d",
+ filter, ret);
+ if (ret == LDAP_CONSTRAINT_VIOLATION) {
+ *errstr = strdup("Password Change Failed");
+ kpwd_err = KRB5_KPASSWD_SOFTERROR;
+ }
+ goto done;
+ }
+ free(filter);
+
+ /* for now just use the first result we get */
+ entry = ldap_first_entry(ld, res);
+ userdn = ldap_get_dn(ld, entry);
+
+ ldap_msgfree(res);
+ res = NULL;
+
+ if (!userdn) {
+ syslog(LOG_ERR, "No userdn, can't change password!");
+ goto done;
+ }
+
+ /* build password change control */
+ ctrl = ber_alloc_t(LBER_USE_DER);
+ if (!ctrl) {
+ syslog(LOG_ERR, "Out of memory!");
+ goto done;
+ }
+
+ ber_printf(ctrl, "{tstO}",
+ LDAP_TAG_EXOP_MODIFY_PASSWD_ID, userdn,
+ LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, &newpw);
+
+ ret = ber_flatten(ctrl, &control);
+ if (ret < 0) {
+ syslog(LOG_ERR, "ber flattening failed!");
+ goto done;
+ }
+
+ /* perform password change */
+ ret = ldap_extended_operation(ld,
+ LDAP_EXOP_MODIFY_PASSWD,
+ control, NULL, NULL,
+ &msgid);
+ if (ret != LDAP_SUCCESS) {
+ syslog(LOG_ERR, "ldap_extended_operation() failed. (%d)", ret);
+ goto done;
+ }
+
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ ret = ldap_result(ld, msgid, 1, &tv, &res);
+ if (ret == -1) {
+ ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &rc);
+ syslog(LOG_ERR, "ldap_result() failed. (%d)", rc);
+ goto done;
+ }
+
+ ret = ldap_parse_extended_result(ld, res, &retoid, &retdata, 0);
+ if(ret != LDAP_SUCCESS) {
+ syslog(LOG_ERR, "ldap_parse_extended_result() failed.");
+ ldap_msgfree(res);
+ goto done;
+ }
+ if (retoid || retdata) {
+ syslog(LOG_ERR, "ldap_parse_extended_result() returned data, but we don't handle it yet.");
+ }
+
+ ret = ldap_parse_result(ld, res, &rc, NULL, &err, NULL, &srvctrl, 0);
+ if(ret != LDAP_SUCCESS) {
+ syslog(LOG_ERR, "ldap_parse_result() failed.");
+ goto done;
+ }
+ if (rc != LDAP_SUCCESS) {
+ if (rc == LDAP_CONSTRAINT_VIOLATION) {
+ kpwd_err = KRB5_KPASSWD_SOFTERROR;
+ }
+ ret = LDAP_OPERATIONS_ERROR;
+ }
+ if (err) {
+ syslog(LOG_ERR, "ldap_parse_result(): [%s]", err);
+ ldap_memfree(err);
+ }
+
+ if (srvctrl) {
+
+ LDAPControl *pprc = NULL;
+ int i;
+
+ for (i = 0; srvctrl[i]; i++) {
+ if (0 == strcmp(srvctrl[i]->ldctl_oid, LDAP_CONTROL_PASSWORDPOLICYRESPONSE)) {
+ pprc = srvctrl[i];
+ }
+ }
+ if (pprc) {
+ sctrl = ber_init(&pprc->ldctl_value);
+ }
+
+ if (sctrl) {
+ /*
+ * PasswordPolicyResponseValue ::= SEQUENCE {
+ * warning [0] CHOICE OPTIONAL {
+ * timeBeforeExpiration [0] INTEGER (0 .. maxInt),
+ * graceLoginsRemaining [1] INTEGER (0 .. maxInt) }
+ * error [1] ENUMERATED OPTIONAL {
+ * passwordExpired (0),
+ * accountLocked (1),
+ * changeAfterReset (2),
+ * passwordModNotAllowed (3),
+ * mustSupplyOldPassword (4),
+ * invalidPasswordSyntax (5),
+ * passwordTooShort (6),
+ * passwordTooYoung (7),
+ * passwordInHistory (8) } }
+ */
+
+ ber_tag_t rtag, btag;
+ ber_int_t bint;
+ rtag = ber_scanf(sctrl, "{t", &btag);
+ if (btag == LDAP_TAG_PWP_WARNING) {
+ rtag = ber_scanf(sctrl, "{ti}", &btag, &bint);
+ if (btag == LDAP_TAG_PWP_SECSLEFT) {
+ ret = asprintf(&exterr2, " (%d seconds left before password expires)", bint);
+ } else {
+ ret = asprintf(&exterr2, " (%d grace logins remaining)", bint);
+ }
+ if (ret == -1) {
+ syslog(LOG_ERR, "OOM while creating error message ...");
+ exterr2 = NULL;
+ }
+ rtag = ber_scanf(sctrl, "t", &btag);
+ }
+ if (btag == LDAP_TAG_PWP_ERROR) {
+ rtag = ber_scanf(sctrl, "e", &bint);
+ switch(bint) {
+ case 0:
+ ret = asprintf(&exterr1, " Err%d: Password Expired.", bint);
+ break;
+ case 1:
+ ret = asprintf(&exterr1, " Err%d: Account locked.", bint);
+ break;
+ case 2:
+ ret = asprintf(&exterr1, " Err%d: Password changed after reset.", bint);
+ break;
+ case 3:
+ ret = asprintf(&exterr1, " Err%d: Password change not allowed.", bint);
+ break;
+ case 4:
+ ret = asprintf(&exterr1, " Err%d: [Shouldn't happen].", bint);
+ break;
+ case 5:
+ ret = asprintf(&exterr1, " Err%d: Password too simple.", bint);
+ break;
+ case 6:
+ ret = asprintf(&exterr1, " Err%d: Password too short.", bint);
+ break;
+ case 7:
+ ret = asprintf(&exterr1, " Err%d: Too soon to change password.", bint);
+ break;
+ case 8:
+ ret = asprintf(&exterr1, " Err%d: Password reuse not permitted.", bint);
+ break;
+ default:
+ ret = asprintf(&exterr1, " Err%d: Unknown Errorcode.", bint);
+ break;
+ }
+ if (ret == -1) {
+ syslog(LOG_ERR, "OOM while creating error message ...");
+ exterr1 = NULL;
+ }
+ }
+ }
+ }
+
+ if (ret == LDAP_SUCCESS) {
+ kpwd_err = KRB5_KPASSWD_SUCCESS;
+ exterr0 = "Password change succeeded";
+ } else {
+ exterr0 = "Password change failed";
+ }
+ ret = asprintf(errstr, "%s%s%s", exterr0, exterr1?exterr1:"", exterr2?exterr2:"");
+ if (ret == -1) {
+ syslog(LOG_ERR, "OOM while creating error message ...");
+ *errstr = NULL;
+ }
+
+done:
+ if (ctrl) ber_free(ctrl, 1);
+ if (sctrl) ber_free(sctrl, 1);
+ if (srvctrl) ldap_controls_free(srvctrl);
+ if (res) ldap_msgfree(res);
+ if (control) ber_bvfree(control);
+ free(exterr1);
+ free(exterr2);
+ free(userdn);
+ if (ld) ldap_unbind_ext(ld, NULL, NULL);
+ if (tmp_file) {
+ unlink(tmp_file);
+ free(tmp_file);
+ }
+ return kpwd_err;
+}
+
+void handle_krb_packets(uint8_t *buf, ssize_t buflen,
+ struct socklist *sd,
+ struct sockaddr_storage *from,
+ uint8_t **repbuf, ssize_t *replen)
+{
+ krb5_auth_context auth_context;
+ krb5_context context;
+ krb5_keytab keytab;
+ krb5_principal kprincpw;
+ krb5_ticket *ticket;
+ krb5_address lkaddr, rkaddr;
+ krb5_data kreq, krep, kenc, kdec;
+ krb5_replay_data replay;
+ krb5_error krb5err;
+ int krberr;
+ size_t reqlen;
+ size_t verno;
+ char *client_name, *realm_name;
+ char *result_string;
+ int result_err;
+ uint8_t *reply;
+ ssize_t replylen;
+
+ *replen = 0;
+
+ result_string = NULL;
+ auth_context = NULL;
+ krep.length = 0;
+ krep.data = NULL;
+ kdec.length = 0;
+ kdec.data = NULL;
+ kprincpw = NULL;
+ context = NULL;
+ ticket = NULL;
+
+ switch(((struct sockaddr *)from)->sa_family) {
+ case AF_INET:
+ lkaddr.addrtype = ADDRTYPE_INET;
+ lkaddr.length = sizeof(((struct sockaddr_in *)&sd->dest_addr)->sin_addr);
+ lkaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)&sd->dest_addr)->sin_addr);
+
+ rkaddr.addrtype = ADDRTYPE_INET;
+ rkaddr.length = sizeof(((struct sockaddr_in *)from)->sin_addr);
+ rkaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)from)->sin_addr);
+ break;
+ case AF_INET6:
+ if (IN6_IS_ADDR_V4MAPPED (&((struct sockaddr_in6 *)from)->sin6_addr)) {
+ lkaddr.addrtype = ADDRTYPE_INET;
+ lkaddr.length = 4;
+ lkaddr.contents = 12 + (krb5_octet *) &(((struct sockaddr_in6 *)&sd->dest_addr)->sin6_addr);
+
+ rkaddr.addrtype = ADDRTYPE_INET;
+ rkaddr.length = 4;
+ rkaddr.contents = 12 + (krb5_octet *) &(((struct sockaddr_in6 *)from)->sin6_addr);
+ } else {
+ lkaddr.addrtype = ADDRTYPE_INET6;
+ lkaddr.length = sizeof(((struct sockaddr_in6 *)&sd->dest_addr)->sin6_addr);
+ lkaddr.contents = (krb5_octet *) &(((struct sockaddr_in6 *)&sd->dest_addr)->sin6_addr);
+
+ rkaddr.addrtype = ADDRTYPE_INET6;
+ rkaddr.length = sizeof(((struct sockaddr_in6 *)from)->sin6_addr);
+ rkaddr.contents = (krb5_octet *) &(((struct sockaddr_in6 *)from)->sin6_addr);
+ }
+ break;
+ default:
+ result_string = strdup("Invalid remopte IP address");
+ result_err = KRB5_KPASSWD_MALFORMED;
+ syslog(LOG_ERR, "%s", result_string);
+ goto done;
+ }
+
+ if (buflen < 4) {
+ result_string = strdup("Request truncated");
+ result_err = KRB5_KPASSWD_MALFORMED;
+ syslog(LOG_ERR, "%s", result_string);
+ goto done;
+ }
+
+ reqlen = (buf[0] << 8) + buf[1];
+
+ if (reqlen != buflen) {
+ result_string = strdup("Unmatching request length");
+ result_err = KRB5_KPASSWD_MALFORMED;
+ syslog(LOG_ERR, "%s", result_string);
+ goto done;
+ }
+
+ verno = (buf[2] << 8) + buf[3];
+
+ if (verno != 1) {
+ result_string = strdup("Unsupported version");
+ result_err = KRB5_KPASSWD_BAD_VERSION;
+ syslog(LOG_ERR, "%s", result_string);
+ goto done;
+ }
+
+ kreq.length = (buf[4] << 8) + buf[5];
+ if (kreq.length > (buflen - 6)) {
+ result_string = strdup("Request truncated");
+ result_err = KRB5_KPASSWD_MALFORMED;
+ syslog(LOG_ERR, "%s", result_string);
+ goto done;
+ }
+ kreq.data = (char *)&buf[6];
+
+ krberr = krb5_init_context(&context);
+ if (krberr) {
+ result_string = strdup("Failed to init kerberos context");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s", result_string);
+ goto done;
+ }
+
+ krberr = krb5_get_default_realm(context, &realm_name);
+ if (krberr) {
+ result_string = strdup("Failed to get default realm name");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s", result_string);
+ goto done;
+ }
+
+ krberr = krb5_auth_con_init(context, &auth_context);
+ if (krberr) {
+ result_string = strdup("Unable to init auth context");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto done;
+ }
+
+ krberr = krb5_auth_con_setflags(context, auth_context,
+ KRB5_AUTH_CONTEXT_DO_SEQUENCE);
+ if (krberr) {
+ result_string = strdup("Unable to init auth context");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto done;
+ }
+
+ krberr = krb5_build_principal(context, &kprincpw,
+ strlen(realm_name), realm_name,
+ "kadmin", "changepw", NULL);
+ if (krberr) {
+ result_string = strdup("Unable to build principal");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto done;
+ }
+
+ krberr = krb5_kt_resolve(context, keytab_name, &keytab);
+ if (krberr) {
+ result_string = strdup("Unable to retrieve keytab");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto done;
+ }
+
+ krberr = krb5_rd_req(context, &auth_context, &kreq,
+ kprincpw, keytab, NULL, &ticket);
+ if (krberr) {
+ result_string = strdup("Unable to read request");
+ result_err = KRB5_KPASSWD_AUTHERROR;
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto done;
+ }
+
+ /* build the AP Reply before actually changing the password
+ * this minimize the risk of a fatal error occurring _after_
+ * the password have been successfully changed */
+ krberr = krb5_mk_rep(context, auth_context, &krep);
+ if (krberr) {
+ result_string = strdup("Failed to to build reply");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto done;
+ }
+
+ /* verify that this is an AS_REQ ticket */
+ if (!(ticket->enc_part2->flags & TKT_FLG_INITIAL)) {
+ result_string = strdup("Ticket must be derived from a password");
+ result_err = KRB5_KPASSWD_AUTHERROR;
+ syslog(LOG_ERR, "%s", result_string);
+ goto kpreply;
+ }
+
+ krberr = krb5_unparse_name(context, ticket->enc_part2->client,
+ &client_name);
+ if (krberr) {
+ result_string = strdup("Unable to parse client name");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s", result_string);
+ goto kpreply;
+ }
+
+ krberr = krb5_auth_con_setaddrs(context, auth_context, NULL, &rkaddr);
+ if (krberr) {
+ result_string = strdup("Failed to set client address");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto kpreply;
+ }
+
+ /* decrypt the new password */
+ kenc.length = reqlen - kreq.length - 6;
+ kenc.data = kreq.data + kreq.length;
+
+ /* rd_priv needs the remote address while mk_priv (used later)
+ * requires the local address (from kadmin code) */
+ krberr = krb5_rd_priv(context, auth_context, &kenc, &kdec, &replay);
+ if (krberr) {
+ result_string = strdup("Failed to decrypt password");
+ result_err = KRB5_KPASSWD_HARDERROR;
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto kpreply;
+ }
+
+ if (debug > 100) {
+ syslog(LOG_ERR, "Client %s trying to set password [%*s]",
+ client_name, kdec.length, kdec.data);
+ }
+
+ /* Actually try to change the password */
+ result_err = ldap_pwd_change(client_name, realm_name, kdec, &result_string);
+ if (result_string == NULL) {
+ result_string = strdup("Server Error while performing LDAP password change");
+ }
+ syslog(LOG_ERR, "%s", result_string);
+
+ /* make sure password is cleared off before we free the memory */
+ memset(kdec.data, 0, kdec.length);
+ free(kdec.data);
+ kdec.length = 0;
+
+kpreply:
+
+ /* set-up the the clear text reply */
+ kdec.length = 2 + strlen(result_string);
+ kdec.data = malloc(kdec.length);
+ if (!kdec.data) {
+ syslog(LOG_ERR, "Out of memory!");
+ kdec.length = 0;
+ goto done;
+ }
+
+ kdec.data[0] = (result_err >> 8) & 0xff;
+ kdec.data[1] = result_err & 0xff;
+ memcpy(&kdec.data[2], result_string, strlen(result_string));
+
+ krberr = krb5_auth_con_setaddrs(context, auth_context, &lkaddr, NULL);
+ if (krberr) {
+ result_string = strdup("Failed to set local address");
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto done;
+ }
+
+ krberr = krb5_mk_priv(context, auth_context, &kdec, &kenc, &replay);
+ if (krberr) {
+ result_string = strdup("Failed to encrypt reply message");
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ /* encryption was unsuccessful, let's return a krb error */
+
+ /* the ap data is no more useful */
+ free(krep.data);
+ krep.length = 0;
+
+ /* build a krberror encrypted paylod */
+ krb5err.error = KRB5_CHPW_FAIL;
+ krb5err.server = kprincpw;
+ krb5err.client = NULL;
+ krb5err.ctime = 0;
+ krb5err.cusec = 0;
+ krb5err.susec = 0;
+ krberr = krb5_timeofday(context, &krb5err.stime);
+ if (krberr) {
+ result_string = strdup("Failed to set time of day");
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto done;
+ }
+
+ krb5err.text.length = 0;
+ krb5err.e_data = kdec;
+ krberr = krb5_mk_error(context, &krb5err, &kenc);
+ if (krberr) {
+ result_string = strdup("Failed to build error message");
+ syslog(LOG_ERR, "%s: %s", result_string,
+ krb5_get_error_message(context, krberr));
+ goto done;
+ }
+ }
+
+ replylen = 6 + krep.length + kenc.length;
+ reply = malloc(replylen);
+ if (!reply) {
+ syslog(LOG_ERR, "Out of memory!");
+ goto done;
+ }
+ *repbuf = reply;
+
+ reply[0] = (replylen >> 8) & 0xff;
+ reply[1] = replylen & 0xff;
+ reply[2] = 0x00;
+ reply[3] = 0x01;
+ reply[4] = (krep.length >> 8) & 0xff;
+ reply[5] = krep.length & 0xff;
+
+ if (krep.length) {
+ memcpy(&reply[6], krep.data, krep.length);
+ }
+ memcpy(&reply[6 + krep.length], kenc.data, kenc.length);
+
+ *replen = replylen;
+
+done:
+ free(result_string);
+ if (auth_context) krb5_auth_con_free(context, auth_context);
+ if (kprincpw) krb5_free_principal(context, kprincpw);
+ if (krep.length) free(krep.data);
+ if (ticket) krb5_free_ticket(context, ticket);
+ if (kdec.length) free(kdec.data);
+ if (context) krb5_free_context(context);
+}
+
+pid_t handle_conn(struct socklist *sd)
+{
+ int mfd, tcp;
+ pid_t pid;
+ char addrto6[INET6_ADDRSTRLEN+1];
+ char address[INET6_ADDRSTRLEN+1];
+ uint8_t request[1500];
+ ssize_t reqlen;
+ uint8_t *reply;
+ ssize_t replen;
+ struct sockaddr_storage from;
+ socklen_t fromlen;
+ ssize_t sendret;
+ int ret;
+
+ fromlen = sizeof(from);
+ mfd = 0;
+ tcp = 0;
+ reqlen = 0;
+
+ /* receive request */
+ if (sd->socktype == SOCK_STREAM) {
+ tcp = 1;
+ mfd = accept(sd->fd, (struct sockaddr *)&from, &fromlen);
+ if (mfd == -1) {
+ syslog(LOG_ERR, "Accept failed with error (%d) %s",
+ errno, strerror(errno));
+ return -1;
+ }
+ } else {
+ /* read first to empty the buffer on udp connections */
+ reqlen = recvfrom(sd->fd, request, sizeof(request), 0,
+ (struct sockaddr *)&from, &fromlen);
+ if (reqlen <= 0) {
+ syslog(LOG_ERR, "Error receiving request (%d) %s",
+ errno, strerror(errno));
+ return -1;
+ }
+
+ }
+
+ ret = getnameinfo((struct sockaddr *)&from, fromlen,
+ addrto6, INET6_ADDRSTRLEN+1,
+ NULL, 0, NI_NUMERICHOST);
+ if (ret) {
+ syslog(LOG_ERR, "Error retrieving host address\n");
+ return -1;
+ }
+
+ if (debug > 0) {
+ syslog(LOG_ERR, "Connection from %s", addrto6);
+ }
+
+ if (strchr(addrto6, ':') == NULL) {
+ char *prefix6 = "::ffff:";
+ /* this is an IPv4 formatted addr
+ * convert to IPv6 mapped addr */
+ memcpy(address, prefix6, 7);
+ memcpy(&address[7], addrto6, INET6_ADDRSTRLEN-7);
+ } else {
+ /* regular IPv6 address, copy as is */
+ memcpy(address, addrto6, INET6_ADDRSTRLEN);
+ }
+ /* make sure we have termination */
+ address[INET6_ADDRSTRLEN] = '\0';
+
+ /* Check blacklist for requests from the same IP until operations
+ * are finished on the active client.
+ * the password change may be slow and pam_krb5 sends up to 3 UDP
+ * requests waiting 1 sec. each time.
+ * We do not want to start 3 password changes at the same time */
+
+ if (check_blacklist(address)) {
+ if (debug > 0) {
+ syslog(LOG_ERR, "[%s] blacklisted", address);
+ }
+ if (tcp) close(mfd);
+ return 0;
+ }
+
+ /* now read data if it was a TCP connection */
+ if (tcp) {
+ reqlen = recvfrom(mfd, request, sizeof(request), 0,
+ (struct sockaddr *)&from, &fromlen);
+ if (reqlen <= 0) {
+ syslog(LOG_ERR, "Error receiving request (%d) %s",
+ errno, strerror(errno));
+ close(mfd);
+ return -1;
+ }
+ }
+#if 1
+ /* handle kerberos and ldap operations in childrens */
+ pid = fork();
+ if (pid == -1) {
+ syslog(LOG_ERR, "Fork failed with error (%d) %s",
+ errno, strerror(errno));
+ if (tcp) close(mfd);
+ return 0;
+ }
+ if (pid != 0) { /* parent */
+ if (tcp) close(mfd);
+ add_blacklist(pid, address);
+ return pid;
+ }
+#endif
+
+ /* children */
+ if (debug > 0) syslog(LOG_ERR, "Servicing %s", address);
+
+ /* TCP packets prepend the lenght as a 32bit network order field,
+ * this information seem to be just redundant, so let's simply
+ * skip it */
+ if (tcp) {
+ handle_krb_packets(request+4, reqlen-4, sd, &from, &reply, &replen);
+ } else {
+ handle_krb_packets(request, reqlen, sd, &from, &reply, &replen);
+ }
+
+ if (replen) { /* we have something to reply */
+ if (tcp) {
+ sendret = sendto(mfd, reply, replen, 0, NULL, 0);
+ } else {
+ sendret = sendto(sd->fd, reply, replen, 0, (struct sockaddr *)&from, fromlen);
+ }
+ if (sendret == -1) {
+ syslog(LOG_ERR, "Error sending reply (%d)", errno);
+ }
+ }
+ if (tcp) close(mfd);
+ exit(0);
+}
+
+static int create_socket(struct addrinfo *ai, struct socklist **_sds,
+ struct pollfd **_pfds, int *_nfds)
+{
+ struct socklist *csd, *tsd;
+ struct pollfd *pfds;
+ int nfds;
+ int ret;
+ int tru = 1;
+
+ pfds = *_pfds;
+ nfds = *_nfds;
+
+ csd = calloc(1, sizeof(struct socklist));
+ if (csd == NULL) {
+ syslog(LOG_ERR, "Out of memory, can't create socklist\n");
+ return 1;
+ }
+ csd->socktype = ai->ai_socktype;
+ csd->dest_addr_len = ai->ai_addrlen;
+ memcpy(&csd->dest_addr, ai->ai_addr, ai->ai_addrlen);
+
+ csd->fd = socket(csd->dest_addr.ss_family, csd->socktype, 0);
+ if (csd->fd == -1) {
+ syslog(LOG_ERR, "Unable to create socket (%s)",
+ strerror(errno));
+ goto errout;
+ }
+ ret = setsockopt(csd->fd, SOL_SOCKET, SO_REUSEADDR,
+ (void *)&tru, sizeof(tru));
+
+ ret = bind(csd->fd, (struct sockaddr *)&csd->dest_addr, csd->dest_addr_len);
+ if (ret) {
+ if (errno != EADDRINUSE) {
+ syslog(LOG_ERR, "Unable to bind to socket");
+ close(csd->fd);
+ goto errout;
+ }
+ /* if EADDRINUSE it means we are on a machine
+ * with a dual ipv4/ipv6 stack that does not
+ * allow to bind on both at the same time as the
+ * ipv6 bind already allows connections on ipv4
+ * Just ignore */
+ close(csd->fd);
+ free(csd);
+ return 0;
+ }
+
+ if (csd->socktype == SOCK_STREAM) {
+ ret = listen(csd->fd, SOMAXCONN);
+ if (ret) {
+ syslog(LOG_ERR, "Unable to listen to TCP socket (%s)",
+ strerror(errno));
+ close(csd->fd);
+ goto errout;
+ }
+ }
+
+ pfds = realloc(pfds, sizeof(struct pollfd) * (nfds +1));
+ if (pfds == NULL) {
+ syslog(LOG_ERR, "Out of memory, can't alloc pollfd array\n");
+ close(csd->fd);
+ goto errout;
+ }
+ pfds[nfds].events = POLLIN;
+ pfds[nfds].fd = csd->fd;
+ nfds++;
+
+ if (*_sds) {
+ for (tsd = *_sds; tsd->next; tsd = tsd->next) /* skip */ ;
+ tsd->next = csd;
+ } else {
+ *_sds = csd;
+ }
+
+ *_pfds = pfds;
+ *_nfds = nfds;
+
+ return 0;
+
+errout:
+ free(csd);
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+ pid_t pid;
+ struct ifaddrs *ifa, *tifa;
+ struct addrinfo *ai, *tai;
+ struct addrinfo hints;
+ char host[NI_MAXHOST];
+ struct socklist *sds, *csd;
+ struct pollfd *pfds;
+ int nfds;
+ int ret;
+ char *env;
+
+ /* log to syslog */
+ openlog("kpasswd", LOG_PID, LOG_DAEMON);
+
+ /* do not keep any fs busy */
+ ret = chdir("/");
+ if (ret == -1) {
+ syslog(LOG_ERR, "Unable to change dir to '/'");
+ exit(-1);
+ }
+
+ /* daemonize */
+ pid = fork();
+ if (pid == -1) {
+ syslog(LOG_ERR, "Error fork() failed!");
+ exit(-1);
+ }
+ if (pid != 0) { /* parent */
+ exit(0);
+ }
+
+ /* new session */
+ setsid();
+
+ /* close std* descriptors */
+ close(0);
+ close(1);
+ close(2);
+
+ /* fork again to make sure we completely detach from parent process */
+ pid = fork();
+ if (pid == -1) {
+ syslog(LOG_ERR, "Error fork() failed!");
+ exit(-1);
+ }
+ if (pid != 0) { /* parent */
+ exit(0);
+ }
+
+ /* source env vars */
+ env = getenv("KRB5_KTNAME");
+ if (!env) {
+ env = DEFAULT_KEYTAB;
+ }
+ keytab_name = strdup(env);
+ if (!keytab_name) {
+ syslog(LOG_ERR, "Out of memory!");
+ }
+
+ env = getenv("IPA_KPASSWD_DEBUG");
+ if (env) {
+ debug = strtol(env, NULL, 0);
+ }
+
+ ret = getifaddrs(&ifa);
+ if (ret) {
+ syslog(LOG_ERR, "getifaddrs failed: %s", gai_strerror(ret));
+ exit(1);
+ }
+
+ /* Write out the pid file after the sigterm handler */
+ const char *pid_file = "/var/run/ipa_kpasswd.pid";
+ FILE *f = fopen(pid_file, "w");
+ int fail = 1;
+ if (f) {
+ int n_bytes = fprintf(f, "%ld\n", (long) getpid());
+ if (fclose(f) == 0 && 0 < n_bytes)
+ fail = 0;
+ }
+ if (fail) {
+ syslog(LOG_ERR, "Couldn't create pid file %s: %s",
+ pid_file, strerror(errno));
+ exit(1);
+ }
+
+ nfds = 0;
+ pfds = NULL;
+ sds = NULL;
+
+ for (tifa = ifa; tifa; tifa = tifa->ifa_next) {
+
+ if (NULL == tifa->ifa_addr)
+ /* uhmm no address ?? skip it */
+ continue;
+
+ if (tifa->ifa_addr->sa_family != AF_INET &&
+ tifa->ifa_addr->sa_family != AF_INET6) {
+ /* not interesting for us */
+ continue;
+ }
+
+ ret = getnameinfo(tifa->ifa_addr, sizeof(struct sockaddr_storage),
+ host, sizeof(host), NULL, 0, NI_NUMERICHOST);
+ if (ret) {
+ syslog(LOG_ERR, "Error converting address (%s)",
+ gai_strerror(ret));
+ continue;
+ } else {
+ syslog(LOG_INFO, "Setting up socket for [%s]", host);
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = AF_UNSPEC;
+
+ /* this should return 2 entries, one for UDP and one for TCP */
+ ret = getaddrinfo(host, "kpasswd", &hints, &ai);
+ if (ret) {
+ syslog(LOG_ERR, "Error getting address info (%s) for [%s]",
+ gai_strerror(ret), host);
+ continue;
+ }
+
+ for (tai = ai; tai; tai = tai->ai_next) {
+ char *socktype = (tai->ai_socktype==SOCK_STREAM)?"TCP":"UDP";
+ ret = create_socket(tai, &sds, &pfds, &nfds);
+ if (ret) {
+ syslog(LOG_ERR,
+ "Failed to set up %s socket for [%s]",
+ socktype, host);
+ }
+ }
+ }
+
+ if (nfds == 0) {
+ syslog(LOG_ERR, "Failed to setup any socket. Aborting");
+ exit(1);
+ }
+
+ /* now that sockets are set up, enter the poll loop */
+
+ while (1) {
+ int cstatus, cid, i;
+
+ ret = poll(pfds, nfds, 3000);
+
+ switch(ret) {
+ case 0:
+ break;
+ case -1:
+ if (errno != EINTR) {
+ syslog(LOG_ERR,
+ "Unexpected error in poll (%d) %s",
+ errno, strerror(errno));
+ exit(5);
+ }
+ break;
+ default:
+ for (i = 0; i < nfds; i++) {
+ if (pfds[i].revents & POLLIN) {
+ for (csd = sds; csd; csd = csd->next) {
+ if (csd->fd == pfds[i].fd) {
+ handle_conn(csd);
+ }
+ }
+ }
+ }
+ }
+
+ /* check for children exiting */
+ cid = waitpid(-1, &cstatus, WNOHANG);
+ if (cid != -1 && cid != 0) {
+ if (debug > 0)
+ syslog(LOG_ERR, "pid %d completed operations!\n", cid);
+ remove_blacklist(cid);
+ }
+ }
+}
diff --git a/daemons/ipa-kpasswd/ipa_kpasswd.init b/daemons/ipa-kpasswd/ipa_kpasswd.init
new file mode 100644
index 000000000..d7244bed6
--- /dev/null
+++ b/daemons/ipa-kpasswd/ipa_kpasswd.init
@@ -0,0 +1,83 @@
+#!/bin/sh
+#
+# ipa_kpasswd This starts and stops ipa_kpasswd
+#
+# chkconfig: - 36 64
+# description: ipa_kpasswd IPA Kpasswd daemon
+# processname: /usr/sbin/ipa_kpasswd
+# configdir: /etc/sysconfig/ipa-kpasswd
+#
+
+# Source function library.
+if [ -f /etc/rc.d/init.d/functions ] ; then
+. /etc/rc.d/init.d/functions
+fi
+# Source networking configuration.
+if [ -f /etc/sysconfig/network ] ; then
+. /etc/sysconfig/network
+fi
+
+# Check that networking is up.
+if [ "${NETWORKING}" = "no" ]
+then
+ echo "Networking is down"
+ exit 0
+fi
+
+# Source networking configuration.
+if [ -f /etc/sysconfig/ipa-kpasswd ] ; then
+. /etc/sysconfig/ipa-kpasswd
+fi
+
+NAME="ipa_kpasswd"
+PROG="/usr/sbin/ipa_kpasswd"
+
+start() {
+ echo -n $"Starting $NAME: "
+ daemon $NAME
+ RETVAL=$?
+ echo
+ [ $RETVAL -eq 0 ] && touch /var/lock/subsys/ipa_kpasswd || \
+ RETVAL=1
+ return $RETVAL
+}
+
+stop() {
+ echo -n $"Shutting down $NAME: "
+ killproc $NAME
+ RETVAL=$?
+ echo
+ [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/ipa_kpasswd
+ return $RETVAL
+}
+
+restart() {
+ stop
+ start
+}
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ status)
+ status $PROG
+ ;;
+ restart)
+ restart
+ ;;
+ condrestart)
+ [ -f /var/lock/subsys/ipa_kpasswd ] && restart || :
+ ;;
+ reload)
+ exit 3
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart|condrestart}"
+ exit 2
+esac
+
+exit $?
diff --git a/daemons/ipa-slapi-plugins/Makefile.am b/daemons/ipa-slapi-plugins/Makefile.am
new file mode 100644
index 000000000..f316371c3
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/Makefile.am
@@ -0,0 +1,16 @@
+NULL =
+
+SUBDIRS = \
+ ipa-pwd-extop \
+ ipa-memberof \
+ dna \
+ ipa-winsync \
+ $(NULL)
+
+EXTRA_DIST = \
+ README \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
diff --git a/daemons/ipa-slapi-plugins/README b/daemons/ipa-slapi-plugins/README
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/README
diff --git a/daemons/ipa-slapi-plugins/dna/Makefile.am b/daemons/ipa-slapi-plugins/dna/Makefile.am
new file mode 100644
index 000000000..4a54b8d5d
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/dna/Makefile.am
@@ -0,0 +1,42 @@
+NULL =
+
+INCLUDES = \
+ -I. \
+ -I$(srcdir) \
+ -DPREFIX=\""$(prefix)"\" \
+ -DBINDIR=\""$(bindir)"\" \
+ -DLIBDIR=\""$(libdir)"\" \
+ -DLIBEXECDIR=\""$(libexecdir)"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ $(MOZLDAP_CFLAGS) \
+ $(KRB5_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(NULL)
+
+plugindir = $(libdir)/dirsrv/plugins
+plugin_LTLIBRARIES = \
+ libipa-dna-plugin.la \
+ $(NULL)
+
+libipa_dna_plugin_la_SOURCES = \
+ dna.c \
+ $(NULL)
+
+libipa_dna_plugin_la_LDFLAGS = -avoid-version
+
+libipa_dna_plugin_la_LIBADD = \
+ $(MOZLDAP_LIBS) \
+ $(NULL)
+
+appdir = $(IPA_DATA_DIR)
+app_DATA = \
+ dna-conf.ldif \
+ $(NULL)
+
+EXTRA_DIST = \
+ $(app_DATA) \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
diff --git a/daemons/ipa-slapi-plugins/dna/dna-conf.ldif b/daemons/ipa-slapi-plugins/dna/dna-conf.ldif
new file mode 100644
index 000000000..02532b4e4
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/dna/dna-conf.ldif
@@ -0,0 +1,14 @@
+dn: cn=ipa-dna,cn=plugins,cn=config
+changetype: add
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: ipa-dna
+nsslapd-pluginpath: libipa-dna-plugin
+nsslapd-plugininitfunc: ipa_dna_init
+nsslapd-plugintype: preoperation
+nsslapd-pluginenabled: on
+nsslapd-pluginid: ipa-dna
+nsslapd-pluginversion: 1.0
+nsslapd-pluginvendor: Red Hat
+nsslapd-plugindescription: IPA Distributed numeric assignment plugin
diff --git a/daemons/ipa-slapi-plugins/dna/dna.c b/daemons/ipa-slapi-plugins/dna/dna.c
new file mode 100644
index 000000000..cb6a0629c
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/dna/dna.c
@@ -0,0 +1,1462 @@
+/** 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.
+ *
+ *
+ * Author: Pete Rowley
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+/**
+ * Distributed Numeric Assignment plug-in
+ */
+
+#include <dirsrv/slapi-plugin.h>
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+/*#include "portable.h"*/
+#include "nspr.h"
+/*#include "slapi-private.h"*/
+/*#include "dirlite_strings.h"*/
+/*#include "dirver.h"*/
+
+#include "prclist.h"
+#include "ldif.h"
+
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+#define DNA_PLUGIN_SUBSYSTEM "ipa-dna-plugin"
+#define DNA_PLUGIN_VERSION 0x00020000
+
+/* temporary */
+#define DNA_DN "cn=ipa-dna,cn=plugins,cn=config"
+
+#define DNA_SUCCESS 0
+#define DNA_FAILURE -1
+
+/**
+ * DNA config types
+ */
+#define DNA_TYPE "dnaType"
+#define DNA_PREFIX "dnaPrefix"
+#define DNA_NEXTVAL "dnaNextValue"
+#define DNA_INTERVAL "dnaInterval"
+#define DNA_GENERATE "dnaMagicRegen"
+#define DNA_FILTER "dnaFilter"
+#define DNA_SCOPE "dnaScope"
+
+/* since v2 */
+#define DNA_MAXVAL "dnaMaxValue"
+#define DNA_SHARED_CFG_DN "dnaSharedCfgDN"
+
+/* Shared Config */
+#define DNA_GLOBAL_RANGE "dnaGlobalRange"
+#define DNA_RANGE "dnaRange"
+#define DNA_MAX_RANGE_SIZE "dnaMaxRangeSize"
+#define DNA_CHUNK_SIZE "dnaChunkSize"
+
+
+
+#define FEATURE_DESC "IPA Distributed Numeric Assignment"
+#define PLUGIN_DESC "IPA Distributed Numeric Assignment plugin"
+#define PLUGIN_DESC_INT_PREOP PLUGIN_DESC " preop internal"
+#define PLUGIN_DESC_POSTOP PLUGIN_DESC " postop"
+#define PLUGIN_DESC_INT_POSTOP PLUGIN_DESC " postop internal"
+
+static Slapi_PluginDesc pdesc = { FEATURE_DESC,
+ "FreeIPA project", "FreeIPA/1.0",
+ PLUGIN_DESC
+};
+
+
+/**
+ * linked list of config entries
+ */
+
+struct configEntry {
+ PRCList list;
+ char *dn;
+ char *type;
+ char *prefix;
+ PRUint64 nextval;
+ PRUint64 interval;
+ PRUint64 maxval;
+ char *filter;
+ struct slapi_filter *slapi_filter;
+ char *generate;
+ char *scope;
+};
+
+static PRCList *dna_global_config = NULL;
+static PRRWLock *g_dna_cache_lock;
+
+static void *_PluginID = NULL;
+static char *_PluginDN = NULL;
+
+static int g_plugin_started = 0;
+
+
+/*
+ * new value lock
+ */
+static Slapi_Mutex *g_new_value_lock;
+
+/**
+ *
+ * DNA plug-in management functions
+ *
+ */
+int ipa_dna_init(Slapi_PBlock * pb);
+static int dna_start(Slapi_PBlock * pb);
+static int dna_close(Slapi_PBlock * pb);
+static int dna_internal_preop_init(Slapi_PBlock *pb);
+static int dna_postop_init(Slapi_PBlock * pb);
+
+/**
+ *
+ * Local operation functions
+ *
+ */
+static int loadPluginConfig();
+static int parseConfigEntry(Slapi_Entry * e);
+static void deleteConfig();
+static void freeConfigEntry(struct configEntry ** entry);
+
+/**
+ *
+ * helpers
+ *
+ */
+static char *dna_get_dn(Slapi_PBlock * pb);
+static int dna_dn_is_config(char *dn);
+static int dna_get_next_value(struct configEntry * config_entry,
+ char **next_value_ret);
+
+/**
+ *
+ * the ops (where the real work is done)
+ *
+ */
+static int dna_config_check_post_op(Slapi_PBlock * pb);
+static int dna_pre_op(Slapi_PBlock * pb, int modtype);
+static int dna_mod_pre_op(Slapi_PBlock * pb);
+static int dna_add_pre_op(Slapi_PBlock * pb);
+
+/**
+ * debug functions - global, for the debugger
+ */
+void dnaDumpConfig();
+void dnaDumpConfigEntry(struct configEntry *);
+
+/**
+ * set the debug level
+ */
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+/**
+ *
+ * Deal with cache locking
+ *
+ */
+void dna_read_lock()
+{
+ PR_RWLock_Rlock(g_dna_cache_lock);
+}
+
+void dna_write_lock()
+{
+ PR_RWLock_Wlock(g_dna_cache_lock);
+}
+
+void dna_unlock()
+{
+ PR_RWLock_Unlock(g_dna_cache_lock);
+}
+
+/**
+ *
+ * Get the dna plug-in version
+ *
+ */
+int dna_version()
+{
+ return DNA_PLUGIN_VERSION;
+}
+
+/**
+ * Plugin identity mgmt
+ */
+void setPluginID(void *pluginID)
+{
+ _PluginID = pluginID;
+}
+
+void *getPluginID()
+{
+ return _PluginID;
+}
+
+void setPluginDN(char *pluginDN)
+{
+ _PluginDN = pluginDN;
+}
+
+char *getPluginDN()
+{
+ return _PluginDN;
+}
+
+/*
+ dna_init
+ -------------
+ adds our callbacks to the list
+*/
+int ipa_dna_init(Slapi_PBlock * pb)
+{
+ int status = DNA_SUCCESS;
+ char *plugin_identity = NULL;
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> ipa_dna_init\n");
+
+ /**
+ * Store the plugin identity for later use.
+ * Used for internal operations
+ */
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
+ PR_ASSERT(plugin_identity);
+ setPluginID(plugin_identity);
+
+ if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) dna_start) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) dna_close) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *) &pdesc) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN,
+ (void *) dna_mod_pre_op) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN,
+ (void *) dna_add_pre_op) != 0 ||
+ /* internal preoperation */
+ slapi_register_plugin("internalpreoperation", /* op type */
+ 1, /* Enabled */
+ "dna_internal_preop_init", /* this function desc */
+ dna_internal_preop_init, /* init func */
+ PLUGIN_DESC_INT_PREOP, /* plugin desc */
+ NULL, /* ? */
+ plugin_identity /* access control */
+ ) ||
+ /* the config change checking post op */
+ slapi_register_plugin("postoperation", /* op type */
+ 1, /* Enabled */
+ "dna_postop_init", /* this function desc */
+ dna_postop_init, /* init func for post op */
+ PLUGIN_DESC_POSTOP, /* plugin desc */
+ NULL, /* ? */
+ plugin_identity /* access control */
+ )
+ ) {
+ slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "ipa_dna_init: failed to register plugin\n");
+ status = DNA_FAILURE;
+ }
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- ipa_dna_init\n");
+ return status;
+}
+
+
+static int
+dna_internal_preop_init(Slapi_PBlock *pb)
+{
+ int status = DNA_SUCCESS;
+
+ if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *) &pdesc) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN,
+ (void *) dna_mod_pre_op) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN,
+ (void *) dna_add_pre_op) != 0) {
+ status = DNA_FAILURE;
+ }
+
+ return status;
+}
+
+
+static int dna_postop_init(Slapi_PBlock * pb)
+{
+ int status = DNA_SUCCESS;
+
+ if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *) &pdesc) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN,
+ (void *) dna_config_check_post_op) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN,
+ (void *) dna_config_check_post_op) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN,
+ (void *) dna_config_check_post_op) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN,
+ (void *) dna_config_check_post_op) != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_postop_init: failed to register plugin\n");
+ status = DNA_FAILURE;
+ }
+
+ return status;
+}
+
+/*
+ dna_start
+ --------------
+ Kicks off the config cache.
+ It is called after dna_init.
+*/
+static int dna_start(Slapi_PBlock * pb)
+{
+ char *plugindn = NULL;
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> dna_start\n");
+
+ /* Check if we're already started */
+ if (g_plugin_started) {
+ goto done;
+ }
+
+ g_dna_cache_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "dna");
+ g_new_value_lock = slapi_new_mutex();
+
+ if (!g_dna_cache_lock || !g_new_value_lock) {
+ slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_start: lock creation failed\n");
+
+ return DNA_FAILURE;
+ }
+
+ /**
+ * Get the plug-in target dn from the system
+ * and store it for future use. This should avoid
+ * hardcoding of DN's in the code.
+ */
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &plugindn);
+ if (NULL == plugindn || 0 == strlen(plugindn)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM,
+ "dna_start: had to use hard coded config dn\n");
+ plugindn = DNA_DN;
+ } else {
+ slapi_log_error(SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM,
+ "dna_start: config at %s\n", plugindn);
+
+ }
+
+ setPluginDN(plugindn);
+
+ /**
+ * Load the config for our plug-in
+ */
+ dna_global_config = (PRCList *)
+ slapi_ch_calloc(1, sizeof(struct configEntry));
+ PR_INIT_CLIST(dna_global_config);
+
+ if (loadPluginConfig() != DNA_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_start: unable to load plug-in configuration\n");
+ return DNA_FAILURE;
+ }
+
+ g_plugin_started = 1;
+ slapi_log_error(SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM,
+ "dna: ready for service\n");
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- dna_start\n");
+
+done:
+ return DNA_SUCCESS;
+}
+
+/*
+ dna_close
+ --------------
+ closes down the cache
+*/
+static int dna_close(Slapi_PBlock * pb)
+{
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> dna_close\n");
+
+ deleteConfig();
+
+ slapi_ch_free((void **)&dna_global_config);
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- dna_close\n");
+
+ return DNA_SUCCESS;
+}
+
+/*
+ * config looks like this
+ * - cn=myplugin
+ * --- cn=posix
+ * ------ cn=accounts
+ * ------ cn=groups
+ * --- cn=samba
+ * --- cn=etc
+ * ------ cn=etc etc
+ */
+static int loadPluginConfig()
+{
+ int status = DNA_SUCCESS;
+ int result;
+ int i;
+ Slapi_PBlock *search_pb;
+ Slapi_Entry **entries = NULL;
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> loadPluginConfig\n");
+
+ dna_write_lock();
+ deleteConfig();
+
+ search_pb = slapi_pblock_new();
+
+ slapi_search_internal_set_pb(search_pb, getPluginDN(),
+ LDAP_SCOPE_SUBTREE, "objectclass=*",
+ NULL, 0, NULL, NULL, getPluginID(), 0);
+ slapi_search_internal_pb(search_pb);
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
+
+ if (LDAP_SUCCESS != result) {
+ status = DNA_FAILURE;
+ goto cleanup;
+ }
+
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
+ &entries);
+ if (NULL == entries || NULL == entries[0]) {
+ status = DNA_SUCCESS;
+ goto cleanup;
+ }
+
+ for (i = 0; (entries[i] != NULL); i++) {
+ status = parseConfigEntry(entries[i]);
+ if (DNA_SUCCESS != status)
+ break;
+ }
+
+ cleanup:
+ slapi_free_search_results_internal(search_pb);
+ slapi_pblock_destroy(search_pb);
+ dna_unlock();
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- loadPluginConfig\n");
+
+ return status;
+}
+
+static int parseConfigEntry(Slapi_Entry * e)
+{
+ char *value;
+ struct configEntry *entry;
+ struct configEntry *config_entry;
+ PRCList *list;
+ int entry_added = 0;
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> parseConfigEntry\n");
+
+ entry = (struct configEntry *)
+ slapi_ch_calloc(1, sizeof(struct configEntry));
+ if (NULL == entry)
+ goto bail;
+
+ value = slapi_entry_get_ndn(e);
+ if (value) {
+ entry->dn = strdup(value);
+ }
+
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "----------> dn [%s]\n", entry->dn, 0, 0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_TYPE);
+ if (value) {
+ entry->type = value;
+ } else
+ goto bail;
+
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "----------> dnaType [%s]\n", entry->type, 0, 0);
+
+ /* FIXME: check the attribute type, it must suport matching rules and be
+ * indexed, these are requirements and failure to meet them should result in
+ * the configuration to be disarded and an ERROR logged prominently */
+
+ value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL);
+ if (value) {
+ entry->nextval = strtoul(value, 0, 0);
+ slapi_ch_free_string(&value);
+ } else
+ goto bail;
+
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "----------> dnaNextValue [%d]\n", entry->nextval, 0,
+ 0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_PREFIX);
+ if (value && value[0]) {
+ entry->prefix = value;
+ }
+
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "----------> dnaPrefix [%s]\n", entry->prefix, 0, 0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_INTERVAL);
+ if (value) {
+ entry->interval = strtoul(value, 0, 0);
+ } else
+ goto bail;
+
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "----------> dnaInterval [%s]\n", value, 0, 0);
+
+ slapi_ch_free_string(&value);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_GENERATE);
+ if (value) {
+ entry->generate = value;
+ }
+
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "----------> dnaMagicRegen [%s]\n", entry->generate,
+ 0, 0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_FILTER);
+ if (value) {
+ entry->filter = value;
+ entry->slapi_filter = slapi_str2filter(value);
+ } else
+ goto bail;
+
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "----------> dnaFilter [%s]\n", value, 0, 0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_SCOPE);
+ if (value) {
+ entry->scope = slapi_dn_normalize(value);
+ }
+
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "----------> dnaScope [%s]\n", entry->scope, 0, 0);
+
+ /* optional, if not specified set -1 which is converted to the max unisgnee
+ * value */
+ value = slapi_entry_attr_get_charptr(e, DNA_MAXVAL);
+ if (value) {
+ entry->maxval = strtoul(value, 0, 0);
+
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "----------> dnaMaxValue [%ld]\n", value, 0, 0);
+
+ slapi_ch_free_string(&value);
+ } else
+ entry->maxval = -1;
+
+
+ /**
+ * Finally add the entry to the list
+ * we group by type then by filter
+ * and finally sort by dn length with longer dn's
+ * first - this allows the scope checking
+ * code to be simple and quick and
+ * cunningly linear
+ */
+ if (!PR_CLIST_IS_EMPTY(dna_global_config)) {
+ list = PR_LIST_HEAD(dna_global_config);
+ while (list != dna_global_config) {
+ config_entry = (struct configEntry *) list;
+
+ if (slapi_attr_type_cmp(config_entry->type, entry->type, 1))
+ goto next;
+
+ if (slapi_filter_compare(config_entry->slapi_filter,
+ entry->slapi_filter))
+ goto next;
+
+ if (slapi_dn_issuffix(entry->scope, config_entry->scope)) {
+ PR_INSERT_BEFORE(&(entry->list), list);
+ slapi_log_error(SLAPI_LOG_CONFIG,
+ DNA_PLUGIN_SUBSYSTEM,
+ "store [%s] before [%s] \n", entry->scope,
+ config_entry->scope, 0);
+ entry_added = 1;
+ break;
+ }
+
+ next:
+ list = PR_NEXT_LINK(list);
+
+ if (dna_global_config == list) {
+ /* add to tail */
+ PR_INSERT_BEFORE(&(entry->list), list);
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "store [%s] at tail\n", entry->scope, 0,
+ 0);
+ entry_added = 1;
+ break;
+ }
+ }
+ } else {
+ /* first entry */
+ PR_INSERT_LINK(&(entry->list), dna_global_config);
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "store [%s] at head \n", entry->scope, 0, 0);
+ entry_added = 1;
+ }
+
+ bail:
+ if (0 == entry_added) {
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "config entry [%s] skipped\n", entry->dn, 0, 0);
+ freeConfigEntry(&entry);
+ }
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- parseConfigEntry\n");
+
+ return DNA_SUCCESS;
+}
+
+static void freeConfigEntry(struct configEntry ** entry)
+{
+ struct configEntry *e = *entry;
+
+ if (e->dn) {
+ slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+ "freeing config entry [%s]\n", e->dn, 0, 0);
+ slapi_ch_free_string(&e->dn);
+ }
+
+ if (e->type)
+ slapi_ch_free_string(&e->type);
+
+ if (e->prefix)
+ slapi_ch_free_string(&e->prefix);
+
+ if (e->filter)
+ slapi_ch_free_string(&e->filter);
+
+ if (e->slapi_filter)
+ slapi_filter_free(e->slapi_filter, 1);
+
+ if (e->generate)
+ slapi_ch_free_string(&e->generate);
+
+ if (e->scope)
+ slapi_ch_free_string(&e->scope);
+
+ slapi_ch_free((void **) entry);
+}
+
+static void deleteConfigEntry(PRCList * entry)
+{
+ PR_REMOVE_LINK(entry);
+ freeConfigEntry((struct configEntry **) & entry);
+}
+
+static void deleteConfig()
+{
+ PRCList *list;
+
+ while (!PR_CLIST_IS_EMPTY(dna_global_config)) {
+ list = PR_LIST_HEAD(dna_global_config);
+ deleteConfigEntry(list);
+ }
+
+ return;
+}
+
+/****************************************************
+ Distributed ranges Helpers
+****************************************************/
+
+static int dna_fix_maxval(Slapi_DN *dn, PRUint64 *cur, PRUint64 *max)
+{
+ /* TODO: check the main partition to see if another range
+ * is available, and set the new local configuration
+ * accordingly.
+ * If a new range is not available run the retrieval task
+ * and simply return error
+ */
+
+ return LDAP_OPERATIONS_ERROR;
+}
+
+static void dna_notice_allocation(Slapi_DN *dn, PRUint64 new)
+{
+ /* TODO: check if we passed a new chunk threshold and update
+ * the shared configuration on the public partition.
+ */
+
+ return;
+}
+
+/****************************************************
+ Helpers
+****************************************************/
+
+static char *dna_get_dn(Slapi_PBlock * pb)
+{
+ char *dn = 0;
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> dna_get_dn\n");
+
+ if (slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn)) {
+ slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_get_dn: failed to get dn of changed entry");
+ goto bail;
+ }
+
+/* slapi_dn_normalize( dn );
+*/
+ bail:
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- dna_get_dn\n");
+
+ return dn;
+}
+
+/* config check
+ matching config dn or a descendent reloads config
+*/
+static int dna_dn_is_config(char *dn)
+{
+ int ret = 0;
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> dna_is_config\n");
+
+ if (slapi_dn_issuffix(dn, getPluginDN())) {
+ ret = 1;
+ }
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- dna_is_config\n");
+
+ return ret;
+}
+
+#define DNA_LDAP_TAG_SK_REVERSE 0x81L
+
+static LDAPControl *dna_build_sort_control(const char *attr)
+{
+ LDAPControl *ctrl;
+ BerElement *ber;
+ int rc;
+
+ ber = ber_alloc();
+ if (NULL == ber)
+ return NULL;
+
+ rc = ber_printf(ber, "{{stb}}", attr, DNA_LDAP_TAG_SK_REVERSE, 1);
+ if (-1 == rc) {
+ ber_free(ber, 1);
+ return NULL;
+ }
+
+ rc = slapi_build_control(LDAP_CONTROL_SORTREQUEST, ber, 1, &ctrl);
+
+ ber_free(ber, 1);
+
+ if (LDAP_SUCCESS != rc)
+ return NULL;
+
+ return ctrl;
+}
+
+/****************************************************
+ Functions that actually do things other
+ than config and startup
+****************************************************/
+
+/* we do search all values between newval and maxval asking the
+ * server to sort them, then we check the first free spot and
+ * use it as newval */
+static int dna_first_free_value(struct configEntry *config_entry,
+ PRUint64 *newval,
+ PRUint64 maxval,
+ PRUint64 increment)
+{
+ Slapi_Entry **entries = NULL;
+ Slapi_PBlock *pb = NULL;
+ LDAPControl **ctrls;
+ char *attrs[2];
+ char *filter;
+ char *prefix;
+ char *type;
+ int preflen;
+ int result, status;
+ PRUint64 tmpval, sval, i;
+ char *strval = NULL;
+
+ prefix = config_entry->prefix;
+ type = config_entry->type;
+ tmpval = *newval;
+
+ attrs[0] = type;
+ attrs[1] = NULL;
+
+ ctrls = (LDAPControl **)slapi_ch_calloc(2, sizeof(LDAPControl));
+ if (NULL == ctrls)
+ return LDAP_OPERATIONS_ERROR;
+
+ ctrls[0] = dna_build_sort_control(config_entry->type);
+ if (NULL == ctrls[0]) {
+ slapi_ch_free((void **)&ctrls);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ filter = slapi_ch_smprintf("(&%s(&(%s>=%s%llu)(%s<=%s%llu)))",
+ config_entry->filter,
+ type, prefix?prefix:"", tmpval,
+ type, prefix?prefix:"", maxval);
+ if (NULL == filter) {
+ ldap_control_free(ctrls[0]);
+ slapi_ch_free((void **)&ctrls);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ pb = slapi_pblock_new();
+ if (NULL == pb) {
+ ldap_control_free(ctrls[0]);
+ slapi_ch_free((void **)&ctrls);
+ slapi_ch_free_string(&filter);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ slapi_search_internal_set_pb(pb, config_entry->scope,
+ LDAP_SCOPE_SUBTREE, filter,
+ attrs, 0, ctrls,
+ NULL, getPluginID(), 0);
+ slapi_search_internal_pb(pb);
+/*
+ ldap_control_free(ctrls[0]);
+*/
+ slapi_ch_free_string(&filter);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
+ if (LDAP_SUCCESS != result) {
+ status = LDAP_OPERATIONS_ERROR;
+ goto cleanup;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
+ &entries);
+
+ if (NULL == entries || NULL == entries[0]) {
+ /* no values means we already have a good value */
+ status = LDAP_SUCCESS;
+ goto cleanup;
+ }
+
+ /* entries are sorted and filtered for value >= tval therefore if the
+ * first one does not match tval it means that the value is free,
+ * otherwise we need to cycle through values until we find a mismatch,
+ * the first mismatch is the first free pit */
+
+ preflen = prefix?strlen(prefix):0;
+ sval = 0;
+ for (i = 0; NULL != entries[i]; i++) {
+ strval = slapi_entry_attr_get_charptr(entries[i], type);
+ if (preflen) {
+ if (strlen(strval) <= preflen) {
+ /* something very wrong here ... */
+ status = LDAP_OPERATIONS_ERROR;
+ goto cleanup;
+ }
+ strval = &strval[preflen-1];
+ }
+
+ errno = 0;
+ sval = strtoul(strval, 0, 0);
+ if (errno) {
+ /* something very wrong here ... */
+ status = LDAP_OPERATIONS_ERROR;
+ goto cleanup;
+ }
+ slapi_ch_free_string(&strval);
+
+ if (tmpval != sval)
+ break;
+
+ if (maxval < sval)
+ break;
+
+ tmpval += increment;
+ }
+
+ *newval = tmpval;
+ status = LDAP_SUCCESS;
+
+cleanup:
+ slapi_ch_free_string(&strval);
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+
+ return status;
+}
+
+/*
+ * Perform ldap operationally atomic increment
+ * Return the next value to be assigned
+ * Method:
+ * 1. retrieve entry
+ * 2. do increment operations
+ * 3. remove current value, add new value in one operation
+ * 4. if failed, and less than 3 times, goto 1
+ */
+static int dna_get_next_value(struct configEntry *config_entry,
+ char **next_value_ret)
+{
+ Slapi_PBlock *pb = NULL;
+ char *old_value = NULL;
+ Slapi_Entry *e = NULL;
+ Slapi_DN *dn = NULL;
+ char *attrlist[4];
+ int attempts;
+ int ret;
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> dna_get_next_value\n");
+
+ /* get pre-requisites to search */
+ dn = slapi_sdn_new_dn_byref(config_entry->dn);
+ attrlist[0] = DNA_NEXTVAL;
+ attrlist[1] = DNA_MAXVAL;
+ attrlist[2] = DNA_INTERVAL;
+ attrlist[3] = NULL;
+
+
+ /* the operation is constructed such that race conditions
+ * to increment the value are detected and avoided - one wins,
+ * one loses - however, there is no need for the server to compete
+ * with itself so we lock here
+ */
+
+ slapi_lock_mutex(g_new_value_lock);
+
+ for (attempts = 0; attempts < 3; attempts++) {
+
+ LDAPMod mod_add;
+ LDAPMod mod_delete;
+ LDAPMod *mods[3];
+ char *delete_val[2];
+ char *add_val[2];
+ char new_value[16];
+ char *interval;
+ char *max_value;
+ PRUint64 increment = 1; /* default increment */
+ PRUint64 setval = 0;
+ PRUint64 newval = 0;
+ PRUint64 maxval = -1;
+
+ /* do update */
+ ret = slapi_search_internal_get_entry(dn, attrlist, &e,
+ getPluginID());
+ if (LDAP_SUCCESS != ret) {
+ ret = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ old_value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL);
+ if (NULL == old_value) {
+ ret = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ setval = strtoul(old_value, 0, 0);
+
+ max_value = slapi_entry_attr_get_charptr(e, DNA_MAXVAL);
+ if (max_value) {
+ maxval = strtoul(max_value, 0, 0);
+ slapi_ch_free_string(&max_value);
+ }
+
+ /* if not present the default is 1 */
+ interval = slapi_entry_attr_get_charptr(e, DNA_INTERVAL);
+ if (NULL != interval) {
+ increment = strtoul(interval, 0, 0);
+ }
+
+ slapi_entry_free(e);
+ e = NULL;
+
+ /* check the value is actually in range */
+
+ /* verify the new value is actually free and get the first
+ * one free if not*/
+ ret = dna_first_free_value(config_entry, &setval, maxval, increment);
+ if (LDAP_SUCCESS != ret)
+ goto done;
+
+ /* try for a new range or fail */
+ if (setval > maxval) {
+ ret = dna_fix_maxval(dn, &setval, &maxval);
+ if (LDAP_SUCCESS != ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_get_next_value: no more IDs available!!\n");
+ goto done;
+ }
+
+ /* verify the new value is actually free and get the first
+ * one free if not */
+ ret = dna_first_free_value(config_entry, &setval, maxval, increment);
+ if (LDAP_SUCCESS != ret)
+ goto done;
+ }
+
+ if (setval > maxval) {
+ ret = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ newval = setval + increment;
+
+ /* try for a new range or fail */
+ if (newval > maxval) {
+ ret = dna_fix_maxval(dn, &newval, &maxval);
+ if (LDAP_SUCCESS != ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_get_next_value: no more IDs available!!\n");
+ goto done;
+ }
+ }
+
+ /* try to set the new value */
+
+ sprintf(new_value, "%llu", newval);
+
+ delete_val[0] = old_value;
+ delete_val[1] = 0;
+
+ mod_delete.mod_op = LDAP_MOD_DELETE;
+ mod_delete.mod_type = DNA_NEXTVAL;
+ mod_delete.mod_values = delete_val;
+
+ add_val[0] = new_value;
+ add_val[1] = 0;
+
+ mod_add.mod_op = LDAP_MOD_ADD;
+ mod_add.mod_type = DNA_NEXTVAL;
+ mod_add.mod_values = add_val;
+
+ mods[0] = &mod_delete;
+ mods[1] = &mod_add;
+ mods[2] = 0;
+
+ pb = slapi_pblock_new();
+ if (NULL == pb) {
+ ret = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ slapi_modify_internal_set_pb(pb, config_entry->dn,
+ mods, 0, 0, getPluginID(), 0);
+
+ slapi_modify_internal_pb(pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+
+ slapi_pblock_destroy(pb);
+ pb = NULL;
+ slapi_ch_free_string(&interval);
+ slapi_ch_free_string(&old_value);
+
+ if (LDAP_SUCCESS == ret) {
+ *next_value_ret = slapi_ch_smprintf("%llu", setval);
+ if (NULL == *next_value_ret) {
+ ret = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ dna_notice_allocation(dn, newval);
+ goto done;
+ }
+
+ if (LDAP_NO_SUCH_ATTRIBUTE != ret) {
+ /* not the result of a race
+ to change the value
+ */
+ goto done;
+ }
+ }
+
+ done:
+
+ slapi_unlock_mutex(g_new_value_lock);
+
+ if (LDAP_SUCCESS != ret)
+ slapi_ch_free_string(&old_value);
+
+ if (dn)
+ slapi_sdn_free(&dn);
+
+ if (e)
+ slapi_entry_free(e);
+
+ if (pb)
+ slapi_pblock_destroy(pb);
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- dna_get_next_value\n");
+
+ return ret;
+}
+
+/* for mods and adds:
+ where dn's are supplied, the closest in scope
+ is used as long as the type and filter
+ are identical - otherwise all matches count
+*/
+
+static int dna_pre_op(Slapi_PBlock * pb, int modtype)
+{
+ char *dn = 0;
+ PRCList *list = 0;
+ struct configEntry *config_entry = 0;
+ struct slapi_entry *e = 0;
+ char *last_type = 0;
+ char *value = 0;
+ int generate = 0;
+ Slapi_Mods *smods = 0;
+ Slapi_Mod *smod = 0;
+ LDAPMod **mods;
+ int free_entry = 0;
+ char *errstr = NULL;
+ int ret = 0;
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> dna_pre_op\n");
+
+ /* Just bail if we aren't ready to service requests yet. */
+ if (!g_plugin_started)
+ goto bail;
+
+ if (0 == (dn = dna_get_dn(pb)))
+ goto bail;
+
+ if (dna_dn_is_config(dn))
+ goto bail;
+
+ if (LDAP_CHANGETYPE_ADD == modtype) {
+ slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
+ } else {
+ /* 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);
+ */
+ Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(dn);
+ if (tmp_dn) {
+ slapi_search_internal_get_entry(tmp_dn, 0, &e, getPluginID());
+ slapi_sdn_free(&tmp_dn);
+ free_entry = 1;
+ }
+
+ /* 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);
+ }
+
+ if (0 == e)
+ goto bailmod;
+
+ dna_read_lock();
+
+ if (!PR_CLIST_IS_EMPTY(dna_global_config)) {
+ list = PR_LIST_HEAD(dna_global_config);
+
+ while (list != dna_global_config && LDAP_SUCCESS == ret) {
+ config_entry = (struct configEntry *) list;
+
+ /* did we already service this type? */
+ if (last_type) {
+ if (!slapi_attr_type_cmp(config_entry->type, last_type, 1))
+ goto next;
+ }
+
+ /* is the entry in scope? */
+ if (config_entry->scope) {
+ if (!slapi_dn_issuffix(dn, config_entry->scope))
+ goto next;
+ }
+
+ /* does the entry match the filter? */
+ if (config_entry->slapi_filter) {
+ if (LDAP_SUCCESS != slapi_vattr_filter_test(pb,
+ e,
+ config_entry->
+ slapi_filter, 0))
+ goto next;
+ }
+
+
+ if (LDAP_CHANGETYPE_ADD == modtype) {
+ /* does attribute contain the magic value
+ or is the type not there?
+ */
+ value =
+ slapi_entry_attr_get_charptr(e, config_entry->type);
+ if ((value
+ && !slapi_UTF8CASECMP(config_entry->generate, value))
+ || 0 == value) {
+ generate = 1;
+ }
+ } else {
+ /* check mods for magic value */
+ Slapi_Mod *next_mod = slapi_mod_new();
+ smod = slapi_mods_get_first_smod(smods, next_mod);
+ while (smod) {
+ char *type = (char *)
+ slapi_mod_get_type(smod);
+
+ if (slapi_attr_types_equivalent(type,
+ config_entry->type)) {
+ struct berval *bv =
+ slapi_mod_get_first_value(smod);
+ int len = strlen(config_entry->generate);
+
+
+ if (len == bv->bv_len) {
+ if (!slapi_UTF8NCASECMP(bv->bv_val,
+ config_entry->generate,
+ len))
+
+ generate = 1;
+ break;
+ }
+ }
+
+ slapi_mod_done(next_mod);
+ smod = slapi_mods_get_next_smod(smods, next_mod);
+ }
+
+ slapi_mod_free(&next_mod);
+ }
+
+ if (generate) {
+ char *new_value;
+ int len;
+
+ /* create the value to add */
+ ret = dna_get_next_value(config_entry, &value);
+ if (DNA_SUCCESS != ret) {
+ errstr = slapi_ch_smprintf("Allocation of a new value for"
+ " %s failed! Unable to proceed.",
+ config_entry->type);
+ break;
+ }
+
+ len = strlen(value) + 1;
+ if (config_entry->prefix) {
+ len += strlen(config_entry->prefix);
+ }
+
+ new_value = slapi_ch_malloc(len);
+
+ if (config_entry->prefix) {
+ strcpy(new_value, config_entry->prefix);
+ strcat(new_value, value);
+ } else
+ strcpy(new_value, value);
+
+ /* do the mod */
+ if (LDAP_CHANGETYPE_ADD == modtype) {
+ /* add - add to entry */
+ slapi_entry_attr_set_charptr(e,
+ config_entry->type,
+ new_value);
+ } else {
+ /* mod - add to mods */
+ slapi_mods_add_string(smods,
+ LDAP_MOD_REPLACE,
+ config_entry->type, new_value);
+ }
+
+ /* free up */
+ slapi_ch_free_string(&value);
+ slapi_ch_free_string(&new_value);
+
+ /* make sure we don't generate for this
+ * type again
+ */
+ if (LDAP_SUCCESS == ret) {
+ last_type = config_entry->type;
+ }
+
+ generate = 0;
+ }
+ next:
+ list = PR_NEXT_LINK(list);
+ }
+ }
+
+ dna_unlock();
+
+ bailmod:
+ if (LDAP_CHANGETYPE_MODIFY == modtype) {
+ /* these are the mods you made, really,
+ * I didn't change them, honest, just had a quick look
+ */
+ mods = slapi_mods_get_ldapmods_passout(smods);
+ slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods);
+ slapi_mods_free(&smods);
+ }
+
+ bail:
+
+ if (free_entry && e)
+ slapi_entry_free(e);
+
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM,
+ "dna_pre_op: operation failure [%d]\n", ret);
+ slapi_send_ldap_result(pb, ret, NULL, errstr, 0, NULL);
+ slapi_ch_free((void **)&errstr);
+ ret = DNA_FAILURE;
+ }
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- dna_pre_op\n");
+
+ return ret;
+}
+
+static int dna_add_pre_op(Slapi_PBlock * pb)
+{
+ return dna_pre_op(pb, LDAP_CHANGETYPE_ADD);
+}
+
+static int dna_mod_pre_op(Slapi_PBlock * pb)
+{
+ return dna_pre_op(pb, LDAP_CHANGETYPE_MODIFY);
+}
+
+static int dna_config_check_post_op(Slapi_PBlock * pb)
+{
+ char *dn;
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "--> dna_config_check_post_op\n");
+
+ if ((dn = dna_get_dn(pb))) {
+ if (dna_dn_is_config(dn))
+ loadPluginConfig();
+ }
+
+ slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
+ "<-- dna_config_check_post_op\n");
+
+ return 0;
+}
+
+/****************************************************
+ End of
+ Functions that actually do things other
+ than config and startup
+****************************************************/
+
+/**
+ * debug functions to print config
+ */
+void dnaDumpConfig()
+{
+ PRCList *list;
+
+ dna_read_lock();
+
+ if (!PR_CLIST_IS_EMPTY(dna_global_config)) {
+ list = PR_LIST_HEAD(dna_global_config);
+ while (list != dna_global_config) {
+ dnaDumpConfigEntry((struct configEntry *) list);
+ list = PR_NEXT_LINK(list);
+ }
+ }
+
+ dna_unlock();
+}
+
+
+void dnaDumpConfigEntry(struct configEntry * entry)
+{
+ printf("<- type --------------> %s\n", entry->type);
+ printf("<---- prefix ---------> %s\n", entry->prefix);
+ printf("<---- next value -----> %lu\n", entry->nextval);
+ printf("<---- interval -------> %lu\n", entry->interval);
+ printf("<---- generate flag --> %s\n", entry->generate);
+}
diff --git a/daemons/ipa-slapi-plugins/ipa-memberof/Makefile.am b/daemons/ipa-slapi-plugins/ipa-memberof/Makefile.am
new file mode 100644
index 000000000..d0ac7f935
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-memberof/Makefile.am
@@ -0,0 +1,43 @@
+NULL =
+
+INCLUDES = \
+ -I. \
+ -I$(srcdir) \
+ -DPREFIX=\""$(prefix)"\" \
+ -DBINDIR=\""$(bindir)"\" \
+ -DLIBDIR=\""$(libdir)"\" \
+ -DLIBEXECDIR=\""$(libexecdir)"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ $(MOZLDAP_CFLAGS) \
+ $(KRB5_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(NULL)
+
+plugindir = $(libdir)/dirsrv/plugins
+plugin_LTLIBRARIES = \
+ libipa-memberof-plugin.la \
+ $(NULL)
+
+libipa_memberof_plugin_la_SOURCES = \
+ ipa-memberof.c \
+ ipa-memberof_config.c \
+ $(NULL)
+
+libipa_memberof_plugin_la_LDFLAGS = -avoid-version
+
+libipa_memberof_plugin_la_LIBADD = \
+ $(MOZLDAP_LIBS) \
+ $(NULL)
+
+appdir = $(IPA_DATA_DIR)
+app_DATA = \
+ memberof-conf.ldif \
+ $(NULL)
+
+EXTRA_DIST = \
+ $(app_DATA) \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
diff --git a/daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c b/daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c
new file mode 100644
index 000000000..3baf2f6cf
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c
@@ -0,0 +1,2244 @@
+/** 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:
+ * Pete Rowley <prowley@redhat.com>
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK
+ **/
+
+/* The memberof plugin updates the memberof attribute of entries
+ * based on modifications performed on groupofuniquenames entries
+ *
+ * In addition the plugin provides a DS task that may be started
+ * administrative clients and that creates the initial memberof
+ * list for imported entries and/or fixes the memberof list of
+ * existing entries that have inconsistent state (for example,
+ * if the memberof attribute was incorrectly edited directly)
+ *
+ * To start the memberof task add an entry like:
+ *
+ * dn: cn=mytask, cn=memberof task, cn=tasks, cn=config
+ * objectClass: top
+ * objectClass: extensibleObject
+ * cn: mytask
+ * basedn: dc=example, dc=com
+ * filter: (uid=test4)
+ *
+ * where "basedn" is required and refers to the top most node to perform the
+ * task on, and where "filter" is an optional attribute that provides a filter
+ * describing the entries to be worked on
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <dirsrv/slapi-plugin.h>
+
+#include "string.h"
+#include "nspr.h"
+
+#include "ipa-memberof.h"
+
+static Slapi_PluginDesc pdesc = { "ipamo", "FreeIPA project", "FreeIPA/1.0",
+ "IPA memberof plugin" };
+
+static void* _PluginID = NULL;
+static Slapi_Mutex *memberof_operation_lock = 0;
+MemberOfConfig *qsortConfig = 0;
+
+typedef struct _memberofstringll
+{
+ const char *dn;
+ void *next;
+} memberofstringll;
+
+typedef struct _memberof_get_groups_data
+{
+ MemberOfConfig *config;
+ Slapi_Value *memberdn_val;
+ Slapi_ValueSet **groupvals;
+} memberof_get_groups_data;
+
+/****** secrets *********/
+#ifndef SLAPI_TASK_PUBLIC
+/*from FDS slap.h
+ * until we get a proper api for access
+ */
+#define TASK_RUNNING_AS_TASK 0x0
+
+/******************************************************************************
+ * Online tasks interface (to support import, export, etc)
+ * After some cleanup, we could consider making these public.
+ */
+struct _slapi_task {
+ struct _slapi_task *next;
+ char *task_dn;
+ int task_exitcode; /* for the end user */
+ int task_state; /* (see above) */
+ int task_progress; /* number between 0 and task_work */
+ int task_work; /* "units" of work to be done */
+ int task_flags; /* (see above) */
+
+ /* it is the task's responsibility to allocate this memory & free it: */
+ char *task_status; /* transient status info */
+ char *task_log; /* appended warnings, etc */
+
+ void *task_private; /* for use by backends */
+ TaskCallbackFn cancel; /* task has been cancelled by user */
+ TaskCallbackFn destructor; /* task entry is being destroyed */
+ int task_refcount;
+};
+
+static void slapi_task_set_data(Slapi_Task *task, void *data)
+{
+ if (task) {
+ task->task_private = data;
+ }
+}
+
+/*
+ * Retrieve some opaque task specific data from the task.
+ */
+static void * slapi_task_get_data(Slapi_Task *task)
+{
+ if (task) {
+ return task->task_private;
+ }
+}
+
+static void slapi_task_begin(Slapi_Task *task, int total_work)
+{
+ if (task) {
+ task->task_work = total_work;
+ task->task_progress = 0;
+ task->task_state = SLAPI_TASK_RUNNING;
+ slapi_task_status_changed(task);
+ }
+}
+
+static void slapi_task_inc_progress(Slapi_Task *task)
+{
+ if (task) {
+ task->task_progress++;
+ slapi_task_status_changed(task);
+ }
+}
+
+static void slapi_task_finish(Slapi_Task *task, int rc)
+{
+ if (task) {
+ task->task_exitcode = rc;
+ task->task_state = SLAPI_TASK_FINISHED;
+ slapi_task_status_changed(task);
+ }
+}
+
+static void slapi_task_set_destructor_fn(Slapi_Task *task, TaskCallbackFn func)
+{
+ if (task) {
+ task->destructor = func;
+ }
+}
+
+#endif /* !SLAPI_TASK_PUBLIC */
+/****** secrets ********/
+
+/*** function prototypes ***/
+
+/* exported functions */
+int ipamo_postop_init(Slapi_PBlock *pb );
+
+/* plugin callbacks */
+static int memberof_postop_del(Slapi_PBlock *pb );
+static int memberof_postop_modrdn(Slapi_PBlock *pb );
+static int memberof_postop_modify(Slapi_PBlock *pb );
+static int memberof_postop_add(Slapi_PBlock *pb );
+static int memberof_postop_start(Slapi_PBlock *pb);
+static int memberof_postop_close(Slapi_PBlock *pb);
+
+/* supporting cast */
+static int memberof_oktodo(Slapi_PBlock *pb);
+static char *memberof_getdn(Slapi_PBlock *pb);
+static int memberof_modop_one(Slapi_PBlock *pb, MemberOfConfig *config, int mod_op,
+ char *op_this, char *op_to);
+static int memberof_modop_one_r(Slapi_PBlock *pb, MemberOfConfig *config, int mod_op,
+ char *group_dn, char *op_this, char *op_to, memberofstringll *stack);
+static int memberof_add_one(Slapi_PBlock *pb, MemberOfConfig *config, char *addthis,
+ char *addto);
+static int memberof_del_one(Slapi_PBlock *pb, MemberOfConfig *config, char *delthis,
+ char *delfrom);
+static int memberof_mod_smod_list(Slapi_PBlock *pb, MemberOfConfig *config, int mod,
+ char *groupdn, Slapi_Mod *smod);
+static int memberof_add_smod_list(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *groupdn, Slapi_Mod *smod);
+static int memberof_del_smod_list(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *groupdn, Slapi_Mod *smod);
+static int memberof_mod_attr_list(Slapi_PBlock *pb, MemberOfConfig *config, int mod,
+ char *groupdn, Slapi_Attr *attr);
+static int memberof_mod_attr_list_r(Slapi_PBlock *pb, MemberOfConfig *config,
+ int mod, char *group_dn, char *op_this, Slapi_Attr *attr, memberofstringll *stack);
+static int memberof_add_attr_list(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *groupdn, Slapi_Attr *attr);
+static int memberof_del_attr_list(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *groupdn, Slapi_Attr *attr);
+static int memberof_moddn_attr_list(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *pre_dn, char *post_dn, Slapi_Attr *attr);
+static int memberof_replace_list(Slapi_PBlock *pb, MemberOfConfig *config, char *group_dn);
+static void memberof_set_plugin_id(void * plugin_id);
+static void *memberof_get_plugin_id();
+static int memberof_compare(MemberOfConfig *config, const void *a, const void *b);
+static int memberof_qsort_compare(const void *a, const void *b);
+static void memberof_load_array(Slapi_Value **array, Slapi_Attr *attr);
+static int memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, char *dn);
+static int memberof_call_foreach_dn(Slapi_PBlock *pb, char *dn,
+ char *type, plugin_search_entry_callback callback, void *callback_data);
+static int memberof_is_direct_member(MemberOfConfig *config, Slapi_Value *groupdn,
+ Slapi_Value *memberdn);
+static Slapi_ValueSet *memberof_get_groups(MemberOfConfig *config, char *memberdn);
+static int memberof_get_groups_r(MemberOfConfig *config, char *memberdn,
+ memberof_get_groups_data *data);
+static int memberof_get_groups_callback(Slapi_Entry *e, void *callback_data);
+static int memberof_test_membership(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *group_dn);
+static int memberof_test_membership_callback(Slapi_Entry *e, void *callback_data);
+static int memberof_del_dn_type_callback(Slapi_Entry *e, void *callback_data);
+static int memberof_replace_dn_type_callback(Slapi_Entry *e, void *callback_data);
+static int memberof_replace_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *pre_dn, char *post_dn);
+static int memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config,
+ int mod_op, char *group_dn, char *op_this, char *replace_with, char *op_to,
+ memberofstringll *stack);
+static int memberof_task_add(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext,
+ void *arg);
+static void memberof_task_destructor(Slapi_Task *task);
+static const char *fetch_attr(Slapi_Entry *e, const char *attrname,
+ const char *default_val);
+static void memberof_fixup_task_thread(void *arg);
+static int memberof_fix_memberof(MemberOfConfig *config, char *dn, char *filter_str);
+static int memberof_fix_memberof_callback(Slapi_Entry *e, void *callback_data);
+
+
+/*** implementation ***/
+
+
+/*** exported functions ***/
+
+/*
+ * ipamo_postop_init()
+ *
+ * Register plugin call backs
+ *
+ */
+int
+ipamo_postop_init(Slapi_PBlock *pb)
+{
+ int ret = 0;
+ char *memberof_plugin_identity = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_init\n" );
+ /*
+ * Get plugin identity and stored it for later use
+ * Used for internal operations
+ */
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &memberof_plugin_identity);
+ PR_ASSERT (memberof_plugin_identity);
+ memberof_set_plugin_id(memberof_plugin_identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN,
+ (void *) memberof_postop_del ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN,
+ (void *) memberof_postop_modrdn ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN,
+ (void *) memberof_postop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN,
+ (void *) memberof_postop_add ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) memberof_postop_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) memberof_postop_close ) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "ipamo_postop_init failed\n" );
+ ret = -1;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_postop_init\n" );
+ return ret;
+}
+
+/*
+ * memberof_postop_start()
+ *
+ * Do plugin start up stuff
+ *
+ */
+int memberof_postop_start(Slapi_PBlock *pb)
+{
+ int rc = 0;
+ Slapi_Entry *config_e = NULL; /* entry containing plugin config */
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "--> memberof_postop_start\n" );
+
+ memberof_operation_lock = slapi_new_mutex();
+ if(0 == memberof_operation_lock)
+ {
+ rc = -1;
+ goto bail;
+ }
+
+ if ( slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &config_e ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "missing config entry\n" );
+ rc = -1;
+ goto bail;
+ }
+
+ if (( rc = memberof_config( config_e )) != LDAP_SUCCESS ) {
+ slapi_log_error( SLAPI_LOG_FATAL, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "configuration failed (%s)\n", ldap_err2string( rc ));
+ return( -1 );
+ }
+
+ rc = slapi_task_register_handler("memberof task", memberof_task_add);
+ if(rc)
+ {
+ goto bail;
+ }
+
+ /*
+ * TODO: start up operation actor thread
+ * need to get to a point where server failure
+ * or shutdown doesn't hose our operations
+ * so we should create a task entry that contains
+ * all required information to complete the operation
+ * then the tasks can be restarted safely if
+ * interrupted
+ */
+
+bail:
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "<-- memberof_postop_start\n" );
+
+ return rc;
+}
+
+/*
+ * memberof_postop_close()
+ *
+ * Do plugin shut down stuff
+ *
+ */
+int memberof_postop_close(Slapi_PBlock *pb)
+{
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "--> memberof_postop_close\n" );
+
+
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "<-- memberof_postop_close\n" );
+ return 0;
+}
+
+/*
+ * memberof_postop_del()
+ *
+ * All entries with a memberOf attribute that contains the group DN get retrieved
+ * and have the their memberOf attribute regenerated (it is far too complex and
+ * error prone to attempt to change only those dn values involved in this case -
+ * mainly because the deleted group may itself be a member of other groups which
+ * may be members of other groups etc. in a big recursive mess involving dependency
+ * chains that must be created and traversed in order to decide if an entry should
+ * really have those groups removed too)
+ */
+int memberof_postop_del(Slapi_PBlock *pb)
+{
+ int ret = 0;
+ MemberOfConfig configCopy = {0, 0, 0, 0};
+ char *dn;
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "--> memberof_postop_del\n" );
+
+ if(memberof_oktodo(pb) && (dn = memberof_getdn(pb)))
+ {
+ struct slapi_entry *e = NULL;
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &e );
+
+ /* We need to get the config lock first. Trying to get the
+ * config lock after we already hold the op lock can cause
+ * a deadlock. */
+ memberof_rlock_config();
+ /* copy config so it doesn't change out from under us */
+ memberof_copy_config(&configCopy, memberof_get_config());
+ memberof_unlock_config();
+
+ /* get the memberOf operation lock */
+ memberof_lock();
+
+ /* remove this group DN from the
+ * membership lists of groups
+ */
+ memberof_del_dn_from_groups(pb, &configCopy, dn);
+
+ /* is the entry of interest as a group? */
+ if(e && !slapi_filter_test_simple(e, configCopy.group_filter))
+ {
+ Slapi_Attr *attr = 0;
+
+ if(0 == slapi_entry_attr_find(e, configCopy.groupattr, &attr))
+ {
+ memberof_del_attr_list(pb, &configCopy, dn, attr);
+ }
+ }
+
+ memberof_unlock();
+
+ memberof_free_config(&configCopy);
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "<-- memberof_postop_del\n" );
+ return ret;
+}
+
+typedef struct _memberof_del_dn_data
+{
+ char *dn;
+ char *type;
+} memberof_del_dn_data;
+
+int memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, char *dn)
+{
+ memberof_del_dn_data data = {dn, config->groupattr};
+
+ return memberof_call_foreach_dn(pb, dn,
+ config->groupattr, memberof_del_dn_type_callback, &data);
+}
+
+int memberof_del_dn_type_callback(Slapi_Entry *e, void *callback_data)
+{
+ int rc = 0;
+ LDAPMod mod;
+ LDAPMod *mods[2];
+ char *val[2];
+ Slapi_PBlock *mod_pb = 0;
+
+ mod_pb = slapi_pblock_new();
+
+ mods[0] = &mod;
+ mods[1] = 0;
+
+ val[0] = ((memberof_del_dn_data *)callback_data)->dn;
+ val[1] = 0;
+
+ mod.mod_op = LDAP_MOD_DELETE;
+ mod.mod_type = ((memberof_del_dn_data *)callback_data)->type;
+ mod.mod_values = val;
+
+ slapi_modify_internal_set_pb(
+ mod_pb, slapi_entry_get_dn(e),
+ mods, 0, 0,
+ memberof_get_plugin_id(), 0);
+
+ slapi_modify_internal_pb(mod_pb);
+
+ slapi_pblock_get(mod_pb,
+ SLAPI_PLUGIN_INTOP_RESULT,
+ &rc);
+
+ slapi_pblock_destroy(mod_pb);
+
+ return rc;
+}
+
+/*
+ * Does a callback search of "type=dn" under the db suffix that "dn" is in.
+ * If "dn" is a user, you'd want "type" to be "member". If "dn" is a group,
+ * you could want type to be either "member" or "memberOf" depending on the
+ * case.
+ */
+int memberof_call_foreach_dn(Slapi_PBlock *pb, char *dn,
+ char *type, plugin_search_entry_callback callback, void *callback_data)
+{
+ int rc = 0;
+ Slapi_PBlock *search_pb = slapi_pblock_new();
+ Slapi_Backend *be = 0;
+ Slapi_DN *sdn = 0;
+ Slapi_DN *base_sdn = 0;
+ char *filter_str = 0;
+
+ /* get the base dn for the backend we are in
+ (we don't support having members and groups in
+ different backends - issues with offline / read only backends)
+ */
+ sdn = slapi_sdn_new_dn_byref(dn);
+ be = slapi_be_select(sdn);
+ if(be)
+ {
+ base_sdn = (Slapi_DN*)slapi_be_getsuffix(be,0);
+ }
+
+ if(base_sdn)
+ {
+ filter_str = slapi_ch_smprintf("(%s=%s)", type, dn);
+ }
+
+ if(filter_str)
+ {
+ slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(base_sdn),
+ LDAP_SCOPE_SUBTREE, filter_str, 0, 0,
+ 0, 0,
+ memberof_get_plugin_id(),
+ 0);
+
+ slapi_search_internal_callback_pb(search_pb,
+ callback_data,
+ 0, callback,
+ 0);
+ }
+
+ slapi_sdn_free(&sdn);
+ slapi_pblock_destroy(search_pb);
+ slapi_ch_free_string(&filter_str);
+ return rc;
+}
+
+/*
+ * memberof_postop_modrdn()
+ *
+ * All entries with a memberOf attribute that contains the old group DN get retrieved
+ * and have the old group DN deleted and the new group DN added to their memberOf attribute
+ */
+int memberof_postop_modrdn(Slapi_PBlock *pb)
+{
+ int ret = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "--> memberof_postop_modrdn\n" );
+
+ if(memberof_oktodo(pb))
+ {
+ MemberOfConfig *mainConfig = 0;
+ MemberOfConfig configCopy = {0, 0, 0, 0};
+ struct slapi_entry *pre_e = NULL;
+ struct slapi_entry *post_e = NULL;
+ char *pre_dn = 0;
+ char *post_dn = 0;
+ int interested = 0;
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &pre_e );
+ slapi_pblock_get( pb, SLAPI_ENTRY_POST_OP, &post_e );
+
+ if(pre_e && post_e)
+ {
+ pre_dn = slapi_entry_get_ndn(pre_e);
+ post_dn = slapi_entry_get_ndn(post_e);
+ }
+
+ /* is the entry of interest? */
+ memberof_rlock_config();
+ mainConfig = memberof_get_config();
+ if(pre_dn && post_dn &&
+ !slapi_filter_test_simple(post_e, mainConfig->group_filter))
+ {
+ interested = 1;
+ /* copy config so it doesn't change out from under us */
+ memberof_copy_config(&configCopy, mainConfig);
+ }
+ memberof_unlock_config();
+
+ if(interested)
+ {
+ Slapi_Attr *attr = 0;
+
+ memberof_lock();
+
+ /* get a list of member attributes present in the group
+ * entry that is being renamed. */
+ if(0 == slapi_entry_attr_find(post_e, configCopy.groupattr, &attr))
+ {
+ memberof_moddn_attr_list(pb, &configCopy, pre_dn, post_dn, attr);
+ }
+
+ /* modrdn must change the dns in groups that have
+ * this group as a member.
+ */
+ memberof_replace_dn_from_groups(pb, &configCopy, pre_dn, post_dn);
+
+ memberof_unlock();
+
+ memberof_free_config(&configCopy);
+ }
+ }
+
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "<-- memberof_postop_modrdn\n" );
+ return ret;
+}
+
+typedef struct _replace_dn_data
+{
+ char *pre_dn;
+ char *post_dn;
+ char *type;
+} replace_dn_data;
+
+int memberof_replace_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *pre_dn, char *post_dn)
+{
+ replace_dn_data data = {pre_dn, post_dn, config->groupattr};
+
+ return memberof_call_foreach_dn(pb, pre_dn, config->groupattr,
+ memberof_replace_dn_type_callback, &data);
+}
+
+
+int memberof_replace_dn_type_callback(Slapi_Entry *e, void *callback_data)
+{
+ int rc = 0;
+ LDAPMod delmod;
+ LDAPMod addmod;
+ LDAPMod *mods[3];
+ char *delval[2];
+ char *addval[2];
+ Slapi_PBlock *mod_pb = 0;
+
+ mod_pb = slapi_pblock_new();
+
+ mods[0] = &delmod;
+ mods[1] = &addmod;
+ mods[2] = 0;
+
+ delval[0] = ((replace_dn_data *)callback_data)->pre_dn;
+ delval[1] = 0;
+
+ delmod.mod_op = LDAP_MOD_DELETE;
+ delmod.mod_type = ((replace_dn_data *)callback_data)->type;
+ delmod.mod_values = delval;
+
+ addval[0] = ((replace_dn_data *)callback_data)->post_dn;
+ addval[1] = 0;
+
+ addmod.mod_op = LDAP_MOD_ADD;
+ addmod.mod_type = ((replace_dn_data *)callback_data)->type;
+ addmod.mod_values = addval;
+
+ slapi_modify_internal_set_pb(
+ mod_pb, slapi_entry_get_dn(e),
+ mods, 0, 0,
+ memberof_get_plugin_id(), 0);
+
+ slapi_modify_internal_pb(mod_pb);
+
+ slapi_pblock_get(mod_pb,
+ SLAPI_PLUGIN_INTOP_RESULT,
+ &rc);
+
+ slapi_pblock_destroy(mod_pb);
+
+ return rc;
+}
+
+/*
+ * memberof_postop_modify()
+ *
+ * Added members are retrieved and have the group DN added to their memberOf attribute
+ * Deleted members are retrieved and have the group DN deleted from their memberOf attribute
+ * On replace of the membership attribute values:
+ * 1. Sort old and new values
+ * 2. Iterate through both lists at same time
+ * 3. Any value not in old list but in new list - add group DN to memberOf attribute
+ * 4. Any value in old list but not in new list - remove group DN from memberOf attribute
+ *
+ * Note: this will suck for large groups but nonetheless is optimal (it's linear) given
+ * current restrictions i.e. originally adding members in sorted order would allow
+ * us to sort one list only (the new one) but that is under server control, not this plugin
+ */
+int memberof_postop_modify(Slapi_PBlock *pb)
+{
+ int ret = 0;
+ char *dn = 0;
+ Slapi_Mods *smods = 0;
+ Slapi_Mod *smod = 0;
+ LDAPMod **mods;
+ Slapi_Mod *next_mod = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "--> memberof_postop_modify\n" );
+
+ if(memberof_oktodo(pb) &&
+ (dn = memberof_getdn(pb)))
+ {
+ int config_copied = 0;
+ MemberOfConfig *mainConfig = 0;
+ MemberOfConfig configCopy = {0, 0, 0, 0};
+
+ /* get the mod set */
+ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+ smods = slapi_mods_new();
+ slapi_mods_init_byref(smods, mods);
+
+ next_mod = slapi_mod_new();
+ smod = slapi_mods_get_first_smod(smods, next_mod);
+ while(smod)
+ {
+ int interested = 0;
+ char *type = (char *)slapi_mod_get_type(smod);
+
+ /* We only want to copy the config if we encounter an
+ * operation that we need to act on. We also want to
+ * only copy the config the first time it's needed so
+ * it remains the same for all mods in the operation,
+ * despite any config changes that may be made. */
+ if (!config_copied)
+ {
+ memberof_rlock_config();
+ mainConfig = memberof_get_config();
+
+ if(slapi_attr_types_equivalent(type, mainConfig->groupattr))
+ {
+ interested = 1;
+ /* copy config so it doesn't change out from under us */
+ memberof_copy_config(&configCopy, mainConfig);
+ config_copied = 1;
+ }
+
+ memberof_unlock_config();
+ } else {
+ if(slapi_attr_types_equivalent(type, configCopy.groupattr))
+ {
+ interested = 1;
+ }
+ }
+
+ if(interested)
+ {
+ int op = slapi_mod_get_operation(smod);
+
+ memberof_lock();
+
+ /* the modify op decides the function */
+ switch(op & ~LDAP_MOD_BVALUES)
+ {
+ case LDAP_MOD_ADD:
+ {
+ /* add group DN to targets */
+ memberof_add_smod_list(pb, &configCopy, dn, smod);
+ break;
+ }
+
+ case LDAP_MOD_DELETE:
+ {
+ /* If there are no values in the smod, we should
+ * just do a replace instead. The user is just
+ * trying to delete all members from this group
+ * entry, which the replace code deals with. */
+ if (slapi_mod_get_num_values(smod) == 0)
+ {
+ memberof_replace_list(pb, &configCopy, dn);
+ }
+ else
+ {
+ /* remove group DN from target values in smod*/
+ memberof_del_smod_list(pb, &configCopy, dn, smod);
+ }
+ break;
+ }
+
+ case LDAP_MOD_REPLACE:
+ {
+ /* replace current values */
+ memberof_replace_list(pb, &configCopy, dn);
+ break;
+ }
+
+ default:
+ {
+ slapi_log_error(
+ SLAPI_LOG_PLUGIN,
+ MEMBEROF_PLUGIN_SUBSYSTEM,
+ "memberof_postop_modify: unknown mod type\n" );
+ break;
+ }
+ }
+
+ memberof_unlock();
+ }
+
+ slapi_mod_done(next_mod);
+ smod = slapi_mods_get_next_smod(smods, next_mod);
+ }
+
+ if (config_copied)
+ {
+ memberof_free_config(&configCopy);
+ }
+
+ slapi_mod_free(&next_mod);
+ slapi_mods_free(&smods);
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "<-- memberof_postop_modify\n" );
+ return ret;
+}
+
+
+/*
+ * memberof_postop_add()
+ *
+ * All members in the membership attribute of the new entry get retrieved
+ * and have the group DN added to their memberOf attribute
+ */
+int memberof_postop_add(Slapi_PBlock *pb)
+{
+ int ret = 0;
+ int interested = 0;
+ char *dn = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "--> memberof_postop_add\n" );
+
+ if(memberof_oktodo(pb) && (dn = memberof_getdn(pb)))
+ {
+ MemberOfConfig *mainConfig = 0;
+ MemberOfConfig configCopy = {0, 0, 0, 0};
+ struct slapi_entry *e = NULL;
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_POST_OP, &e );
+
+
+ /* is the entry of interest? */
+ memberof_rlock_config();
+ mainConfig = memberof_get_config();
+ if(e && !slapi_filter_test_simple(e, mainConfig->group_filter))
+ {
+ interested = 1;
+ /* copy config so it doesn't change out from under us */
+ memberof_copy_config(&configCopy, mainConfig);
+ }
+ memberof_unlock_config();
+
+ if(interested)
+ {
+ Slapi_Attr *attr = 0;
+
+ memberof_lock();
+
+ if(0 == slapi_entry_attr_find(e, configCopy.groupattr, &attr))
+ {
+ memberof_add_attr_list(pb, &configCopy, dn, attr);
+ }
+
+ memberof_unlock();
+
+ memberof_free_config(&configCopy);
+ }
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "<-- memberof_postop_add\n" );
+ return ret;
+}
+
+/*** Support functions ***/
+
+/*
+ * memberof_oktodo()
+ *
+ * Check that the op succeeded
+ * Note: we also respond to replicated ops so we don't test for that
+ * this does require that the memberOf attribute not be replicated
+ * and this means that memberof is consistent with local state
+ * not the network system state
+ *
+ */
+int memberof_oktodo(Slapi_PBlock *pb)
+{
+ int ret = 1;
+ int oprc = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "--> memberof_postop_oktodo\n" );
+
+ if(slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "memberof_postop_oktodo: could not get parameters\n" );
+ ret = -1;
+ }
+
+ /* this plugin should only execute if the operation succeeded
+ */
+ if(oprc != 0)
+ {
+ ret = 0;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "<-- memberof_postop_oktodo\n" );
+
+ return ret;
+}
+
+/*
+ * memberof_getdn()
+ *
+ * Get dn of target entry
+ *
+ */
+char *memberof_getdn(Slapi_PBlock *pb)
+{
+ char *dn = 0;
+
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+
+ return dn;
+}
+
+/*
+ * memberof_modop_one()
+ *
+ * Perform op on memberof attribute of op_to using op_this as the value
+ * However, if op_to happens to be a group, we must arrange for the group
+ * members to have the mod performed on them instead, and we must take
+ * care to not recurse when we have visted a group before
+ *
+ * Also, we must not delete entries that are a member of the group
+ */
+int memberof_modop_one(Slapi_PBlock *pb, MemberOfConfig *config, int mod_op,
+ char *op_this, char *op_to)
+{
+ return memberof_modop_one_r(pb, config, mod_op, op_this, op_this, op_to, 0);
+}
+
+/* memberof_modop_one_r()
+ *
+ * recursive function to perform above (most things don't need the replace arg)
+ */
+
+int memberof_modop_one_r(Slapi_PBlock *pb, MemberOfConfig *config, int mod_op,
+ char *group_dn, char *op_this, char *op_to, memberofstringll *stack)
+{
+ return memberof_modop_one_replace_r(
+ pb, config, mod_op, group_dn, op_this, 0, op_to, stack);
+}
+
+/* memberof_modop_one_replace_r()
+ *
+ * recursive function to perform above (with added replace arg)
+ */
+int memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config,
+ int mod_op, char *group_dn, char *op_this, char *replace_with,
+ char *op_to, memberofstringll *stack)
+{
+ int rc = 0;
+ LDAPMod mod;
+ LDAPMod replace_mod;
+ LDAPMod *mods[3];
+ char *val[2];
+ char *replace_val[2];
+ Slapi_PBlock *mod_pb = 0;
+ char *attrlist[2] = {config->groupattr,0};
+ Slapi_DN *op_to_sdn = 0;
+ Slapi_Entry *e = 0;
+ memberofstringll *ll = 0;
+ char *op_str = 0;
+ Slapi_Value *to_dn_val = slapi_value_new_string(op_to);
+ Slapi_Value *this_dn_val = slapi_value_new_string(op_this);
+
+ /* determine if this is a group op or single entry */
+ op_to_sdn = slapi_sdn_new_dn_byref(op_to);
+ slapi_search_internal_get_entry( op_to_sdn, attrlist,
+ &e, memberof_get_plugin_id());
+ if(!e)
+ {
+ /* In the case of a delete, we need to worry about the
+ * missing entry being a nested group. There's a small
+ * window where another thread may have deleted a nested
+ * group that our group_dn entry refers to. This has the
+ * potential of us missing some indirect member entries
+ * that need to be updated. */
+ if(LDAP_MOD_DELETE == mod_op)
+ {
+ Slapi_PBlock *search_pb = slapi_pblock_new();
+ Slapi_DN *base_sdn = 0;
+ Slapi_Backend *be = 0;
+ char *filter_str = 0;
+ int n_entries = 0;
+
+ /* We can't tell for sure if the op_to entry is a
+ * user or a group since the entry doesn't exist
+ * anymore. We can safely ignore the missing entry
+ * if no other entries have a memberOf attribute that
+ * points to the missing entry. */
+ be = slapi_be_select(op_to_sdn);
+ if(be)
+ {
+ base_sdn = (Slapi_DN*)slapi_be_getsuffix(be,0);
+ }
+
+ if(base_sdn)
+ {
+ filter_str = slapi_ch_smprintf("(%s=%s)",
+ config->memberof_attr, op_to);
+ }
+
+ if(filter_str)
+ {
+ slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(base_sdn),
+ LDAP_SCOPE_SUBTREE, filter_str, 0, 0, 0, 0,
+ memberof_get_plugin_id(), 0);
+
+ if (slapi_search_internal_pb(search_pb))
+ {
+ /* get result and log an error */
+ int res = 0;
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+ slapi_log_error( SLAPI_LOG_FATAL, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "memberof_modop_one_replace_r: error searching for members: "
+ "%d", res);
+ } else {
+ slapi_pblock_get(search_pb, SLAPI_NENTRIES, &n_entries);
+
+ if(n_entries > 0)
+ {
+ /* We want to fixup the membership for the
+ * entries that referred to the missing group
+ * entry. This will fix the references to
+ * the missing group as well as the group
+ * represented by op_this. */
+ memberof_test_membership(pb, config, op_to);
+ }
+ }
+
+ slapi_free_search_results_internal(search_pb);
+ slapi_ch_free_string(&filter_str);
+ }
+
+ slapi_pblock_destroy(search_pb);
+ }
+
+ goto bail;
+ }
+
+ if(LDAP_MOD_DELETE == mod_op)
+ {
+ op_str = "DELETE";
+ }
+ else if(LDAP_MOD_ADD == mod_op)
+ {
+ op_str = "ADD";
+ }
+ else if(LDAP_MOD_REPLACE == mod_op)
+ {
+ op_str = "REPLACE";
+ }
+ else
+ {
+ op_str = "UNKNOWN";
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "memberof_modop_one_replace_r: %s %s in %s\n"
+ ,op_str, op_this, op_to);
+
+ if(!slapi_filter_test_simple(e, config->group_filter))
+ {
+ /* group */
+ Slapi_Value *ll_dn_val = 0;
+ Slapi_Attr *members = 0;
+
+ ll = stack;
+
+ /* have we been here before? */
+ while(ll)
+ {
+ ll_dn_val = slapi_value_new_string(ll->dn);
+
+ if(0 == memberof_compare(config, &ll_dn_val, &to_dn_val))
+ {
+ slapi_value_free(&ll_dn_val);
+
+ /* someone set up infinitely
+ recursive groups - bail out */
+ slapi_log_error( SLAPI_LOG_PLUGIN,
+ MEMBEROF_PLUGIN_SUBSYSTEM,
+ "memberof_modop_one_replace_r: group recursion"
+ " detected in %s\n"
+ ,op_to);
+ goto bail;
+ }
+
+ slapi_value_free(&ll_dn_val);
+ ll = ll->next;
+ }
+
+ /* do op on group */
+ slapi_log_error( SLAPI_LOG_PLUGIN,
+ MEMBEROF_PLUGIN_SUBSYSTEM,
+ "memberof_modop_one_replace_r: descending into group %s\n",
+ op_to);
+ /* Add the nested group's DN to the stack so we can detect loops later. */
+ ll = (memberofstringll*)slapi_ch_malloc(sizeof(memberofstringll));
+ ll->dn = op_to;
+ ll->next = stack;
+
+ slapi_entry_attr_find( e, config->groupattr, &members );
+ if(members)
+ {
+ memberof_mod_attr_list_r(pb, config, mod_op, group_dn, op_this, members, ll);
+ }
+
+ {
+ /* crazyness follows:
+ * strict-aliasing doesn't like the required cast
+ * to void for slapi_ch_free so we are made to
+ * juggle to get a normal thing done
+ */
+ void *pll = ll;
+ slapi_ch_free(&pll);
+ ll = 0;
+ }
+ }
+ /* continue with operation */
+ {
+ /* We want to avoid listing a group as a memberOf itself
+ * in case someone set up a circular grouping.
+ */
+ if (0 == memberof_compare(config, &this_dn_val, &to_dn_val))
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN,
+ MEMBEROF_PLUGIN_SUBSYSTEM,
+ "memberof_modop_one_replace_r: not processing memberOf "
+ "operations on self entry: %s\n", this_dn_val);
+ goto bail;
+ }
+
+ /* For add and del modify operations, we just regenerate the
+ * memberOf attribute. */
+ if(LDAP_MOD_DELETE == mod_op || LDAP_MOD_ADD == mod_op)
+ {
+ /* find parent groups and replace our member attr */
+ memberof_fix_memberof_callback(e, config);
+ } else {
+ /* single entry - do mod */
+ mod_pb = slapi_pblock_new();
+
+ mods[0] = &mod;
+ if(LDAP_MOD_REPLACE == mod_op)
+ {
+ mods[1] = &replace_mod;
+ mods[2] = 0;
+ }
+ else
+ {
+ mods[1] = 0;
+ }
+
+ val[0] = op_this;
+ val[1] = 0;
+ mod.mod_op = LDAP_MOD_REPLACE == mod_op?LDAP_MOD_DELETE:mod_op;
+ mod.mod_type = config->memberof_attr;
+ mod.mod_values = val;
+
+ if(LDAP_MOD_REPLACE == mod_op)
+ {
+ replace_val[0] = replace_with;
+ replace_val[1] = 0;
+
+ replace_mod.mod_op = LDAP_MOD_ADD;
+ replace_mod.mod_type = config->memberof_attr;
+ replace_mod.mod_values = replace_val;
+ }
+
+ slapi_modify_internal_set_pb(
+ mod_pb, op_to,
+ mods, 0, 0,
+ memberof_get_plugin_id(), 0);
+
+ slapi_modify_internal_pb(mod_pb);
+
+ slapi_pblock_get(mod_pb,
+ SLAPI_PLUGIN_INTOP_RESULT,
+ &rc);
+
+ slapi_pblock_destroy(mod_pb);
+ }
+ }
+
+bail:
+ slapi_sdn_free(&op_to_sdn);
+ slapi_value_free(&to_dn_val);
+ slapi_value_free(&this_dn_val);
+ slapi_entry_free(e);
+ return rc;
+}
+
+
+/*
+ * memberof_add_one()
+ *
+ * Add addthis DN to the memberof attribute of addto
+ *
+ */
+int memberof_add_one(Slapi_PBlock *pb, MemberOfConfig *config, char *addthis, char *addto)
+{
+ return memberof_modop_one(pb, config, LDAP_MOD_ADD, addthis, addto);
+}
+
+/*
+ * memberof_del_one()
+ *
+ * Delete delthis DN from the memberof attribute of delfrom
+ *
+ */
+int memberof_del_one(Slapi_PBlock *pb, MemberOfConfig *config, char *delthis, char *delfrom)
+{
+ return memberof_modop_one(pb, config, LDAP_MOD_DELETE, delthis, delfrom);
+}
+
+/*
+ * memberof_mod_smod_list()
+ *
+ * Perform mod for group DN to the memberof attribute of the list of targets
+ *
+ */
+int memberof_mod_smod_list(Slapi_PBlock *pb, MemberOfConfig *config, int mod,
+ char *group_dn, Slapi_Mod *smod)
+{
+ int rc = 0;
+ struct berval *bv = slapi_mod_get_first_value(smod);
+ int last_size = 0;
+ char *last_str = 0;
+
+ while(bv)
+ {
+ char *dn_str = 0;
+
+ if(last_size > bv->bv_len)
+ {
+ dn_str = last_str;
+ }
+ else
+ {
+ int the_size = (bv->bv_len * 2) + 1;
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ dn_str = (char*)slapi_ch_malloc(the_size);
+
+ last_str = dn_str;
+ last_size = the_size;
+ }
+
+ memset(dn_str, 0, last_size);
+
+ strncpy(dn_str, bv->bv_val, (size_t)bv->bv_len);
+
+ memberof_modop_one(pb, config, mod, group_dn, dn_str);
+
+ bv = slapi_mod_get_next_value(smod);
+ }
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ return rc;
+}
+
+/*
+ * memberof_add_smod_list()
+ *
+ * Add group DN to the memberof attribute of the list of targets
+ *
+ */
+int memberof_add_smod_list(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *groupdn, Slapi_Mod *smod)
+{
+ return memberof_mod_smod_list(pb, config, LDAP_MOD_ADD, groupdn, smod);
+}
+
+
+/*
+ * memberof_del_smod_list()
+ *
+ * Remove group DN from the memberof attribute of the list of targets
+ *
+ */
+int memberof_del_smod_list(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *groupdn, Slapi_Mod *smod)
+{
+ return memberof_mod_smod_list(pb, config, LDAP_MOD_DELETE, groupdn, smod);
+}
+
+/**
+ * Plugin identity mgmt
+ */
+void memberof_set_plugin_id(void * plugin_id)
+{
+ _PluginID=plugin_id;
+}
+
+void * memberof_get_plugin_id()
+{
+ return _PluginID;
+}
+
+
+/*
+ * memberof_mod_attr_list()
+ *
+ * Perform mod for group DN to the memberof attribute of the list of targets
+ *
+ */
+int memberof_mod_attr_list(Slapi_PBlock *pb, MemberOfConfig *config, int mod,
+ char *group_dn, Slapi_Attr *attr)
+{
+ return memberof_mod_attr_list_r(pb, config, mod, group_dn, group_dn, attr, 0);
+}
+
+int memberof_mod_attr_list_r(Slapi_PBlock *pb, MemberOfConfig *config, int mod,
+ char *group_dn, char *op_this, Slapi_Attr *attr, memberofstringll *stack)
+{
+ int rc = 0;
+ Slapi_Value *val = 0;
+ Slapi_Value *op_this_val = 0;
+ int last_size = 0;
+ char *last_str = 0;
+ int hint = slapi_attr_first_value(attr, &val);
+
+ op_this_val = slapi_value_new_string(op_this);
+
+ while(val)
+ {
+ char *dn_str = 0;
+ struct berval *bv = 0;
+
+ /* We don't want to process a memberOf operation on ourselves. */
+ if(0 != memberof_compare(config, &val, &op_this_val))
+ {
+ bv = (struct berval *)slapi_value_get_berval(val);
+
+ if(last_size > bv->bv_len)
+ {
+ dn_str = last_str;
+ }
+ else
+ {
+ int the_size = (bv->bv_len * 2) + 1;
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ dn_str = (char*)slapi_ch_malloc(the_size);
+
+ last_str = dn_str;
+ last_size = the_size;
+ }
+
+ memset(dn_str, 0, last_size);
+
+ strncpy(dn_str, bv->bv_val, (size_t)bv->bv_len);
+
+ /* If we're doing a replace (as we would in the MODRDN case), we need
+ * to specify the new group DN value */
+ if(mod == LDAP_MOD_REPLACE)
+ {
+ memberof_modop_one_replace_r(pb, config, mod, group_dn, op_this,
+ group_dn, dn_str, stack);
+ }
+ else
+ {
+ memberof_modop_one_r(pb, config, mod, group_dn, op_this, dn_str, stack);
+ }
+ }
+
+ hint = slapi_attr_next_value(attr, hint, &val);
+ }
+
+ slapi_value_free(&op_this_val);
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ return rc;
+}
+
+/*
+ * memberof_add_attr_list()
+ *
+ * Add group DN to the memberof attribute of the list of targets
+ *
+ */
+int memberof_add_attr_list(Slapi_PBlock *pb, MemberOfConfig *config, char *groupdn,
+ Slapi_Attr *attr)
+{
+ return memberof_mod_attr_list(pb, config, LDAP_MOD_ADD, groupdn, attr);
+}
+
+/*
+ * memberof_del_attr_list()
+ *
+ * Remove group DN from the memberof attribute of the list of targets
+ *
+ */
+int memberof_del_attr_list(Slapi_PBlock *pb, MemberOfConfig *config, char *groupdn,
+ Slapi_Attr *attr)
+{
+ return memberof_mod_attr_list(pb, config, LDAP_MOD_DELETE, groupdn, attr);
+}
+
+/*
+ * memberof_moddn_attr_list()
+ *
+ * Perform mod for group DN to the memberof attribute of the list of targets
+ *
+ */
+int memberof_moddn_attr_list(Slapi_PBlock *pb, MemberOfConfig *config,
+ char *pre_dn, char *post_dn, Slapi_Attr *attr)
+{
+ int rc = 0;
+ Slapi_Value *val = 0;
+ int last_size = 0;
+ char *last_str = 0;
+ int hint = slapi_attr_first_value(attr, &val);
+
+ while(val)
+ {
+ char *dn_str = 0;
+ struct berval *bv = (struct berval *)slapi_value_get_berval(val);
+
+ if(last_size > bv->bv_len)
+ {
+ dn_str = last_str;
+ }
+ else
+ {
+ int the_size = (bv->bv_len * 2) + 1;
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ dn_str = (char*)slapi_ch_malloc(the_size);
+
+ last_str = dn_str;
+ last_size = the_size;
+ }
+
+ memset(dn_str, 0, last_size);
+
+ strncpy(dn_str, bv->bv_val, (size_t)bv->bv_len);
+
+ memberof_modop_one_replace_r(pb, config, LDAP_MOD_REPLACE,
+ post_dn, pre_dn, post_dn, dn_str, 0);
+
+ hint = slapi_attr_next_value(attr, hint, &val);
+ }
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ return rc;
+}
+
+/* memberof_get_groups()
+ *
+ * Gets a list of all groups that an entry is a member of.
+ * This is done by looking only at member attribute values.
+ * A Slapi_ValueSet* is returned. It is up to the caller to
+ * free it.
+ */
+Slapi_ValueSet *memberof_get_groups(MemberOfConfig *config, char *memberdn)
+{
+ Slapi_Value *memberdn_val = slapi_value_new_string(memberdn);
+ Slapi_ValueSet *groupvals = slapi_valueset_new();
+ memberof_get_groups_data data = {config, memberdn_val, &groupvals};
+
+ memberof_get_groups_r(config, memberdn, &data);
+
+ slapi_value_free(&memberdn_val);
+
+ return groupvals;
+}
+
+int memberof_get_groups_r(MemberOfConfig *config, char *memberdn, memberof_get_groups_data *data)
+{
+ /* Search for member=<memberdn>
+ * For each match, add it to the list, recurse and do same search */
+ return memberof_call_foreach_dn(NULL, memberdn, config->groupattr,
+ memberof_get_groups_callback, data);
+}
+
+/* memberof_get_groups_callback()
+ *
+ * Callback to perform work of memberof_get_groups()
+ */
+int memberof_get_groups_callback(Slapi_Entry *e, void *callback_data)
+{
+ char *group_dn = slapi_entry_get_dn(e);
+ Slapi_Value *group_dn_val = 0;
+ Slapi_ValueSet *groupvals = *((memberof_get_groups_data*)callback_data)->groupvals;
+
+ /* get the DN of the group */
+ group_dn_val = slapi_value_new_string(group_dn);
+
+ /* check if e is the same as our original member entry */
+ if (0 == memberof_compare(((memberof_get_groups_data*)callback_data)->config,
+ &((memberof_get_groups_data*)callback_data)->memberdn_val, &group_dn_val))
+ {
+ /* A recursive group caused us to find our original
+ * entry we passed to memberof_get_groups(). We just
+ * skip processing this entry. */
+ slapi_log_error( SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "memberof_get_groups_callback: group recursion"
+ " detected in %s\n" ,group_dn);
+ slapi_value_free(&group_dn_val);
+ goto bail;
+
+ }
+
+ /* have we been here before? */
+ if (groupvals &&
+ slapi_valueset_find(((memberof_get_groups_data*)callback_data)->config->group_slapiattr,
+ groupvals, group_dn_val))
+ {
+ /* we either hit a recursive grouping, or an entry is
+ * a member of a group through multiple paths. Either
+ * way, we can just skip processing this entry since we've
+ * already gone through this part of the grouping hierarchy. */
+ slapi_log_error( SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "memberof_get_groups_callback: possible group recursion"
+ " detected in %s\n" ,group_dn);
+ slapi_value_free(&group_dn_val);
+ goto bail;
+ }
+
+ /* Push group_dn_val into the valueset. This memory is now owned
+ * by the valueset. */
+ slapi_valueset_add_value_ext(groupvals, group_dn_val, SLAPI_VALUE_FLAG_PASSIN);
+
+ /* now recurse to find parent groups of e */
+ memberof_get_groups_r(((memberof_get_groups_data*)callback_data)->config,
+ group_dn, callback_data);
+
+ bail:
+ return 0;
+}
+
+/* memberof_is_direct_member()
+ *
+ * tests for direct membership of memberdn in group groupdn
+ * returns non-zero when true, zero otherwise
+ */
+int memberof_is_direct_member(MemberOfConfig *config, Slapi_Value *groupdn,
+ Slapi_Value *memberdn)
+{
+ int rc = 0;
+ Slapi_DN *sdn = 0;
+ char *attrlist[2] = {config->groupattr,0};
+ Slapi_Entry *group_e = 0;
+ Slapi_Attr *attr = 0;
+
+ sdn = slapi_sdn_new_dn_byref(slapi_value_get_string(groupdn));
+
+ slapi_search_internal_get_entry(sdn, attrlist,
+ &group_e, memberof_get_plugin_id());
+
+ if(group_e)
+ {
+ slapi_entry_attr_find(group_e, config->groupattr, &attr );
+ if(attr)
+ {
+ rc = 0 == slapi_attr_value_find(
+ attr, slapi_value_get_berval(memberdn));
+ }
+ slapi_entry_free(group_e);
+ }
+
+ slapi_sdn_free(&sdn);
+ return rc;
+}
+
+/* memberof_test_membership()
+ *
+ * Finds all entries who are a "memberOf" the group
+ * represented by "group_dn". For each matching entry, we
+ * call memberof_test_membership_callback().
+ *
+ * for each attribute in the memberof attribute
+ * determine if the entry is still a member.
+ *
+ * test each for direct membership
+ * move groups entry is memberof to member group
+ * test remaining groups for membership in member groups
+ * iterate until a pass fails to move a group over to member groups
+ * remaining groups should be deleted
+ */
+int memberof_test_membership(Slapi_PBlock *pb, MemberOfConfig *config, char *group_dn)
+{
+ return memberof_call_foreach_dn(pb, group_dn, config->memberof_attr,
+ memberof_test_membership_callback , config);
+}
+
+/*
+ * memberof_test_membership_callback()
+ *
+ * A callback function to do the work of memberof_test_membership().
+ * Note that this not only tests membership, but updates the memberOf
+ * attributes in the entry to be correct.
+ */
+int memberof_test_membership_callback(Slapi_Entry *e, void *callback_data)
+{
+ int rc = 0;
+ Slapi_Attr *attr = 0;
+ int total = 0;
+ Slapi_Value **member_array = 0;
+ Slapi_Value **candidate_array = 0;
+ Slapi_Value *entry_dn = 0;
+ MemberOfConfig *config = (MemberOfConfig *)callback_data;
+
+ entry_dn = slapi_value_new_string(slapi_entry_get_dn(e));
+
+ if(0 == entry_dn)
+ {
+ goto bail;
+ }
+
+ /* divide groups into member and non-member lists */
+ slapi_entry_attr_find(e, config->memberof_attr, &attr );
+ if(attr)
+ {
+ slapi_attr_get_numvalues( attr, &total);
+ if(total)
+ {
+ Slapi_Value *val = 0;
+ int hint = 0;
+ int c_index = 0;
+ int m_index = 0;
+ int member_found = 1;
+ int outer_index = 0;
+
+ candidate_array =
+ (Slapi_Value**)
+ slapi_ch_malloc(sizeof(Slapi_Value*)*total);
+ memset(candidate_array, 0, sizeof(Slapi_Value*)*total);
+ member_array =
+ (Slapi_Value**)
+ slapi_ch_malloc(sizeof(Slapi_Value*)*total);
+ memset(member_array, 0, sizeof(Slapi_Value*)*total);
+
+ hint = slapi_attr_first_value(attr, &val);
+
+ while(val)
+ {
+ /* test for direct membership */
+ if(memberof_is_direct_member(config, val, entry_dn))
+ {
+ /* it is a member */
+ member_array[m_index] = val;
+ m_index++;
+ }
+ else
+ {
+ /* not a member, still a candidate */
+ candidate_array[c_index] = val;
+ c_index++;
+ }
+
+ hint = slapi_attr_next_value(attr, hint, &val);
+ }
+
+ /* now iterate over members testing for membership
+ in candidate groups and moving candidates to members
+ when successful, quit when a full iteration adds no
+ new members
+ */
+ while(member_found)
+ {
+ member_found = 0;
+
+ /* For each group that this entry is a verified member of, see if
+ * any of the candidate groups are members. If they are, add them
+ * to the list of verified groups that this entry is a member of.
+ */
+ while(outer_index < m_index)
+ {
+ int inner_index = 0;
+
+ while(inner_index < c_index)
+ {
+ /* Check for a special value in this position
+ * that indicates that the candidate was moved
+ * to the member array. */
+ if((void*)1 ==
+ candidate_array[inner_index])
+ {
+ /* was moved, skip */
+ inner_index++;
+ continue;
+ }
+
+ if(memberof_is_direct_member(
+ config,
+ candidate_array[inner_index],
+ member_array[outer_index]))
+ {
+ member_array[m_index] =
+ candidate_array
+ [inner_index];
+ m_index++;
+
+ candidate_array[inner_index] =
+ (void*)1;
+
+ member_found = 1;
+ }
+
+ inner_index++;
+ }
+
+ outer_index++;
+ }
+ }
+
+ /* here we are left only with values to delete
+ from the memberof attribute in the candidate list
+ */
+ outer_index = 0;
+ while(outer_index < c_index)
+ {
+ /* Check for a special value in this position
+ * that indicates that the candidate was moved
+ * to the member array. */
+ if((void*)1 == candidate_array[outer_index])
+ {
+ /* item moved, skip */
+ outer_index++;
+ continue;
+ }
+
+ memberof_del_one(
+ 0, config,
+ (char*)slapi_value_get_string(
+ candidate_array[outer_index]),
+ (char*)slapi_value_get_string(entry_dn));
+
+ outer_index++;
+ }
+ {
+ /* crazyness follows:
+ * strict-aliasing doesn't like the required cast
+ * to void for slapi_ch_free so we are made to
+ * juggle to get a normal thing done
+ */
+ void *pmember_array = member_array;
+ void *pcandidate_array = candidate_array;
+ slapi_ch_free(&pcandidate_array);
+ slapi_ch_free(&pmember_array);
+ candidate_array = 0;
+ member_array = 0;
+ }
+ }
+ }
+
+bail:
+ slapi_value_free(&entry_dn);
+
+ return rc;
+}
+
+/*
+ * memberof_replace_list()
+ *
+ * Perform replace the group DN list in the memberof attribute of the list of targets
+ *
+ */
+int memberof_replace_list(Slapi_PBlock *pb, MemberOfConfig *config, char *group_dn)
+{
+ struct slapi_entry *pre_e = NULL;
+ struct slapi_entry *post_e = NULL;
+ Slapi_Attr *pre_attr = 0;
+ Slapi_Attr *post_attr = 0;
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &pre_e );
+ slapi_pblock_get( pb, SLAPI_ENTRY_POST_OP, &post_e );
+
+ if(pre_e && post_e)
+ {
+ slapi_entry_attr_find( pre_e, config->groupattr, &pre_attr );
+ slapi_entry_attr_find( post_e, config->groupattr, &post_attr );
+ }
+
+ if(pre_attr || post_attr)
+ {
+ int pre_total = 0;
+ int post_total = 0;
+ Slapi_Value **pre_array = 0;
+ Slapi_Value **post_array = 0;
+ int pre_index = 0;
+ int post_index = 0;
+
+ /* create arrays of values */
+ if(pre_attr)
+ {
+ slapi_attr_get_numvalues( pre_attr, &pre_total);
+ }
+
+ if(post_attr)
+ {
+ slapi_attr_get_numvalues( post_attr, &post_total);
+ }
+
+ /* Stash a plugin global pointer here and have memberof_qsort_compare
+ * use it. We have to do this because we use memberof_qsort_compare
+ * as the comparator function for qsort, which requires the function
+ * to only take two void* args. This is thread-safe since we only
+ * store and use the pointer while holding the memberOf operation
+ * lock. */
+ qsortConfig = config;
+
+ if(pre_total)
+ {
+ pre_array =
+ (Slapi_Value**)
+ slapi_ch_malloc(sizeof(Slapi_Value*)*pre_total);
+ memberof_load_array(pre_array, pre_attr);
+ qsort(
+ pre_array,
+ pre_total,
+ sizeof(Slapi_Value*),
+ memberof_qsort_compare);
+ }
+
+ if(post_total)
+ {
+ post_array =
+ (Slapi_Value**)
+ slapi_ch_malloc(sizeof(Slapi_Value*)*post_total);
+ memberof_load_array(post_array, post_attr);
+ qsort(
+ post_array,
+ post_total,
+ sizeof(Slapi_Value*),
+ memberof_qsort_compare);
+ }
+
+ qsortConfig = 0;
+
+
+ /* work through arrays, following these rules:
+ in pre, in post, do nothing
+ in pre, not in post, delete from entry
+ not in pre, in post, add to entry
+ */
+ while(pre_index < pre_total || post_index < post_total)
+ {
+ if(pre_index == pre_total)
+ {
+ /* add the rest of post */
+ memberof_add_one(
+ pb, config,
+ group_dn,
+ (char*)slapi_value_get_string(
+ post_array[post_index]));
+
+ post_index++;
+ }
+ else if(post_index == post_total)
+ {
+ /* delete the rest of pre */
+ memberof_del_one(
+ pb, config,
+ group_dn,
+ (char*)slapi_value_get_string(
+ pre_array[pre_index]));
+
+ pre_index++;
+ }
+ else
+ {
+ /* decide what to do */
+ int cmp = memberof_compare(
+ config,
+ &(pre_array[pre_index]),
+ &(post_array[post_index]));
+
+ if(cmp < 0)
+ {
+ /* delete pre array */
+ memberof_del_one(
+ pb, config,
+ group_dn,
+ (char*)slapi_value_get_string(
+ pre_array[pre_index]));
+
+ pre_index++;
+ }
+ else if(cmp > 0)
+ {
+ /* add post array */
+ memberof_add_one(
+ pb, config,
+ group_dn,
+ (char*)slapi_value_get_string(
+ post_array[post_index]));
+
+ post_index++;
+ }
+ else
+ {
+ /* do nothing, advance */
+ pre_index++;
+ post_index++;
+ }
+ }
+ }
+ slapi_ch_free((void **)&pre_array);
+ slapi_ch_free((void **)&post_array);
+ }
+
+ return 0;
+}
+
+/* memberof_load_array()
+ *
+ * put attribute values in array structure
+ */
+void memberof_load_array(Slapi_Value **array, Slapi_Attr *attr)
+{
+ Slapi_Value *val = 0;
+ int hint = slapi_attr_first_value(attr, &val);
+
+ while(val)
+ {
+ *array = val;
+ array++;
+ hint = slapi_attr_next_value(attr, hint, &val);
+ }
+}
+
+/* memberof_compare()
+ *
+ * compare two attr values
+ */
+int memberof_compare(MemberOfConfig *config, const void *a, const void *b)
+{
+ Slapi_Value *val1 = *((Slapi_Value **)a);
+ Slapi_Value *val2 = *((Slapi_Value **)b);
+
+ return slapi_attr_value_cmp(
+ config->group_slapiattr,
+ slapi_value_get_berval(val1),
+ slapi_value_get_berval(val2));
+}
+
+/* memberof_qsort_compare()
+ *
+ * This is a version of memberof_compare that uses a plugin
+ * global copy of the config. We'd prefer to pass in a copy
+ * of config that is local to the running thread, but we can't
+ * do this since qsort is using us as a comparator function.
+ * We should only use this function when using qsort, and only
+ * when the memberOf lock is acquired.
+ */
+int memberof_qsort_compare(const void *a, const void *b)
+{
+ Slapi_Value *val1 = *((Slapi_Value **)a);
+ Slapi_Value *val2 = *((Slapi_Value **)b);
+
+ return slapi_attr_value_cmp(
+ qsortConfig->group_slapiattr,
+ slapi_value_get_berval(val1),
+ slapi_value_get_berval(val2));
+}
+
+void memberof_lock()
+{
+ slapi_lock_mutex(memberof_operation_lock);
+}
+
+void memberof_unlock()
+{
+ slapi_unlock_mutex(memberof_operation_lock);
+}
+
+typedef struct _task_data
+{
+ char *dn;
+ char *filter_str;
+} task_data;
+
+void memberof_fixup_task_thread(void *arg)
+{
+ MemberOfConfig configCopy = {0, 0, 0, 0};
+ Slapi_Task *task = (Slapi_Task *)arg;
+ task_data *td = NULL;
+ int rc = 0;
+
+ /* Fetch our task data from the task */
+ td = (task_data *)slapi_task_get_data(task);
+
+ slapi_task_begin(task, 1);
+ slapi_task_log_notice(task, "Memberof task starts (arg: %s) ...\n",
+ td->filter_str);
+
+ /* We need to get the config lock first. Trying to get the
+ * config lock after we already hold the op lock can cause
+ * a deadlock. */
+ memberof_rlock_config();
+ /* copy config so it doesn't change out from under us */
+ memberof_copy_config(&configCopy, memberof_get_config());
+ memberof_unlock_config();
+
+ /* get the memberOf operation lock */
+ memberof_lock();
+
+ /* do real work */
+ rc = memberof_fix_memberof(&configCopy, td->dn, td->filter_str);
+
+ /* release the memberOf operation lock */
+ memberof_unlock();
+
+ memberof_free_config(&configCopy);
+
+ slapi_task_log_notice(task, "Memberof task finished.");
+ slapi_task_log_status(task, "Memberof task finished.");
+ slapi_task_inc_progress(task);
+
+ /* this will queue the destruction of the task */
+ slapi_task_finish(task, rc);
+}
+
+/* extract a single value from the entry (as a string) -- if it's not in the
+ * entry, the default will be returned (which can be NULL).
+ * you do not need to free anything returned by this.
+ */
+const char *fetch_attr(Slapi_Entry *e, const char *attrname,
+ const char *default_val)
+{
+ Slapi_Attr *attr;
+ Slapi_Value *val = NULL;
+
+ if (slapi_entry_attr_find(e, attrname, &attr) != 0)
+ return default_val;
+ slapi_attr_first_value(attr, &val);
+ return slapi_value_get_string(val);
+}
+
+int memberof_task_add(Slapi_PBlock *pb, Slapi_Entry *e,
+ Slapi_Entry *eAfter, int *returncode, char *returntext,
+ void *arg)
+{
+ PRThread *thread = NULL;
+ int rv = SLAPI_DSE_CALLBACK_OK;
+ task_data *mytaskdata = NULL;
+ Slapi_Task *task = NULL;
+ const char *filter;
+ const char *dn = 0;
+
+ *returncode = LDAP_SUCCESS;
+ /* get arg(s) */
+ if ((dn = fetch_attr(e, "basedn", 0)) == NULL)
+ {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ if ((filter = fetch_attr(e, "filter", "(objectclass=inetuser)")) == NULL)
+ {
+ *returncode = LDAP_OBJECT_CLASS_VIOLATION;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+
+ /* setup our task data */
+ mytaskdata = (task_data*)slapi_ch_malloc(sizeof(task_data));
+ if (mytaskdata == NULL)
+ {
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ goto out;
+ }
+ mytaskdata->dn = slapi_ch_strdup(dn);
+ mytaskdata->filter_str = slapi_ch_strdup(filter);
+
+ /* allocate new task now */
+ task = slapi_new_task(slapi_entry_get_ndn(e));
+
+ /* register our destructor for cleaning up our private data */
+ slapi_task_set_destructor_fn(task, memberof_task_destructor);
+
+ /* Stash a pointer to our data in the task */
+ slapi_task_set_data(task, mytaskdata);
+
+ /* start the sample task as a separate thread */
+ thread = PR_CreateThread(PR_USER_THREAD, memberof_fixup_task_thread,
+ (void *)task, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE);
+ if (thread == NULL)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "unable to create task thread!\n");
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rv = SLAPI_DSE_CALLBACK_ERROR;
+ slapi_task_finish(task, *returncode);
+ } else {
+ rv = SLAPI_DSE_CALLBACK_OK;
+ }
+
+out:
+ return rv;
+}
+
+void
+memberof_task_destructor(Slapi_Task *task)
+{
+ if (task) {
+ task_data *mydata = (task_data *)slapi_task_get_data(task);
+ if (mydata) {
+ slapi_ch_free_string(&mydata->dn);
+ slapi_ch_free_string(&mydata->filter_str);
+ /* Need to cast to avoid a compiler warning */
+ slapi_ch_free((void **)&mydata);
+ }
+ }
+}
+
+int memberof_fix_memberof(MemberOfConfig *config, char *dn, char *filter_str)
+{
+ int rc = 0;
+ Slapi_PBlock *search_pb = slapi_pblock_new();
+
+ slapi_search_internal_set_pb(search_pb, dn,
+ LDAP_SCOPE_SUBTREE, filter_str, 0, 0,
+ 0, 0,
+ memberof_get_plugin_id(),
+ 0);
+
+ rc = slapi_search_internal_callback_pb(search_pb,
+ config,
+ 0, memberof_fix_memberof_callback,
+ 0);
+
+ slapi_pblock_destroy(search_pb);
+
+ return rc;
+}
+
+/* memberof_fix_memberof_callback()
+ * Add initial and/or fix up broken group list in entry
+ *
+ * 1. Remove all present memberOf values
+ * 2. Add direct group membership memberOf values
+ * 3. Add indirect group membership memberOf values
+ */
+int memberof_fix_memberof_callback(Slapi_Entry *e, void *callback_data)
+{
+ int rc = 0;
+ char *dn = slapi_entry_get_dn(e);
+ MemberOfConfig *config = (MemberOfConfig *)callback_data;
+ memberof_del_dn_data del_data = {0, config->memberof_attr};
+ Slapi_ValueSet *groups = 0;
+
+ /* get a list of all of the groups this user belongs to */
+ groups = memberof_get_groups(config, dn);
+
+ /* If we found some groups, replace the existing memberOf attribute
+ * with the found values. */
+ if (groups && slapi_valueset_count(groups))
+ {
+ Slapi_PBlock *mod_pb = slapi_pblock_new();
+ Slapi_Value *val = 0;
+ Slapi_Mod *smod;
+ LDAPMod **mods = (LDAPMod **) slapi_ch_malloc(2 * sizeof(LDAPMod *));
+ int hint = 0;
+
+ /* NGK - need to allocate the smod */
+ smod = slapi_mod_new();
+ slapi_mod_init(smod, 0);
+ slapi_mod_set_operation(smod, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
+ slapi_mod_set_type(smod, config->memberof_attr);
+
+ /* Loop through all of our values and add them to smod */
+ hint = slapi_valueset_first_value(groups, &val);
+ while (val)
+ {
+ /* this makes a copy of the berval */
+ slapi_mod_add_value(smod, slapi_value_get_berval(val));
+ hint = slapi_valueset_next_value(groups, hint, &val);
+ }
+
+ mods[0] = slapi_mod_get_ldapmod_passout(smod);
+ mods[1] = 0;
+
+ slapi_modify_internal_set_pb(
+ mod_pb, dn, mods, 0, 0,
+ memberof_get_plugin_id(), 0);
+
+ slapi_modify_internal_pb(mod_pb);
+
+ slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+
+ ldap_mods_free(mods, 1);
+ slapi_mod_free(&smod);
+ /* NGK - need to free the smod */
+ slapi_pblock_destroy(mod_pb);
+ } else {
+ /* No groups were found, so remove the memberOf attribute
+ * from this entry. */
+ memberof_del_dn_type_callback(e, &del_data);
+ }
+
+ slapi_valueset_free(groups);
+
+ return rc;
+}
+
diff --git a/daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h b/daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h
new file mode 100644
index 000000000..3e7b5cf4b
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h
@@ -0,0 +1,100 @@
+/** 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.
+ *
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/*
+ * ipa-memberof.h - memberOf shared definitions
+ *
+ */
+
+#ifndef _MEMBEROF_H_
+#define _MEMBEROF_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <dirsrv/slapi-plugin.h>
+#include <nspr.h>
+
+/****** secrets *********/
+/*from FDS slapi-private.h
+ * until we get a proper api for access
+ */
+#define SLAPI_DSE_CALLBACK_OK (1)
+#define SLAPI_DSE_CALLBACK_ERROR (-1)
+#define SLAPI_DSE_CALLBACK_DO_NOT_APPLY (0)
+#define SLAPI_DSE_RETURNTEXT_SIZE 512
+#define DSE_FLAG_PREOP 0x0002
+/*********** end secrets **********/
+/*
+ * macros
+ */
+#define MEMBEROF_PLUGIN_SUBSYSTEM "ipa-memberof-plugin" /* used for logging */
+#define MEMBEROF_GROUP_ATTR "member"
+#define MEMBEROF_ATTR "memberOf"
+
+
+/*
+ * structs
+ */
+typedef struct memberofconfig {
+ char *groupattr;
+ char *memberof_attr;
+ Slapi_Filter *group_filter;
+ Slapi_Attr *group_slapiattr;
+} MemberOfConfig;
+
+
+/*
+ * functions
+ */
+int memberof_config(Slapi_Entry *config_e);
+void memberof_copy_config(MemberOfConfig *dest, MemberOfConfig *src);
+void memberof_free_config(MemberOfConfig *config);
+MemberOfConfig *memberof_get_config();
+void memberof_lock();
+void memberof_unlock();
+void memberof_rlock_config();
+void memberof_wlock_config();
+void memberof_unlock_config();
+
+
+#endif /* _MEMBEROF_H_ */
diff --git a/daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c b/daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c
new file mode 100644
index 000000000..b2bd374ad
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c
@@ -0,0 +1,312 @@
+/** 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.
+ *
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/*
+ * memberof_config.c - configuration-related code for memberOf plug-in
+ *
+ */
+
+#include <plstr.h>
+
+#include "ipa-memberof.h"
+
+#define MEMBEROF_CONFIG_FILTER "(objectclass=*)"
+
+/*
+ * The configuration attributes are contained in the plugin entry e.g.
+ * cn=MemberOf Plugin,cn=plugins,cn=config
+ *
+ * Configuration is a two step process. The first pass is a validation step which
+ * occurs pre-op - check inputs and error out if bad. The second pass actually
+ * applies the changes to the run time config.
+ */
+
+
+/*
+ * function prototypes
+ */
+static int memberof_apply_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg);
+static int memberof_search (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+/*
+ * static variables
+ */
+/* This is the main configuration which is updated from dse.ldif. The
+ * config will be copied when it is used by the plug-in to prevent it
+ * being changed out from under a running memberOf operation. */
+static MemberOfConfig theConfig;
+static PRRWLock *memberof_config_lock = 0;
+static int inited = 0;
+
+
+static int dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+/*
+ * memberof_config()
+ *
+ * Read configuration and create a configuration data structure.
+ * This is called after the server has configured itself so we can
+ * perform checks with regards to suffixes if it ever becomes
+ * necessary.
+ * Returns an LDAP error code (LDAP_SUCCESS if all goes well).
+ */
+int
+memberof_config(Slapi_Entry *config_e)
+{
+ int returncode = LDAP_SUCCESS;
+ char returntext[SLAPI_DSE_RETURNTEXT_SIZE];
+
+ if ( inited ) {
+ slapi_log_error( SLAPI_LOG_FATAL, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "only one memberOf plugin instance can be used\n" );
+ return( LDAP_PARAM_ERROR );
+ }
+
+ /* initialize the RW lock to protect the main config */
+ memberof_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "memberof_config_lock");
+
+ /* initialize fields */
+ memberof_apply_config(NULL, NULL, config_e,
+ &returncode, returntext, NULL);
+
+ /* config DSE must be initialized before we get here */
+ if (returncode == LDAP_SUCCESS) {
+ const char *config_dn = slapi_entry_get_dn_const(config_e);
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP,
+ config_dn, LDAP_SCOPE_BASE, MEMBEROF_CONFIG_FILTER,
+ dont_allow_that,NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP,
+ config_dn, LDAP_SCOPE_BASE, MEMBEROF_CONFIG_FILTER,
+ dont_allow_that, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP,
+ config_dn, LDAP_SCOPE_BASE, MEMBEROF_CONFIG_FILTER,
+ dont_allow_that, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP,
+ config_dn, LDAP_SCOPE_BASE, MEMBEROF_CONFIG_FILTER,
+ memberof_search,NULL);
+ }
+
+ inited = 1;
+
+ if (returncode != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, MEMBEROF_PLUGIN_SUBSYSTEM,
+ "Error %d: %s\n", returncode, returntext);
+ }
+
+ return returncode;
+}
+
+
+/*
+ * memberof_apply_config()
+ *
+ * Just use hardcoded config values.
+ */
+static int
+memberof_apply_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ char *groupattr = NULL;
+ char *memberof_attr = NULL;
+ char *filter_str = NULL;
+
+ *returncode = LDAP_SUCCESS;
+
+ groupattr = slapi_ch_strdup(MEMBEROF_GROUP_ATTR);
+ memberof_attr = slapi_ch_strdup(MEMBEROF_ATTR);
+
+ /* We want to be sure we don't change the config in the middle of
+ * a memberOf operation, so we obtain an exclusive lock here */
+ memberof_wlock_config();
+
+ if (!theConfig.groupattr ||
+ (groupattr && PL_strcmp(theConfig.groupattr, groupattr))) {
+ slapi_ch_free_string(&theConfig.groupattr);
+ theConfig.groupattr = groupattr;
+ groupattr = NULL; /* config now owns memory */
+
+ /* We allocate a Slapi_Attr using the groupattr for
+ * convenience in our memberOf comparison functions */
+ slapi_attr_free(&theConfig.group_slapiattr);
+ theConfig.group_slapiattr = slapi_attr_new();
+ slapi_attr_init(theConfig.group_slapiattr, theConfig.groupattr);
+
+ /* The filter is based off of the groupattr, so we
+ * update it here too. */
+ slapi_filter_free(theConfig.group_filter, 1);
+ filter_str = slapi_ch_smprintf("(%s=*)", theConfig.groupattr);
+ theConfig.group_filter = slapi_str2filter(filter_str);
+ slapi_ch_free_string(&filter_str);
+ }
+
+ if (!theConfig.memberof_attr ||
+ (memberof_attr && PL_strcmp(theConfig.memberof_attr, memberof_attr))) {
+ slapi_ch_free_string(&theConfig.memberof_attr);
+ theConfig.memberof_attr = memberof_attr;
+ memberof_attr = NULL; /* config now owns memory */
+ }
+
+ /* release the lock */
+ memberof_unlock_config();
+
+ slapi_ch_free_string(&groupattr);
+ slapi_ch_free_string(&memberof_attr);
+
+ if (*returncode != LDAP_SUCCESS)
+ {
+ return SLAPI_DSE_CALLBACK_ERROR;
+ }
+ else
+ {
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+}
+
+/*
+ * memberof_copy_config()
+ *
+ * Makes a copy of the config in src. This function will free the
+ * elements of dest if they already exist. This should only be called
+ * if you hold the memberof config lock if src was obtained with
+ * memberof_get_config().
+ */
+void
+memberof_copy_config(MemberOfConfig *dest, MemberOfConfig *src)
+{
+ if (dest && src)
+ {
+ /* Check if the copy is already up to date */
+ if (!dest->groupattr || (src->groupattr
+ && PL_strcmp(dest->groupattr, src->groupattr)))
+ {
+ slapi_ch_free_string(&dest->groupattr);
+ dest->groupattr = slapi_ch_strdup(src->groupattr);
+ slapi_filter_free(dest->group_filter, 1);
+ dest->group_filter = slapi_filter_dup(src->group_filter);
+ slapi_attr_free(&dest->group_slapiattr);
+ dest->group_slapiattr = slapi_attr_dup(src->group_slapiattr);
+ }
+
+ if (!dest->memberof_attr || (src->memberof_attr
+ && PL_strcmp(dest->memberof_attr, src->memberof_attr)))
+ {
+ slapi_ch_free_string(&dest->memberof_attr);
+ dest->memberof_attr = slapi_ch_strdup(src->memberof_attr);
+ }
+ }
+}
+
+/*
+ * memberof_free_config()
+ *
+ * Free's the contents of a config structure.
+ */
+void
+memberof_free_config(MemberOfConfig *config)
+{
+ if (config)
+ {
+ slapi_ch_free_string(&config->groupattr);
+ slapi_filter_free(config->group_filter, 1);
+ slapi_attr_free(&config->group_slapiattr);
+ slapi_ch_free_string(&config->memberof_attr);
+ }
+}
+
+/*
+ * memberof_get_config()
+ *
+ * Returns a pointer to the main config. You should call
+ * memberof_rlock_config() first so the main config doesn't
+ * get modified out from under you.
+ */
+MemberOfConfig *
+memberof_get_config()
+{
+ return &theConfig;
+}
+
+/*
+ * memberof_rlock_config()
+ *
+ * Gets a non-exclusive lock on the main config. This will
+ * prevent the config from being changed out from under you
+ * while you read it, but it will still allow other threads
+ * to read the config at the same time.
+ */
+void
+memberof_rlock_config()
+{
+ PR_RWLock_Rlock(memberof_config_lock);
+}
+
+/*
+ * memberof_wlock_config()
+ *
+ * Gets an exclusive lock on the main config. This should
+ * be called if you need to write to the main config.
+ */
+void
+memberof_wlock_config()
+{
+ PR_RWLock_Wlock(memberof_config_lock);
+}
+
+/*
+ * memberof_unlock_config()
+ *
+ * Unlocks the main config.
+ */
+void
+memberof_unlock_config()
+{
+ PR_RWLock_Unlock(memberof_config_lock);
+}
diff --git a/daemons/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif b/daemons/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif
new file mode 100644
index 000000000..1441afeae
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif
@@ -0,0 +1,14 @@
+dn: cn=ipa-memberof,cn=plugins,cn=config
+changetype: add
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: ipa-memberof
+nsslapd-pluginpath: libipa-memberof-plugin
+nsslapd-plugininitfunc: ipamo_postop_init
+nsslapd-plugintype: postoperation
+nsslapd-pluginenabled: on
+nsslapd-pluginid: memberof
+nsslapd-pluginversion: 1.0
+nsslapd-pluginvendor: Red Hat
+nsslapd-plugindescription: Memberof plugin
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
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/README
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 <ssorce@redhat.com>
+ *
+ * Copyright (C) 2005 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/*
+ * Password Modify - LDAP Extended Operation.
+ * RFC 3062
+ *
+ *
+ * This plugin implements the "Password Modify - LDAP3"
+ * extended operation for LDAP. The plugin function is called by
+ * the server if an LDAP client request contains the OID:
+ * "1.3.6.1.4.1.4203.1.11.1".
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <prio.h>
+#include <ssl.h>
+#include <dirsrv/slapi-plugin.h>
+#define KRB5_PRIVATE 1
+#include <krb5.h>
+#include <lber.h>
+#include <time.h>
+#include <iconv.h>
+#include <openssl/des.h>
+#include <openssl/md4.h>
+
+/* Type of connection for this operation;*/
+#define LDAP_EXTOP_PASSMOD_CONN_SECURE
+
+/* Uncomment the following #undef FOR TESTING:
+ * allows non-SSL connections to use the password change extended op */
+/* #undef LDAP_EXTOP_PASSMOD_CONN_SECURE */
+
+/* ber tags for the PasswdModifyRequestValue sequence */
+#define LDAP_EXTOP_PASSMOD_TAG_USERID 0x80U
+#define LDAP_EXTOP_PASSMOD_TAG_OLDPWD 0x81U
+#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:"<NULL>");
+ *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
diff --git a/daemons/ipa-slapi-plugins/ipa-winsync/Makefile.am b/daemons/ipa-slapi-plugins/ipa-winsync/Makefile.am
new file mode 100644
index 000000000..94bc2dc68
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-winsync/Makefile.am
@@ -0,0 +1,43 @@
+NULL =
+
+INCLUDES = \
+ -I. \
+ -I$(srcdir) \
+ -DPREFIX=\""$(prefix)"\" \
+ -DBINDIR=\""$(bindir)"\" \
+ -DLIBDIR=\""$(libdir)"\" \
+ -DLIBEXECDIR=\""$(libexecdir)"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ $(MOZLDAP_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(NULL)
+
+plugindir = $(libdir)/dirsrv/plugins
+plugin_LTLIBRARIES = \
+ libipa_winsync.la \
+ $(NULL)
+
+libipa_winsync_la_SOURCES = \
+ ipa-winsync.c \
+ ipa-winsync-config.c \
+ $(NULL)
+
+libipa_winsync_la_LDFLAGS = -avoid-version
+
+#libipa_winsync_la_LIBADD = \
+# $(MOZLDAP_LIBS) \
+# $(NULL)
+
+appdir = $(IPA_DATA_DIR)
+app_DATA = \
+ ipa-winsync-conf.ldif \
+ $(NULL)
+
+EXTRA_DIST = \
+ README \
+ $(app_DATA) \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
diff --git a/daemons/ipa-slapi-plugins/ipa-winsync/README b/daemons/ipa-slapi-plugins/ipa-winsync/README
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-winsync/README
diff --git a/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif b/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif
new file mode 100644
index 000000000..5b5c56acb
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif
@@ -0,0 +1,27 @@
+dn: cn=ipa-winsync,cn=plugins,cn=config
+changetype: add
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: ipa-winsync
+nsslapd-pluginpath: libipa_winsync
+nsslapd-plugininitfunc: ipa_winsync_plugin_init
+nsslapd-pluginDescription: Allows IPA to work with the DS windows sync feature
+nsslapd-pluginid: ipa-winsync
+nsslapd-pluginversion: 1.0
+nsslapd-pluginvendor: Red Hat
+nsslapd-plugintype: preoperation
+nsslapd-pluginenabled: on
+nsslapd-plugin-depends-on-type: database
+ipaWinSyncRealmFilter: (objectclass=krbRealmContainer)
+ipaWinSyncRealmAttr: cn
+ipaWinSyncNewEntryFilter: (cn=ipaConfig)
+ipaWinSyncNewUserOCAttr: ipauserobjectclasses
+ipaWinSyncUserFlatten: true
+ipaWinsyncHomeDirAttr: ipaHomesRootDir
+ipaWinSyncDefaultGroupAttr: ipaDefaultPrimaryGroup
+ipaWinSyncDefaultGroupFilter: (gidNumber=*)(objectclass=posixGroup)(objectclass=groupOfNames)
+ipaWinSyncAcctDisable: both
+ipaWinSyncInactivatedFilter: (&(cn=inactivated)(objectclass=groupOfNames))
+ipaWinSyncActivatedFilter: (&(cn=activated)(objectclass=groupOfNames))
+ipaWinSyncForceSync: true
diff --git a/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c b/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c
new file mode 100644
index 000000000..45efa6df0
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c
@@ -0,0 +1,975 @@
+/** 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:
+ * Rich Megginson <rmeggins@redhat.com>
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/*
+ * Windows Synchronization Plug-in for IPA
+ * This plugin allows IPA to intercept operations sent from
+ * Windows to the directory server and vice versa. This allows
+ * IPA to intercept new users added to Windows and synced to the
+ * directory server, and allows IPA to modify the entry, adding
+ * objectclasses and attributes, and changing the DN.
+ */
+
+#ifdef WINSYNC_TEST_IPA
+#include <slapi-plugin.h>
+#include "winsync-plugin.h"
+#else
+#include <dirsrv/slapi-plugin.h>
+#include <dirsrv/winsync-plugin.h>
+#endif
+#include "ipa-winsync.h"
+
+#include <string.h>
+
+#define IPA_WINSYNC_CONFIG_FILTER "(objectclass=*)"
+
+/*
+ * function prototypes
+ */
+static int ipa_winsync_validate_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg);
+static int ipa_winsync_apply_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg);
+static int ipa_winsync_search (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+/*
+ * static variables
+ */
+/* for now, there is only one configuration and it is global to the plugin */
+static IPA_WinSync_Config theConfig;
+static int inited = 0;
+
+static int dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+IPA_WinSync_Config *
+ipa_winsync_get_config()
+{
+ return &theConfig;
+}
+
+/*
+ * Read configuration and create a configuration data structure.
+ * This is called after the server has configured itself so we can check
+ * schema and whatnot.
+ * Returns an LDAP error code (LDAP_SUCCESS if all goes well).
+ */
+int
+ipa_winsync_config(Slapi_Entry *config_e)
+{
+ int returncode = LDAP_SUCCESS;
+ char returntext[SLAPI_DSE_RETURNTEXT_SIZE];
+
+ if ( inited ) {
+ slapi_log_error( SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error: IPA WinSync plug-in already configured. "
+ "Please remove the plugin config entry [%s]\n",
+ slapi_entry_get_dn_const(config_e));
+ return( LDAP_PARAM_ERROR );
+ }
+
+ /* initialize fields */
+ if ((theConfig.lock = slapi_new_mutex()) == NULL) {
+ return( LDAP_LOCAL_ERROR );
+ }
+
+ /* init defaults */
+ theConfig.config_e = slapi_entry_alloc();
+ slapi_entry_init(theConfig.config_e, slapi_ch_strdup(""), NULL);
+ theConfig.flatten = PR_TRUE;
+
+ if (SLAPI_DSE_CALLBACK_OK == ipa_winsync_validate_config(NULL, NULL, config_e,
+ &returncode, returntext, NULL)) {
+ ipa_winsync_apply_config(NULL, NULL, config_e,
+ &returncode, returntext, NULL);
+ }
+
+ /* config DSE must be initialized before we get here */
+ if (returncode == LDAP_SUCCESS) {
+ const char *config_dn = slapi_entry_get_dn_const(config_e);
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, config_dn, LDAP_SCOPE_BASE,
+ IPA_WINSYNC_CONFIG_FILTER, ipa_winsync_validate_config,NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODIFY, DSE_FLAG_POSTOP, config_dn, LDAP_SCOPE_BASE,
+ IPA_WINSYNC_CONFIG_FILTER, ipa_winsync_apply_config,NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_MODRDN, DSE_FLAG_PREOP, config_dn, LDAP_SCOPE_BASE,
+ IPA_WINSYNC_CONFIG_FILTER, dont_allow_that, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, config_dn, LDAP_SCOPE_BASE,
+ IPA_WINSYNC_CONFIG_FILTER, dont_allow_that, NULL);
+ slapi_config_register_callback(SLAPI_OPERATION_SEARCH, DSE_FLAG_PREOP, config_dn, LDAP_SCOPE_BASE,
+ IPA_WINSYNC_CONFIG_FILTER, ipa_winsync_search,NULL);
+ }
+
+ inited = 1;
+
+ if (returncode != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error %d: %s\n", returncode, returntext);
+ }
+
+ return returncode;
+}
+
+static int
+parse_acct_disable(const char *theval)
+{
+ int retval = ACCT_DISABLE_INVALID;
+ if (!theval || !*theval) {
+ return retval;
+ }
+ if (!PL_strcasecmp(theval, IPA_WINSYNC_ACCT_DISABLE_NONE)) {
+ retval = ACCT_DISABLE_NONE;
+ } else if (!PL_strcasecmp(theval, IPA_WINSYNC_ACCT_DISABLE_TO_AD)) {
+ retval = ACCT_DISABLE_TO_AD;
+ } else if (!PL_strcasecmp(theval, IPA_WINSYNC_ACCT_DISABLE_TO_DS)) {
+ retval = ACCT_DISABLE_TO_DS;
+ } else if (!PL_strcasecmp(theval, IPA_WINSYNC_ACCT_DISABLE_BOTH)) {
+ retval = ACCT_DISABLE_BOTH;
+ }
+
+ return retval;
+}
+
+/*
+ Validate the pending changes in the e entry.
+*/
+static int
+ipa_winsync_validate_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e,
+ int *returncode, char *returntext, void *arg)
+{
+ char **attrsvals = NULL;
+ int ii;
+ Slapi_Attr *testattr = NULL;
+ char *strattr = NULL;
+ int acct_disable;
+
+ *returncode = LDAP_UNWILLING_TO_PERFORM; /* be pessimistic */
+
+ /* get realm filter */
+ if (slapi_entry_attr_find(e, IPA_WINSYNC_REALM_FILTER_ATTR, &testattr) ||
+ (NULL == testattr)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_REALM_FILTER_ATTR);
+ goto done2;
+ }
+
+ /* get realm attr */
+ if (slapi_entry_attr_find(e, IPA_WINSYNC_REALM_ATTR_ATTR, &testattr) ||
+ (NULL == testattr)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_REALM_ATTR_ATTR);
+ goto done2;
+ }
+
+ /* get new_entry_filter */
+ if (slapi_entry_attr_find(e, IPA_WINSYNC_NEW_ENTRY_FILTER_ATTR,
+ &testattr) ||
+ (NULL == testattr)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_NEW_ENTRY_FILTER_ATTR);
+ goto done2;
+ }
+
+ /* get new_user_oc_attr */
+ if (slapi_entry_attr_find(e, IPA_WINSYNC_NEW_USER_OC_ATTR,
+ &testattr) ||
+ (NULL == testattr)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_NEW_USER_OC_ATTR);
+ goto done2;
+ }
+
+ /* get homedir_prefix_attr */
+ if (slapi_entry_attr_find(e, IPA_WINSYNC_HOMEDIR_PREFIX_ATTR,
+ &testattr) ||
+ (NULL == testattr)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_HOMEDIR_PREFIX_ATTR);
+ goto done2;
+ }
+
+ /* get default_group_attr */
+ if (slapi_entry_attr_find(e, IPA_WINSYNC_DEFAULTGROUP_ATTR,
+ &testattr) ||
+ (NULL == testattr)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_DEFAULTGROUP_ATTR);
+ goto done2;
+ }
+
+ /* get default_group_filter */
+ if (slapi_entry_attr_find(e, IPA_WINSYNC_DEFAULTGROUP_FILTER_ATTR,
+ &testattr) ||
+ (NULL == testattr)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_DEFAULTGROUP_FILTER_ATTR);
+ goto done2;
+ }
+
+ /* get the list of attributes & values */
+ /* get new_user_oc_attr */
+ if (!(attrsvals = slapi_entry_attr_get_charray(
+ e, IPA_WINSYNC_NEW_USER_ATTRS_VALS))) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPA_WINSYNC_PLUGIN_NAME,
+ "Info: no default attributes and values given in [%s]\n",
+ IPA_WINSYNC_NEW_USER_ATTRS_VALS);
+ }
+
+ /* format of *attrsvals is "attrname value" */
+ /* attrname <space> value */
+ /* value may contain spaces - attrname is everything up to the first
+ space - value is everything after the first space */
+ for (ii = 0; attrsvals && attrsvals[ii]; ++ii) {
+ Slapi_Attr *attr = NULL;
+ char *oidp = NULL;
+ char *val = strchr(attrsvals[ii], ' ');
+ if (!val || !*(val+1)) { /* incorrect format or no value */
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value or incorrect value given for [%s] "
+ "value [%s] index [%d] - correct format is attrname SPACE value",
+ IPA_WINSYNC_NEW_USER_ATTRS_VALS,
+ attrsvals[ii], ii);
+ goto done2;
+ }
+ *val = '\0'; /* separate attr from val */
+ /* check to make sure attribute is in the schema */
+ attr = slapi_attr_new();
+ slapi_attr_set_type(attr, attrsvals[ii]);
+ slapi_attr_get_oid_copy(attr, &oidp);
+ slapi_attr_free(&attr);
+ if (oidp == NULL) { /* no such attribute */
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: invalid attribute name [%s] given for [%s] "
+ "at index [%d] - attribute is not in server schema",
+ attrsvals[ii], IPA_WINSYNC_NEW_USER_ATTRS_VALS,
+ ii);
+ goto done2;
+ }
+
+ /* attribute is valid - continue */
+ slapi_ch_free_string(&oidp);
+ }
+
+ /* get account disable sync direction */
+ if (!(strattr = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_ACCT_DISABLE))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_ACCT_DISABLE);
+ goto done2;
+ }
+
+ acct_disable = parse_acct_disable(strattr);
+ if (ACCT_DISABLE_INVALID == acct_disable) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: invalid value [%s] given for [%s] - valid "
+ "values are " IPA_WINSYNC_ACCT_DISABLE_NONE
+ ", " IPA_WINSYNC_ACCT_DISABLE_TO_AD
+ ", " IPA_WINSYNC_ACCT_DISABLE_TO_DS
+ ", or " IPA_WINSYNC_ACCT_DISABLE_BOTH,
+ strattr, IPA_WINSYNC_ACCT_DISABLE);
+ goto done2;
+ }
+
+ /* if using acct disable sync, must have the attributes
+ IPA_WINSYNC_INACTIVATED_FILTER and IPA_WINSYNC_ACTIVATED_FILTER
+ */
+ if (acct_disable != ACCT_DISABLE_NONE) {
+ if (slapi_entry_attr_find(e, IPA_WINSYNC_INACTIVATED_FILTER,
+ &testattr) ||
+ (NULL == testattr)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s - "
+ "required for account disable sync",
+ IPA_WINSYNC_INACTIVATED_FILTER);
+ goto done2;
+ }
+ if (slapi_entry_attr_find(e, IPA_WINSYNC_ACTIVATED_FILTER,
+ &testattr) ||
+ (NULL == testattr)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s - "
+ "required for account disable sync",
+ IPA_WINSYNC_ACTIVATED_FILTER);
+ goto done2;
+ }
+ }
+
+ /* success */
+ *returncode = LDAP_SUCCESS;
+
+done2:
+ slapi_ch_free_string(&strattr);
+ slapi_ch_array_free(attrsvals);
+ attrsvals = NULL;
+
+ if (*returncode != LDAP_SUCCESS) {
+ return SLAPI_DSE_CALLBACK_ERROR;
+ } else {
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+}
+
+static int
+ipa_winsync_apply_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore,
+ Slapi_Entry* e, int *returncode, char *returntext,
+ void *arg)
+{
+ PRBool flatten = PR_TRUE;
+ char *realm_filter = NULL;
+ char *realm_attr = NULL;
+ char *new_entry_filter = NULL;
+ char *new_user_oc_attr = NULL; /* don't care about groups for now */
+ char *homedir_prefix_attr = NULL;
+ char *default_group_attr = NULL;
+ char *default_group_filter = NULL;
+ char *acct_disable = NULL;
+ int acct_disable_int;
+ char *inactivated_filter = NULL;
+ char *activated_filter = NULL;
+ char **attrsvals = NULL;
+ int ii;
+ Slapi_Attr *testattr = NULL;
+ PRBool forceSync = PR_FALSE;
+
+ *returncode = LDAP_UNWILLING_TO_PERFORM; /* be pessimistic */
+
+ /* get flatten value */
+ if (!slapi_entry_attr_find(e, IPA_WINSYNC_USER_FLATTEN, &testattr) &&
+ (NULL != testattr)) {
+ flatten = slapi_entry_attr_get_bool(e, IPA_WINSYNC_USER_FLATTEN);
+ }
+
+ /* get realm filter */
+ if (!(realm_filter = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_REALM_FILTER_ATTR))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_REALM_FILTER_ATTR);
+ goto done3;
+ }
+
+ /* get realm attr */
+ if (!(realm_attr = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_REALM_ATTR_ATTR))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_REALM_ATTR_ATTR);
+ goto done3;
+ }
+
+ /* get new_entry_filter */
+ if (!(new_entry_filter = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_NEW_ENTRY_FILTER_ATTR))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_NEW_ENTRY_FILTER_ATTR);
+ goto done3;
+ }
+
+ /* get new_user_oc_attr */
+ if (!(new_user_oc_attr = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_NEW_USER_OC_ATTR))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_NEW_USER_OC_ATTR);
+ goto done3;
+ }
+
+ /* get homedir_prefix_attr */
+ if (!(homedir_prefix_attr = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_HOMEDIR_PREFIX_ATTR))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_HOMEDIR_PREFIX_ATTR);
+ goto done3;
+ }
+
+ /* get default_group_attr */
+ if (!(default_group_attr = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_DEFAULTGROUP_ATTR))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_DEFAULTGROUP_ATTR);
+ goto done3;
+ }
+
+ /* get default_group_filter */
+ if (!(default_group_filter = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_DEFAULTGROUP_FILTER_ATTR))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_DEFAULTGROUP_FILTER_ATTR);
+ goto done3;
+ }
+
+ /* get the list of attributes & values */
+ /* get new_user_oc_attr */
+ if (!(attrsvals = slapi_entry_attr_get_charray(
+ e, IPA_WINSYNC_NEW_USER_ATTRS_VALS))) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPA_WINSYNC_PLUGIN_NAME,
+ "Info: no default attributes and values given in [%s]\n",
+ IPA_WINSYNC_NEW_USER_ATTRS_VALS);
+ }
+
+ /* get acct disable sync value */
+ if (!(acct_disable = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_ACCT_DISABLE))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s",
+ IPA_WINSYNC_ACCT_DISABLE);
+ goto done3;
+ }
+
+ acct_disable_int = parse_acct_disable(acct_disable);
+ if (ACCT_DISABLE_INVALID == acct_disable_int) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: invalid value [%s] given for [%s] - valid "
+ "values are " IPA_WINSYNC_ACCT_DISABLE_NONE
+ ", " IPA_WINSYNC_ACCT_DISABLE_TO_AD
+ ", " IPA_WINSYNC_ACCT_DISABLE_TO_DS
+ ", or " IPA_WINSYNC_ACCT_DISABLE_BOTH,
+ acct_disable, IPA_WINSYNC_ACCT_DISABLE);
+ goto done3;
+ }
+
+ if (acct_disable_int != ACCT_DISABLE_NONE) {
+ /* get inactivated group filter */
+ if (!(inactivated_filter = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_INACTIVATED_FILTER))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s - required for account disable sync",
+ IPA_WINSYNC_INACTIVATED_FILTER);
+ goto done3;
+ }
+ /* get activated group filter */
+ if (!(activated_filter = slapi_entry_attr_get_charptr(
+ e, IPA_WINSYNC_ACTIVATED_FILTER))) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value given for %s - required for account disable sync",
+ IPA_WINSYNC_ACTIVATED_FILTER);
+ goto done3;
+ }
+ }
+
+ /* get forceSync value */
+ if (!slapi_entry_attr_find(e, IPA_WINSYNC_FORCE_SYNC, &testattr) &&
+ (NULL != testattr)) {
+ forceSync = slapi_entry_attr_get_bool(e, IPA_WINSYNC_FORCE_SYNC);
+ }
+
+ /* if we got here, we have valid values for everything
+ set the config entry */
+ slapi_lock_mutex(theConfig.lock);
+ slapi_entry_free(theConfig.config_e);
+ theConfig.config_e = slapi_entry_alloc();
+ slapi_entry_init(theConfig.config_e, slapi_ch_strdup(""), NULL);
+
+ /* format of *attrsvals is "attrname value" */
+ /* attrname <space> value */
+ /* value may contain spaces - attrname is everything up to the first
+ space - value is everything after the first space */
+ for (ii = 0; attrsvals && attrsvals[ii]; ++ii) {
+ int rc;
+ Slapi_Value *sva[2];
+ Slapi_Value *sv = NULL;
+ char *val = strchr(attrsvals[ii], ' ');
+ if (!val || !*(val+1)) { /* incorrect format or no value */
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: no value or incorrect value given for [%s] "
+ "value [%s] index [%d] - correct format is attrname SPACE value",
+ IPA_WINSYNC_NEW_USER_ATTRS_VALS,
+ attrsvals[ii], ii);
+ goto done3;
+ }
+ *val++ = '\0'; /* separate attr from val */
+ sv = slapi_value_new_string(val);
+ sva[0] = sv;
+ sva[1] = NULL;
+ if ((rc = slapi_entry_add_values_sv(theConfig.config_e,
+ attrsvals[ii], sva)) &&
+ (rc != LDAP_SUCCESS)) {
+ PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ "Error: could not add value [%s] for attribute name "
+ "[%s] - ldap error [%d: %s]", val, attrsvals[ii],
+ attrsvals[ii], IPA_WINSYNC_NEW_USER_ATTRS_VALS,
+ rc, ldap_err2string(rc));
+ slapi_entry_free(theConfig.config_e);
+ theConfig.config_e = NULL;
+ slapi_value_free(&sv);
+ goto done3;
+ }
+ slapi_value_free(&sv);
+ }
+
+ /* all of the attrs and vals have been set - set the other values */
+ slapi_ch_free_string(&theConfig.realm_filter);
+ theConfig.realm_filter = realm_filter;
+ realm_filter = NULL;
+ slapi_ch_free_string(&theConfig.realm_attr);
+ theConfig.realm_attr = realm_attr;
+ realm_attr = NULL;
+ slapi_ch_free_string(&theConfig.new_entry_filter);
+ theConfig.new_entry_filter = new_entry_filter;
+ new_entry_filter = NULL;
+ slapi_ch_free_string(&theConfig.new_user_oc_attr);
+ theConfig.new_user_oc_attr = new_user_oc_attr;
+ new_user_oc_attr = NULL;
+ slapi_ch_free_string(&theConfig.homedir_prefix_attr);
+ theConfig.homedir_prefix_attr = homedir_prefix_attr;
+ homedir_prefix_attr = NULL;
+ slapi_ch_free_string(&theConfig.default_group_attr);
+ theConfig.default_group_attr = default_group_attr;
+ default_group_attr = NULL;
+ slapi_ch_free_string(&theConfig.default_group_filter);
+ theConfig.default_group_filter = default_group_filter;
+ default_group_filter = NULL;
+ theConfig.flatten = flatten;
+ theConfig.acct_disable = parse_acct_disable(acct_disable);
+ slapi_ch_free_string(&theConfig.inactivated_filter);
+ theConfig.inactivated_filter = inactivated_filter;
+ inactivated_filter = NULL;
+ slapi_ch_free_string(&theConfig.activated_filter);
+ theConfig.activated_filter = activated_filter;
+ activated_filter = NULL;
+ theConfig.forceSync = forceSync;
+
+ /* success */
+ *returncode = LDAP_SUCCESS;
+
+done3:
+ slapi_unlock_mutex(theConfig.lock);
+
+ slapi_ch_free_string(&realm_filter);
+ slapi_ch_free_string(&realm_attr);
+ slapi_ch_free_string(&new_entry_filter);
+ slapi_ch_free_string(&new_user_oc_attr);
+ slapi_ch_free_string(&homedir_prefix_attr);
+ slapi_ch_free_string(&default_group_attr);
+ slapi_ch_free_string(&default_group_filter);
+ slapi_ch_array_free(attrsvals);
+ attrsvals = NULL;
+ slapi_ch_free_string(&acct_disable);
+ slapi_ch_free_string(&inactivated_filter);
+ slapi_ch_free_string(&activated_filter);
+
+ if (*returncode != LDAP_SUCCESS) {
+ return SLAPI_DSE_CALLBACK_ERROR;
+ } else {
+ return SLAPI_DSE_CALLBACK_OK;
+ }
+}
+
+/* create per-domain config object */
+void *
+ipa_winsync_config_new_domain(
+ const Slapi_DN *ds_subtree,
+ const Slapi_DN *ad_subtree
+)
+{
+ IPA_WinSync_Domain_Config *iwdc =
+ (IPA_WinSync_Domain_Config *)
+ slapi_ch_calloc(1, sizeof(IPA_WinSync_Domain_Config));
+
+ return (void *)iwdc;
+}
+
+/* destroy per-domain config object */
+void
+ipa_winsync_config_destroy_domain(
+ void *cbdata, const Slapi_DN *ds_subtree,
+ const Slapi_DN *ad_subtree
+)
+{
+ IPA_WinSync_Domain_Config *iwdc =
+ (IPA_WinSync_Domain_Config *)cbdata;
+ slapi_entry_free(iwdc->domain_e);
+ iwdc->domain_e = NULL;
+ slapi_ch_free_string(&iwdc->realm_name);
+ slapi_ch_free_string(&iwdc->homedir_prefix);
+ slapi_ch_free_string(&iwdc->inactivated_group_dn);
+ slapi_ch_free_string(&iwdc->activated_group_dn);
+ slapi_ch_free((void **)&iwdc);
+
+ return;
+}
+
+/*
+ return the value(s) of the given attribute in the entry that
+ matches the given criteria. The criteria must match one
+ and only one entry.
+ Returns:
+ -1 - problem doing internal search
+ LDAP_UNWILLING_TO_PERFORM - more than one matching entry
+ LDAP_NO_SUCH_OBJECT - no entry found that matched
+ 0 and attrval == NULL - entry found but no attribute
+ other ldap error - error doing search for given basedn
+*/
+static int
+internal_find_entry_get_attr_val(const Slapi_DN *basedn, int scope,
+ const char *filter, const char *attrname,
+ Slapi_ValueSet **svs, char **attrval)
+{
+ Slapi_Entry **entries = NULL;
+ Slapi_PBlock *pb = NULL;
+ const char *search_basedn = slapi_sdn_get_dn(basedn);
+ int search_scope = scope;
+ int ret = LDAP_SUCCESS;
+ const char *attrs[2] = {attrname, NULL};
+
+ if (svs) {
+ *svs = NULL;
+ }
+ if (attrval) {
+ *attrval = NULL;
+ }
+ pb = slapi_pblock_new();
+ slapi_search_internal_set_pb(pb, search_basedn, search_scope, filter,
+ (char **)attrs, 0, NULL, NULL,
+ ipa_winsync_get_plugin_identity(), 0);
+ slapi_search_internal_pb(pb);
+
+ /* This search may return no entries, but should never
+ return an error
+ */
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+ if (ret != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error [%d:%s] searching for base [%s] filter [%s]"
+ " attr [%s]\n", ret, ldap_err2string(ret),
+ search_basedn, filter, attrs[0]);
+ goto out1;
+ }
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (entries && entries[0] && entries[1]) {
+ /* error - should never be more than one matching entry */
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error: more than one entry matches search for "
+ "base [%s] filter [%s] attr [%s]\n",
+ search_basedn, filter, attrs[0]);
+ ret = LDAP_UNWILLING_TO_PERFORM;
+ goto out1;
+ }
+
+ if (entries && entries[0]) { /* found one */
+ if (svs) {
+ Slapi_Attr *attr = NULL;
+ slapi_entry_attr_find(entries[0], attrname, &attr);
+ if (attr) {
+ /* slapi_attr_get_valueset allocates svs - must be freed later */
+ slapi_attr_get_valueset(attr, svs);
+ }
+ }
+ if (attrval) {
+ if (!strcmp(attrname, "dn")) { /* special - to just get the DN */
+ *attrval = slapi_ch_strdup(slapi_entry_get_dn_const(entries[0]));
+ } else {
+ *attrval = slapi_entry_attr_get_charptr(entries[0], attrname);
+ }
+ }
+ } else {
+ ret = LDAP_NO_SUCH_OBJECT;
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPA_WINSYNC_PLUGIN_NAME,
+ "Did not find an entry for search "
+ "base [%s] filter [%s] attr [%s]\n",
+ search_basedn, filter, attrs[0]);
+ }
+
+out1:
+ if (pb) {
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy(pb);
+ pb = NULL;
+ }
+
+ return ret;
+}
+
+/*
+ * Perform the agreement/domain specific configuration.
+ * IPA stores its configuration in the tree. We use the
+ * ds_subtree to search for the domain/realm specific
+ * configuration entries.
+ */
+void
+ipa_winsync_config_refresh_domain(
+ void *cbdata, const Slapi_DN *ds_subtree,
+ const Slapi_DN *ad_subtree
+)
+{
+ IPA_WinSync_Domain_Config *iwdc =
+ (IPA_WinSync_Domain_Config *)cbdata;
+ Slapi_DN *config_dn = slapi_sdn_dup(ds_subtree);
+ char *realm_filter = NULL;
+ char *realm_attr = NULL;
+ char *new_entry_filter = NULL;
+ char *new_user_oc_attr = NULL; /* don't care about groups for now */
+ char *homedir_prefix_attr = NULL;
+ char *default_group_attr = NULL;
+ char *default_group_filter = NULL;
+ char *default_group_name = NULL;
+ char *real_group_filter = NULL;
+ char *default_gid = NULL;
+ Slapi_ValueSet *new_user_objclasses = NULL; /* don't care about groups for now */
+ int loopdone = 0;
+ int search_scope = LDAP_SCOPE_SUBTREE;
+ int ret = LDAP_SUCCESS;
+ Slapi_Value *sv = NULL;
+ int acct_disable;
+ char *inactivated_filter = NULL;
+ char *activated_filter = NULL;
+ char *inactivated_group_dn = NULL;
+ char *activated_group_dn = NULL;
+
+ slapi_lock_mutex(theConfig.lock);
+ realm_filter = slapi_ch_strdup(theConfig.realm_filter);
+ realm_attr = slapi_ch_strdup(theConfig.realm_attr);
+ new_entry_filter = slapi_ch_strdup(theConfig.new_entry_filter);
+ new_user_oc_attr = slapi_ch_strdup(theConfig.new_user_oc_attr);
+ homedir_prefix_attr = slapi_ch_strdup(theConfig.homedir_prefix_attr);
+ default_group_attr = slapi_ch_strdup(theConfig.default_group_attr);
+ default_group_filter = slapi_ch_strdup(theConfig.default_group_filter);
+ acct_disable = theConfig.acct_disable;
+ if (acct_disable != ACCT_DISABLE_NONE) {
+ inactivated_filter = slapi_ch_strdup(theConfig.inactivated_filter);
+ activated_filter = slapi_ch_strdup(theConfig.activated_filter);
+ }
+ slapi_unlock_mutex(theConfig.lock);
+
+ /* starting at ds_subtree, search for the entry
+ containing the Kerberos realm to use */
+ slapi_ch_free_string(&iwdc->realm_name);
+ while(!loopdone && !slapi_sdn_isempty(config_dn)) {
+ ret = internal_find_entry_get_attr_val(config_dn, search_scope,
+ realm_filter, realm_attr,
+ NULL, &iwdc->realm_name);
+
+ if ((0 == ret) && iwdc->realm_name) {
+ loopdone = 1;
+ } else if ((LDAP_NO_SUCH_OBJECT == ret) && !iwdc->realm_name) {
+ /* try again */
+ Slapi_DN *parent_dn = slapi_sdn_new();
+ slapi_sdn_get_parent(config_dn, parent_dn);
+ slapi_sdn_free(&config_dn);
+ config_dn = parent_dn;
+ } else { /* error */
+ goto out;
+ }
+ }
+
+ if (!iwdc->realm_name) {
+ /* error - could not find the IPA config entry with the realm name */
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error: could not find the entry containing the realm name for "
+ "ds subtree [%s] filter [%s] attr [%s]\n",
+ slapi_sdn_get_dn(ds_subtree), realm_filter, realm_attr);
+ goto out;
+ }
+
+ /* look for the entry containing the default objectclasses
+ to add to new entries */
+ ret = internal_find_entry_get_attr_val(config_dn, search_scope,
+ new_entry_filter, new_user_oc_attr,
+ &new_user_objclasses, NULL);
+ if (!new_user_objclasses) {
+ /* error - could not find the entry containing list of objectclasses */
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error: could not find the entry containing the new user objectclass list for "
+ "ds subtree [%s] filter [%s] attr [%s]\n",
+ slapi_sdn_get_dn(ds_subtree), new_entry_filter, new_user_oc_attr);
+ goto out;
+ }
+
+ /* get the home directory prefix value */
+ /* note - this is in the same entry as the new entry template, so
+ use the same filter */
+ slapi_ch_free_string(&iwdc->homedir_prefix);
+ ret = internal_find_entry_get_attr_val(config_dn, search_scope,
+ new_entry_filter, homedir_prefix_attr,
+ NULL, &iwdc->homedir_prefix);
+ if (!iwdc->homedir_prefix) {
+ /* error - could not find the home dir prefix */
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error: could not find the entry containing the home directory prefix for "
+ "ds subtree [%s] filter [%s] attr [%s]\n",
+ slapi_sdn_get_dn(ds_subtree), new_entry_filter, homedir_prefix_attr);
+ goto out;
+ }
+
+ /* find the default group - the entry above contains the group name, but
+ we need the gidNumber for posixAccount - so first find the entry
+ and attr value which has the group name, then lookup the group
+ number from the group name */
+ ret = internal_find_entry_get_attr_val(config_dn, search_scope,
+ new_entry_filter, default_group_attr,
+ NULL, &default_group_name);
+ if (!default_group_name) {
+ /* error - could not find the default group name */
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error: could not find the entry containing the default group name for "
+ "ds subtree [%s] filter [%s] attr [%s]\n",
+ slapi_sdn_get_dn(ds_subtree), new_entry_filter, default_group_attr);
+ goto out;
+ }
+
+ /* next, find the group whose name is default_group_name - construct the filter
+ based on the filter attribute value - assumes the group name is stored
+ in the cn attribute value, and the gidNumber in the gidNumber attribute value */
+ real_group_filter = slapi_ch_smprintf("(&(cn=%s)%s)", default_group_name,
+ default_group_filter);
+ ret = internal_find_entry_get_attr_val(config_dn, search_scope,
+ real_group_filter, "gidNumber",
+ NULL, &default_gid);
+ if (!default_gid) {
+ /* error - could not find the default gidNumber */
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error: could not find the entry containing the default gidNumber "
+ "ds subtree [%s] filter [%s] attr [%s]\n",
+ slapi_sdn_get_dn(ds_subtree), new_entry_filter, "gidNumber");
+ goto out;
+ }
+
+ /* If we are syncing account disable, we need to find the groups used
+ to denote active and inactive users e.g.
+ dn: cn=inactivated,cn=account inactivation,cn=accounts,$SUFFIX
+
+ dn: cn=Activated,cn=Account Inactivation,cn=accounts,$SUFFIX
+
+ */
+ if (acct_disable != ACCT_DISABLE_NONE) {
+ ret = internal_find_entry_get_attr_val(config_dn, search_scope,
+ inactivated_filter, "dn",
+ NULL, &inactivated_group_dn);
+ if (!inactivated_group_dn) {
+ /* error - could not find the inactivated group dn */
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error: could not find the DN of the inactivated users group "
+ "ds subtree [%s] filter [%s]\n",
+ slapi_sdn_get_dn(ds_subtree), inactivated_filter);
+ goto out;
+ }
+ ret = internal_find_entry_get_attr_val(config_dn, search_scope,
+ activated_filter, "dn",
+ NULL, &activated_group_dn);
+ if (!activated_group_dn) {
+ /* error - could not find the activated group dn */
+ slapi_log_error(SLAPI_LOG_FATAL, IPA_WINSYNC_PLUGIN_NAME,
+ "Error: could not find the DN of the activated users group "
+ "ds subtree [%s] filter [%s]\n",
+ slapi_sdn_get_dn(ds_subtree), activated_filter);
+ goto out;
+ }
+ }
+
+ /* ok, we have our values */
+ /* first, clear out the old domain config */
+ slapi_entry_free(iwdc->domain_e);
+ iwdc->domain_e = NULL;
+
+ /* next, copy the global attr config */
+ slapi_lock_mutex(theConfig.lock);
+ iwdc->domain_e = slapi_entry_dup(theConfig.config_e);
+ slapi_unlock_mutex(theConfig.lock);
+
+ /* set the objectclasses in the domain_e */
+ slapi_entry_attr_delete(iwdc->domain_e, "objectclass");
+ /* this copies new_user_objclasses */
+ slapi_entry_add_valueset(iwdc->domain_e, "objectclass", new_user_objclasses);
+
+ /* set the default gid number */
+ sv = slapi_value_new_string_passin(default_gid);
+ default_gid = NULL; /* passin owns the memory */
+ if (!slapi_entry_attr_has_syntax_value(iwdc->domain_e, "gidNumber", sv)) {
+ slapi_entry_add_value(iwdc->domain_e, "gidNumber", sv);
+ }
+ slapi_value_free(&sv);
+
+ slapi_ch_free_string(&iwdc->inactivated_group_dn);
+ iwdc->inactivated_group_dn = inactivated_group_dn;
+ inactivated_group_dn = NULL;
+ slapi_ch_free_string(&iwdc->activated_group_dn);
+ iwdc->activated_group_dn = activated_group_dn;
+ activated_group_dn = NULL;
+
+out:
+ slapi_valueset_free(new_user_objclasses);
+ slapi_sdn_free(&config_dn);
+ slapi_ch_free_string(&realm_filter);
+ slapi_ch_free_string(&realm_attr);
+ slapi_ch_free_string(&new_entry_filter);
+ slapi_ch_free_string(&new_user_oc_attr);
+ slapi_ch_free_string(&homedir_prefix_attr);
+ slapi_ch_free_string(&default_group_attr);
+ slapi_ch_free_string(&default_group_filter);
+ slapi_ch_free_string(&default_group_name);
+ slapi_ch_free_string(&real_group_filter);
+ slapi_ch_free_string(&default_gid);
+ slapi_ch_free_string(&inactivated_filter);
+ slapi_ch_free_string(&inactivated_group_dn);
+ slapi_ch_free_string(&activated_filter);
+ slapi_ch_free_string(&activated_group_dn);
+
+ if (LDAP_SUCCESS != ret) {
+ slapi_ch_free_string(&iwdc->realm_name);
+ slapi_ch_free_string(&iwdc->homedir_prefix);
+ slapi_entry_free(iwdc->domain_e);
+ iwdc->domain_e = NULL;
+ }
+
+ return;
+}
diff --git a/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c b/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c
new file mode 100644
index 000000000..9ee8805bb
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c
@@ -0,0 +1,1177 @@
+/** 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:
+ * Rich Megginson <rmeggins@redhat.com>
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/*
+ * Windows Synchronization Plug-in for IPA
+ * This plugin allows IPA to intercept operations sent from
+ * Windows to the directory server and vice versa. This allows
+ * IPA to intercept new users added to Windows and synced to the
+ * directory server, and allows IPA to modify the entry, adding
+ * objectclasses and attributes, and changing the DN.
+ */
+
+#ifdef WINSYNC_TEST_IPA
+#include <slapi-plugin.h>
+#include "winsync-plugin.h"
+#else
+#include <dirsrv/slapi-plugin.h>
+#include <dirsrv/winsync-plugin.h>
+#endif
+#include "ipa-winsync.h"
+
+static char *ipa_winsync_plugin_name = IPA_WINSYNC_PLUGIN_NAME;
+
+static void
+sync_acct_disable(
+ void *cbdata, /* the usual domain config data */
+ const Slapi_Entry *ad_entry, /* the AD entry */
+ Slapi_Entry *ds_entry, /* the DS entry */
+ int direction, /* the direction - TO_AD or TO_DS */
+ Slapi_Entry *update_entry, /* the entry to update for ADDs */
+ Slapi_Mods *smods, /* the mod list for MODIFYs */
+ int *do_modify /* set to true if mods were applied */
+);
+
+static void
+do_force_sync(
+ const Slapi_Entry *ad_entry, /* the AD entry */
+ Slapi_Entry *ds_entry, /* the DS entry */
+ Slapi_Mods *smods, /* the mod list */
+ int *do_modify /* set to true if mods were applied */
+);
+
+/* This is called when a new agreement is created or loaded
+ at startup.
+*/
+static void *
+ipa_winsync_agmt_init(const Slapi_DN *ds_subtree, const Slapi_DN *ad_subtree)
+{
+ void *cbdata = NULL;
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_agmt_init [%s] [%s] -- begin\n",
+ slapi_sdn_get_dn(ds_subtree),
+ slapi_sdn_get_dn(ad_subtree));
+
+ /* do the domain specific configuration based on the ds subtree */
+ cbdata = ipa_winsync_config_new_domain(ds_subtree, ad_subtree);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_agmt_init -- end\n");
+
+ return cbdata;
+}
+
+static void
+ipa_winsync_dirsync_search_params_cb(void *cbdata, const char *agmt_dn,
+ char **base, int *scope, char **filter,
+ char ***attrs, LDAPControl ***serverctrls)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_dirsync_search_params_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_dirsync_search_params_cb -- end\n");
+
+ return;
+}
+
+/* called before searching for a single entry from AD - agmt_dn will be NULL */
+static void
+ipa_winsync_pre_ad_search_cb(void *cbdata, const char *agmt_dn,
+ char **base, int *scope, char **filter,
+ char ***attrs, LDAPControl ***serverctrls)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ad_search_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ad_search_cb -- end\n");
+
+ return;
+}
+
+/* called before an internal search to get a single DS entry - agmt_dn will be NULL */
+static void
+ipa_winsync_pre_ds_search_entry_cb(void *cbdata, const char *agmt_dn,
+ char **base, int *scope, char **filter,
+ char ***attrs, LDAPControl ***serverctrls)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ds_search_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "-- ipa_winsync_pre_ds_search_cb - base [%s] "
+ "scope [%d] filter [%s]\n",
+ *base, *scope, *filter);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ds_search_cb -- end\n");
+
+ return;
+}
+
+/* called before the total update to get all entries from the DS to sync to AD */
+static void
+ipa_winsync_pre_ds_search_all_cb(void *cbdata, const char *agmt_dn,
+ char **base, int *scope, char **filter,
+ char ***attrs, LDAPControl ***serverctrls)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ds_search_all_cb -- orig filter [%s] -- begin\n",
+ ((filter && *filter) ? *filter : "NULL"));
+
+ /* We only want to grab users from the ds side - no groups */
+ slapi_ch_free_string(filter);
+ /* maybe use ntUniqueId=* - only get users that have already been
+ synced with AD - ntUniqueId and ntUserDomainId are
+ indexed for equality only - need to add presence? */
+ *filter = slapi_ch_strdup("(&(objectclass=ntuser)(ntUserDomainId=*))");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ds_search_all_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_pre_ad_mod_user_cb(void *cbdata, const Slapi_Entry *rawentry,
+ Slapi_Entry *ad_entry, Slapi_Entry *ds_entry,
+ Slapi_Mods *smods, int *do_modify)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ad_mod_user_cb -- begin\n");
+
+ sync_acct_disable(cbdata, rawentry, ds_entry, ACCT_DISABLE_TO_AD,
+ NULL, smods, do_modify);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ad_mod_user_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_pre_ad_mod_group_cb(void *cbdata, const Slapi_Entry *rawentry,
+ Slapi_Entry *ad_entry, Slapi_Entry *ds_entry,
+ Slapi_Mods *smods, int *do_modify)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ad_mod_group_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ad_mod_group_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_pre_ds_mod_user_cb(void *cbdata, const Slapi_Entry *rawentry,
+ Slapi_Entry *ad_entry, Slapi_Entry *ds_entry,
+ Slapi_Mods *smods, int *do_modify)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ds_mod_user_cb -- begin\n");
+
+ sync_acct_disable(cbdata, rawentry, ds_entry, ACCT_DISABLE_TO_DS,
+ NULL, smods, do_modify);
+
+ do_force_sync(rawentry, ds_entry, smods, do_modify);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ds_mod_user_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_pre_ds_mod_group_cb(void *cbdata, const Slapi_Entry *rawentry,
+ Slapi_Entry *ad_entry, Slapi_Entry *ds_entry,
+ Slapi_Mods *smods, int *do_modify)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ds_mod_group_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ds_mod_group_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_pre_ds_add_user_cb(void *cbdata, const Slapi_Entry *rawentry,
+ Slapi_Entry *ad_entry, Slapi_Entry *ds_entry)
+{
+ IPA_WinSync_Domain_Config *ipaconfig = (IPA_WinSync_Domain_Config *)cbdata;
+ Slapi_Attr *attr = NULL;
+ Slapi_Attr *e_attr = NULL;
+ char *type = NULL;
+ IPA_WinSync_Config *global_ipaconfig = ipa_winsync_get_config();
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ds_add_user_cb -- begin\n");
+
+ if (!ipaconfig || !ipaconfig->domain_e || !ipaconfig->realm_name ||
+ !ipaconfig->homedir_prefix) {
+ slapi_log_error(SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "Error: configuration failure: cannot map Windows "
+ "entry dn [%s], DS entry dn [%s]\n",
+ slapi_entry_get_dn_const(ad_entry),
+ slapi_entry_get_dn_const(ds_entry));
+ return;
+ }
+
+ /* add the objectclasses and attributes to the entry */
+ for (slapi_entry_first_attr(ipaconfig->domain_e, &attr); attr;
+ slapi_entry_next_attr(ipaconfig->domain_e, attr, &attr))
+ {
+ slapi_attr_get_type(attr, &type);
+ if (!type) {
+ continue; /* should never happen */
+ }
+
+ if (!slapi_entry_attr_find(ds_entry, type, &e_attr) && e_attr) {
+ /* already has attribute - add missing values */
+ Slapi_Value *sv = NULL;
+ int ii = 0;
+ for (ii = slapi_attr_first_value(attr, &sv); ii != -1;
+ ii = slapi_attr_next_value(attr, ii, &sv))
+ {
+ if (!slapi_entry_attr_has_syntax_value(ds_entry, type, sv)) {
+ /* attr-value sv not found in ds_entry; add it */
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ds_add_user_cb -- "
+ "adding val for [%s] to new entry [%s]\n",
+ type, slapi_entry_get_dn_const(ds_entry));
+
+ slapi_entry_add_value(ds_entry, type, sv);
+ }
+ }
+ } else { /* attr not found */
+ Slapi_ValueSet *svs = NULL;
+ slapi_attr_get_valueset(attr, &svs); /* makes a copy */
+ slapi_entry_add_valueset(ds_entry, type, svs);
+ slapi_valueset_free(svs); /* free the copy */
+ }
+ }
+
+ /* add other attributes */
+ type = "krbPrincipalName";
+ if (slapi_entry_attr_find(ds_entry, type, &e_attr) || !e_attr) {
+ char *upn = NULL;
+ char *uid = NULL;
+ char *samAccountName = NULL;
+ /* if the ds_entry already has a uid, use that */
+ if ((uid = slapi_entry_attr_get_charptr(ds_entry, "uid"))) {
+ upn = slapi_ch_smprintf("%s@%s", uid, ipaconfig->realm_name);
+ slapi_ch_free_string(&uid);
+ /* otherwise, use the samAccountName from the ad_entry */
+ } else if ((samAccountName =
+ slapi_entry_attr_get_charptr(ad_entry, "samAccountName"))) {
+ upn = slapi_ch_smprintf("%s@%s", samAccountName, ipaconfig->realm_name);
+ slapi_ch_free_string(&samAccountName);
+ } else { /* fatal error - nothing to use for krbPrincipalName */
+ slapi_log_error(SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "Error creating %s for realm [%s] for Windows "
+ "entry dn [%s], DS entry dn [%s] - Windows entry "
+ "has no samAccountName, and DS entry has no uid.\n",
+ type, ipaconfig->realm_name,
+ slapi_entry_get_dn_const(ad_entry),
+ slapi_entry_get_dn_const(ds_entry));
+ }
+
+ if (upn) {
+ slapi_entry_attr_set_charptr(ds_entry, type, upn);
+ slapi_ch_free_string(&upn);
+ }
+ }
+
+ type = "homeDirectory";
+ if (slapi_entry_attr_find(ds_entry, type, &e_attr) || !e_attr) {
+ char *homeDir = NULL;
+ char *uid = NULL;
+ char *samAccountName = NULL;
+ /* if the ds_entry already has a uid, use that */
+ if ((uid = slapi_entry_attr_get_charptr(ds_entry, "uid"))) {
+ homeDir = slapi_ch_smprintf("%s/%s", ipaconfig->homedir_prefix, uid);
+ slapi_ch_free_string(&uid);
+ /* otherwise, use the samAccountName from the ad_entry */
+ } else if ((samAccountName =
+ slapi_entry_attr_get_charptr(ad_entry, "samAccountName"))) {
+ homeDir = slapi_ch_smprintf("%s/%s", ipaconfig->homedir_prefix,
+ samAccountName);
+ slapi_ch_free_string(&samAccountName);
+ } else { /* fatal error - nothing to use for homeDirectory */
+ slapi_log_error(SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "Error creating %s for realm [%s] for Windows "
+ "entry dn [%s], DS entry dn [%s] - Windows entry "
+ "has no samAccountName, and DS entry has no uid.\n",
+ type, ipaconfig->realm_name,
+ slapi_entry_get_dn_const(ad_entry),
+ slapi_entry_get_dn_const(ds_entry));
+ }
+
+ if (homeDir) {
+ slapi_entry_attr_set_charptr(ds_entry, type, homeDir);
+ slapi_ch_free_string(&homeDir);
+ }
+ }
+
+ /* gecos is not required, but nice to have */
+ type = "gecos";
+ if (slapi_entry_attr_find(ds_entry, type, &e_attr) || !e_attr) {
+ char *cn = NULL;
+ char *displayName = NULL;
+ /* if the ds_entry already has a cn, use that */
+ if ((cn = slapi_entry_attr_get_charptr(ds_entry, "cn"))) {
+ slapi_entry_attr_set_charptr(ds_entry, type, cn);
+ slapi_ch_free_string(&cn);
+ /* otherwise, use the displayName from the ad_entry */
+ } else if ((displayName =
+ slapi_entry_attr_get_charptr(ad_entry, "displayName"))) {
+ slapi_entry_attr_set_charptr(ds_entry, type, displayName);
+ slapi_ch_free_string(&displayName);
+ }
+ }
+
+ sync_acct_disable(cbdata, rawentry, ds_entry, ACCT_DISABLE_TO_DS,
+ ds_entry, NULL, NULL);
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ds_add_user_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_pre_ds_add_group_cb(void *cbdata, const Slapi_Entry *rawentry,
+ Slapi_Entry *ad_entry, Slapi_Entry *ds_entry)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ds_add_group_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ds_add_group_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_get_new_ds_user_dn_cb(void *cbdata, const Slapi_Entry *rawentry,
+ Slapi_Entry *ad_entry, char **new_dn_string,
+ const Slapi_DN *ds_suffix, const Slapi_DN *ad_suffix)
+{
+ char **rdns = NULL;
+ PRBool flatten = PR_TRUE;
+ IPA_WinSync_Config *ipaconfig = ipa_winsync_get_config();
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_get_new_ds_user_dn_cb -- old dn [%s] -- begin\n",
+ *new_dn_string);
+
+ slapi_lock_mutex(ipaconfig->lock);
+ flatten = ipaconfig->flatten;
+ slapi_unlock_mutex(ipaconfig->lock);
+
+ if (!flatten) {
+ return;
+ }
+
+ rdns = ldap_explode_dn(*new_dn_string, 0);
+ if (!rdns || !rdns[0]) {
+ ldap_value_free(rdns);
+ return;
+ }
+
+ slapi_ch_free_string(new_dn_string);
+ *new_dn_string = slapi_ch_smprintf("%s,%s", rdns[0], slapi_sdn_get_dn(ds_suffix));
+ ldap_value_free(rdns);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_get_new_ds_user_dn_cb -- new dn [%s] -- end\n",
+ *new_dn_string);
+
+ return;
+}
+
+static void
+ipa_winsync_get_new_ds_group_dn_cb(void *cbdata, const Slapi_Entry *rawentry,
+ Slapi_Entry *ad_entry, char **new_dn_string,
+ const Slapi_DN *ds_suffix, const Slapi_DN *ad_suffix)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_get_new_ds_group_dn_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_get_new_ds_group_dn_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_pre_ad_mod_user_mods_cb(void *cbdata, const Slapi_Entry *rawentry,
+ const Slapi_DN *local_dn,
+ const Slapi_Entry *ds_entry,
+ LDAPMod * const *origmods,
+ Slapi_DN *remote_dn, LDAPMod ***modstosend)
+{
+ Slapi_Mods *smods;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ad_mod_user_mods_cb -- begin\n");
+
+ /* wrap the modstosend in a Slapi_Mods for convenience */
+ smods = slapi_mods_new();
+ slapi_mods_init_byref(smods, *modstosend);
+ sync_acct_disable(cbdata, rawentry, (Slapi_Entry *)ds_entry,
+ ACCT_DISABLE_TO_AD, NULL, smods, NULL);
+
+ /* convert back to LDAPMod ** and clean up */
+ *modstosend = slapi_mods_get_ldapmods_passout(smods);
+ slapi_mods_free(&smods);
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ad_mod_user_mods_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_pre_ad_mod_group_mods_cb(void *cbdata, const Slapi_Entry *rawentry,
+ const Slapi_DN *local_dn,
+ const Slapi_Entry *ds_entry,
+ LDAPMod * const *origmods,
+ Slapi_DN *remote_dn, LDAPMod ***modstosend)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_pre_ad_mod_group_mods_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_pre_ad_mod_group_mods_cb -- end\n");
+
+ return;
+}
+
+static int
+ipa_winsync_can_add_entry_to_ad_cb(void *cbdata, const Slapi_Entry *local_entry,
+ const Slapi_DN *remote_dn)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_can_add_entry_to_ad_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_can_add_entry_to_ad_cb -- end\n");
+
+ return 0; /* false - do not allow entries to be added to ad */
+}
+
+static void
+ipa_winsync_begin_update_cb(void *cbdata, const Slapi_DN *ds_subtree,
+ const Slapi_DN *ad_subtree, int is_total)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_begin_update_cb -- begin\n");
+
+ ipa_winsync_config_refresh_domain(cbdata, ds_subtree, ad_subtree);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_begin_update_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_end_update_cb(void *cbdata, const Slapi_DN *ds_subtree,
+ const Slapi_DN *ad_subtree, int is_total)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_end_update_cb -- begin\n");
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_end_update_cb -- end\n");
+
+ return;
+}
+
+static void
+ipa_winsync_destroy_agmt_cb(void *cbdata, const Slapi_DN *ds_subtree,
+ const Slapi_DN *ad_subtree)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_destroy_agmt_cb -- begin\n");
+
+ ipa_winsync_config_destroy_domain(cbdata, ds_subtree, ad_subtree);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_destroy_agmt_cb -- end\n");
+
+ return;
+}
+
+static void *ipa_winsync_api[] = {
+ NULL, /* reserved for api broker use, must be zero */
+ ipa_winsync_agmt_init,
+ ipa_winsync_dirsync_search_params_cb,
+ ipa_winsync_pre_ad_search_cb,
+ ipa_winsync_pre_ds_search_entry_cb,
+ ipa_winsync_pre_ds_search_all_cb,
+ ipa_winsync_pre_ad_mod_user_cb,
+ ipa_winsync_pre_ad_mod_group_cb,
+ ipa_winsync_pre_ds_mod_user_cb,
+ ipa_winsync_pre_ds_mod_group_cb,
+ ipa_winsync_pre_ds_add_user_cb,
+ ipa_winsync_pre_ds_add_group_cb,
+ ipa_winsync_get_new_ds_user_dn_cb,
+ ipa_winsync_get_new_ds_group_dn_cb,
+ ipa_winsync_pre_ad_mod_user_mods_cb,
+ ipa_winsync_pre_ad_mod_group_mods_cb,
+ ipa_winsync_can_add_entry_to_ad_cb,
+ ipa_winsync_begin_update_cb,
+ ipa_winsync_end_update_cb,
+ ipa_winsync_destroy_agmt_cb
+};
+
+/**
+ * Plugin identifiers
+ */
+static Slapi_PluginDesc ipa_winsync_pdesc = {
+ "ipa-winsync-plugin",
+ "FreeIPA project",
+ "FreeIPA/1.0",
+ "ipa winsync plugin"
+};
+
+static Slapi_ComponentId *ipa_winsync_plugin_id = NULL;
+
+/*
+** Plugin identity mgmt
+*/
+
+void ipa_winsync_set_plugin_identity(void * identity)
+{
+ ipa_winsync_plugin_id=identity;
+}
+
+void * ipa_winsync_get_plugin_identity()
+{
+ return ipa_winsync_plugin_id;
+}
+
+static int
+ipa_winsync_plugin_start(Slapi_PBlock *pb)
+{
+ int rc;
+ Slapi_Entry *config_e = NULL; /* entry containing plugin config */
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_plugin_start -- begin\n");
+
+ if( slapi_apib_register(WINSYNC_v1_0_GUID, ipa_winsync_api) ) {
+ slapi_log_error( SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_plugin_start -- failed to register winsync api -- end\n");
+ return -1;
+ }
+
+ if ( slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &config_e ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "missing config entry\n" );
+ return( -1 );
+ }
+
+ if (( rc = ipa_winsync_config( config_e )) != LDAP_SUCCESS ) {
+ slapi_log_error( SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "configuration failed (%s)\n", ldap_err2string( rc ));
+ return( -1 );
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_plugin_start -- end\n");
+ return 0;
+}
+
+static int
+ipa_winsync_plugin_close(Slapi_PBlock *pb)
+{
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_plugin_close -- begin\n");
+
+ slapi_apib_unregister(WINSYNC_v1_0_GUID);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_plugin_close -- end\n");
+ return 0;
+}
+
+/* this is the slapi plugin init function,
+ not the one used by the winsync api
+*/
+int ipa_winsync_plugin_init(Slapi_PBlock *pb)
+{
+ void *plugin_id = NULL;
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "--> ipa_winsync_plugin_init -- begin\n");
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) ipa_winsync_plugin_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) ipa_winsync_plugin_close ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&ipa_winsync_pdesc ) != 0 )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_plugin_init -- failed to register plugin -- end\n");
+ return -1;
+ }
+
+ /* Retrieve and save the plugin identity to later pass to
+ internal operations */
+ if (slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id) != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_plugin_init -- failed to retrieve plugin identity -- end\n");
+ return -1;
+ }
+
+ ipa_winsync_set_plugin_identity(plugin_id);
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_winsync_plugin_init -- end\n");
+ return 0;
+}
+
+/*
+ * Check if the given entry has account lock on (i.e. entry is disabled)
+ * Mostly copied from check_account_lock in the server code.
+ * Returns: 0 - account is disabled (lock == "true")
+ * 1 - account is enabled (lock == "false" or empty)
+ * -1 - some sort of error
+ */
+static int
+ipa_check_account_lock(Slapi_Entry *ds_entry, int *isvirt)
+{
+ int rc = 1;
+ Slapi_ValueSet *values = NULL;
+ int type_name_disposition = 0;
+ char *actual_type_name = NULL;
+ int attr_free_flags = 0;
+ char *strval;
+
+ /* first, see if the attribute is a "real" attribute */
+ strval = slapi_entry_attr_get_charptr(ds_entry, "nsAccountLock");
+ if (strval) { /* value is real */
+ *isvirt = 0; /* value is real */
+ rc = 1; /* default to enabled */
+ if (PL_strncasecmp(strval, "true", 4) == 0) {
+ rc = 0; /* account is disabled */
+ }
+ slapi_ch_free_string(&strval);
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_check_account_lock - entry [%s] has real "
+ "attribute nsAccountLock and entry %s locked\n",
+ slapi_entry_get_dn_const(ds_entry),
+ rc ? "is not" : "is");
+ return rc;
+ }
+
+ rc = slapi_vattr_values_get(ds_entry, "nsAccountLock",
+ &values,
+ &type_name_disposition, &actual_type_name,
+ SLAPI_VIRTUALATTRS_REQUEST_POINTERS,
+ &attr_free_flags);
+ if (rc == 0) {
+ Slapi_Value *v = NULL;
+ const struct berval *bvp = NULL;
+
+ rc = 1; /* default is enabled */
+ *isvirt = 1; /* value is virtual */
+ if ((slapi_valueset_first_value(values, &v) != -1) &&
+ (bvp = slapi_value_get_berval(v)) != NULL) {
+ if ( (bvp != NULL) && (PL_strncasecmp(bvp->bv_val, "true", 4) == 0) ) {
+ slapi_vattr_values_free(&values, &actual_type_name, attr_free_flags);
+ rc = 0; /* account is disabled */
+ }
+ }
+
+ if (values != NULL) {
+ slapi_vattr_values_free(&values, &actual_type_name, attr_free_flags);
+ }
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_check_account_lock - entry [%s] has virtual "
+ "attribute nsAccountLock and entry %s locked\n",
+ slapi_entry_get_dn_const(ds_entry),
+ rc ? "is not" : "is");
+ } else {
+ rc = 1; /* no attr == entry is enabled */
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- ipa_check_account_lock - entry [%s] does not "
+ "have attribute nsAccountLock - entry %s locked\n",
+ slapi_entry_get_dn_const(ds_entry),
+ rc ? "is not" : "is");
+ }
+
+ return rc;
+}
+
+static int
+do_group_modify(const char *dn, const char *modtype, int modop, const char *modval)
+{
+ int rc = 0;
+ LDAPMod mod;
+ LDAPMod *mods[2];
+ const char *val[2];
+ Slapi_PBlock *mod_pb = NULL;
+
+ mod_pb = slapi_pblock_new();
+
+ mods[0] = &mod;
+ mods[1] = NULL;
+
+ val[0] = modval;
+ val[1] = NULL;
+
+ mod.mod_op = modop;
+ mod.mod_type = (char *)modtype;
+ mod.mod_values = (char **)val;
+
+ slapi_modify_internal_set_pb(
+ mod_pb, dn, mods, 0, 0,
+ ipa_winsync_get_plugin_identity(), 0);
+
+ slapi_modify_internal_pb(mod_pb);
+
+ slapi_pblock_get(mod_pb,
+ SLAPI_PLUGIN_INTOP_RESULT,
+ &rc);
+
+ slapi_pblock_destroy(mod_pb);
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- do_group_modify - %s value [%s] in attribute [%s] "
+ "in entry [%s] - result (%d: %s)\n",
+ (modop & LDAP_MOD_ADD) ? "added" : "deleted",
+ modval, modtype, dn,
+ rc, ldap_err2string(rc));
+
+ return rc;
+}
+
+/*
+ * This can be used either in the to ad direction or the to ds direction, since in both
+ * cases we have to read both entries and compare the values.
+ * ad_entry - entry from AD
+ * ds_entry - entry from DS
+ * direction - either ACCT_DISABLE_TO_AD or ACCT_DISABLE_TO_DS
+ *
+ * If smods is given, this is the list of mods to send in the given direction. The
+ * appropriate modify operation will be added to this list or changed to the correct
+ * value if it already exists.
+ * Otherwise, if a destination entry is given, the value will be written into
+ * that entry.
+ */
+static void
+sync_acct_disable(
+ void *cbdata, /* the usual domain config data */
+ const Slapi_Entry *ad_entry, /* the AD entry */
+ Slapi_Entry *ds_entry, /* the DS entry */
+ int direction, /* the direction - TO_AD or TO_DS */
+ Slapi_Entry *update_entry, /* the entry to update for ADDs */
+ Slapi_Mods *smods, /* the mod list for MODIFYs */
+ int *do_modify /* if not NULL, set this to true if mods were added */
+)
+{
+ IPA_WinSync_Domain_Config *ipaconfig = (IPA_WinSync_Domain_Config *)cbdata;
+ IPA_WinSync_Config *global_ipaconfig = ipa_winsync_get_config();
+ int acct_disable;
+ int ds_is_enabled = 1; /* default to true */
+ int ad_is_enabled = 1; /* default to true */
+ unsigned long adval = 0; /* raw account val from ad entry */
+ int isvirt = 1; /* default to virt */
+
+ slapi_lock_mutex(global_ipaconfig->lock);
+ acct_disable = global_ipaconfig->acct_disable;
+ slapi_unlock_mutex(global_ipaconfig->lock);
+
+ if (acct_disable == ACCT_DISABLE_NONE) {
+ return; /* not supported */
+ }
+
+ /* get the account lock state of the ds entry */
+ if (0 == ipa_check_account_lock(ds_entry, &isvirt)) {
+ ds_is_enabled = 0;
+ }
+
+ /* get the account lock state of the ad entry */
+ adval = slapi_entry_attr_get_ulong(ad_entry, "UserAccountControl");
+ if (adval & 0x2) {
+ /* account is disabled */
+ ad_is_enabled = 0;
+ }
+
+ if (ad_is_enabled == ds_is_enabled) { /* both have same value - nothing to do */
+ return;
+ }
+
+ /* have to enable or disable */
+ if (direction == ACCT_DISABLE_TO_AD) {
+ unsigned long mask;
+ /* set the mod or entry */
+ if (update_entry) {
+ if (ds_is_enabled) {
+ mask = ~0x2;
+ adval &= mask; /* unset the 0x2 disable bit */
+ } else {
+ mask = 0x2;
+ adval |= mask; /* set the 0x2 disable bit */
+ }
+ slapi_entry_attr_set_ulong(update_entry, "userAccountControl", adval);
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- sync_acct_disable - %s AD account [%s] - "
+ "new value is [%ld]\n",
+ (ds_is_enabled) ? "enabled" : "disabled",
+ slapi_entry_get_dn_const(update_entry),
+ adval);
+ } else {
+ /* iterate through the mods - if there is already a mod
+ for userAccountControl, change it - otherwise, add it */
+ char acctvalstr[32];
+ LDAPMod *mod = NULL;
+ struct berval *mod_bval = NULL;
+ for (mod = slapi_mods_get_first_mod(smods); mod;
+ mod = slapi_mods_get_next_mod(smods)) {
+ if (!PL_strcasecmp(mod->mod_type, "userAccountControl") &&
+ mod->mod_bvalues && mod->mod_bvalues[0]) {
+ mod_bval = mod->mod_bvalues[0];
+ /* mod_bval points directly to value inside mod list */
+ break;
+ }
+ }
+ if (!mod_bval) { /* not found - add it */
+ struct berval tmpbval = {0, NULL};
+ Slapi_Mod *smod = slapi_mod_new();
+ slapi_mod_init(smod, 1); /* one element */
+ slapi_mod_set_type(smod, "userAccountControl");
+ slapi_mod_set_operation(smod, LDAP_MOD_REPLACE|LDAP_MOD_BVALUES);
+ slapi_mod_add_value(smod, &tmpbval);
+ /* add_value makes a copy of the bval - so let's get a pointer
+ to that new value - we will change the bval in place */
+ mod_bval = slapi_mod_get_first_value(smod);
+ /* mod_bval points directly to value inside mod list */
+ /* now add the new mod to smods */
+ slapi_mods_add_ldapmod(smods,
+ slapi_mod_get_ldapmod_passout(smod));
+ /* smods now owns the ldapmod */
+ slapi_mod_free(&smod);
+ if (do_modify) {
+ *do_modify = 1; /* added mods */
+ }
+ }
+ if (mod_bval) {
+ /* this is where we set or update the actual value
+ mod_bval points directly into the mod list we are
+ sending */
+ if (mod_bval->bv_val && (mod_bval->bv_len > 0)) {
+ /* get the old val */
+ adval = strtol(mod_bval->bv_val, NULL, 10);
+ }
+ if (ds_is_enabled) {
+ mask = ~0x2;
+ adval &= mask; /* unset the 0x2 disable bit */
+ } else {
+ mask = 0x2;
+ adval |= mask; /* set the 0x2 disable bit */
+ }
+ PR_snprintf(acctvalstr, sizeof(acctvalstr), "%lu", adval);
+ slapi_ch_free_string(&mod_bval->bv_val);
+ mod_bval->bv_val = slapi_ch_strdup(acctvalstr);
+ mod_bval->bv_len = strlen(acctvalstr);
+ }
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- sync_acct_disable - %s AD account [%s] - "
+ "new value is [%ld]\n",
+ (ds_is_enabled) ? "enabled" : "disabled",
+ slapi_entry_get_dn_const(ad_entry),
+ adval);
+ }
+ }
+
+ if (direction == ACCT_DISABLE_TO_DS) {
+ if (!isvirt) {
+ char *attrtype = NULL;
+ char *attrval = NULL;
+ attrtype = "nsAccountLock";
+ if (ad_is_enabled) {
+ attrval = NULL; /* will delete the value */
+ } else {
+ attrval = "true";
+ }
+
+ if (update_entry) {
+ slapi_entry_attr_set_charptr(update_entry, attrtype, attrval);
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- sync_acct_disable - %s DS account [%s]\n",
+ (ad_is_enabled) ? "enabled" : "disabled",
+ slapi_entry_get_dn_const(ds_entry));
+ } else { /* do mod */
+ struct berval tmpbval = {0, NULL};
+ Slapi_Mod *smod = slapi_mod_new();
+ slapi_mod_init(smod, 1); /* one element */
+ slapi_mod_set_type(smod, attrtype);
+ if (attrval == NULL) {
+ slapi_mod_set_operation(smod, LDAP_MOD_DELETE|LDAP_MOD_BVALUES);
+ } else {
+ slapi_mod_set_operation(smod, LDAP_MOD_REPLACE|LDAP_MOD_BVALUES);
+ }
+ slapi_mod_add_value(smod, &tmpbval);
+ slapi_mods_add_ldapmod(smods,
+ slapi_mod_get_ldapmod_passout(smod));
+ slapi_mod_free(&smod);
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- sync_acct_disable - %s DS account [%s]\n",
+ (ad_is_enabled) ? "enabled" : "disabled",
+ slapi_entry_get_dn_const(ds_entry));
+ if (do_modify) {
+ *do_modify = 1; /* added mods */
+ }
+ }
+ } else { /* use the virtual attr scheme */
+ char *adddn, *deldn;
+ const char *dsdn;
+ int rc;
+ /* in the case of disabling a user, need to remove that user from
+ the activated group, if in there, and add to the inactivated group
+ however, in the case of enabling a user, we just have to remove
+ the user from the inactivated group, if in there - if the user
+ is not in any group, the user is activated by default
+ */
+ if (ad_is_enabled) {
+ /* add user to activated group, delete from inactivated group */
+ adddn = NULL; /* no group means active by default */
+ deldn = ipaconfig->inactivated_group_dn;
+ } else {
+ /* add user to inactivated group, delete from activated group */
+ adddn = ipaconfig->inactivated_group_dn;
+ deldn = ipaconfig->activated_group_dn;
+ }
+
+ dsdn = slapi_entry_get_dn_const(ds_entry);
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- sync_acct_disable - %s DS account [%s] - "
+ "deldn [%s] adddn [%s]\n",
+ (ad_is_enabled) ? "enabling" : "disabling",
+ slapi_entry_get_dn_const(ds_entry),
+ deldn, adddn);
+ /* first, delete the user from the deldn group - ignore (but log)
+ value not found errors - means the user wasn't there yet */
+ rc = do_group_modify(deldn, "member", LDAP_MOD_DELETE, dsdn);
+ if (rc == LDAP_NO_SUCH_ATTRIBUTE) {
+ /* either the value of the attribute doesn't exist */
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "Could not delete user [%s] from the [%s] group: "
+ "either the user was not in the group already, "
+ "or the group had no members\n",
+ dsdn, deldn);
+ } else if (rc != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "Error deleting user [%s] from the [%s] group: "
+ "(%d - %s)\n", dsdn, deldn, rc,
+ ldap_err2string(rc));
+ }
+ /* next, add the user to the adddn group - ignore (but log)
+ if the user is already in that group */
+ if (adddn) {
+ rc = do_group_modify(adddn, "member", LDAP_MOD_ADD, dsdn);
+ } else {
+ rc = LDAP_SUCCESS;
+ }
+ if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
+ /* user already in that group */
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "Could not add user [%s] to the [%s] group: "
+ "user is already in that group\n",
+ dsdn, adddn);
+ } else if (rc != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, ipa_winsync_plugin_name,
+ "Error adding user [%s] to the [%s] group: "
+ "(%d - %s)\n", dsdn, adddn, rc,
+ ldap_err2string(rc));
+ }
+#ifndef MEMBEROF_WORKS_FOR_INTERNAL_OPS
+ /* memberOf doesn't currently listen for internal operations
+ that change group membership - so we manually set the
+ memberOf attribute in the ds entry - this should not
+ conflict with memberOf */
+ {
+ Slapi_Value *sv = slapi_value_new();
+ slapi_value_init_string(sv, deldn);
+ if (slapi_entry_attr_has_syntax_value(ds_entry,
+ "memberOf", sv)) {
+ if (smods) {
+ slapi_mods_add_string(smods, LDAP_MOD_DELETE,
+ "memberOf", deldn);
+ if (do_modify) {
+ *do_modify = 1; /* added mods */
+ }
+ } else if (update_entry) {
+ slapi_entry_delete_string(update_entry,
+ "memberOf", deldn);
+ }
+ }
+ if (adddn) {
+ slapi_value_set_string(sv, adddn);
+ if (!slapi_entry_attr_has_syntax_value(ds_entry,
+ "memberOf", sv)) {
+ if (smods) {
+ slapi_mods_add_string(smods, LDAP_MOD_ADD,
+ "memberOf", adddn);
+ if (do_modify) {
+ *do_modify = 1; /* added mods */
+ }
+ } else if (update_entry) {
+ slapi_entry_add_string(update_entry,
+ "memberOf", adddn);
+ }
+ }
+ }
+ slapi_value_free(&sv);
+ }
+#endif /* MEMBEROF_WORKS_FOR_INTERNAL_OPS */
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- sync_acct_disable - %s DS account [%s]\n",
+ (ad_is_enabled) ? "enabled" : "disabled",
+ slapi_entry_get_dn_const(ds_entry));
+ }
+ }
+
+ return;
+}
+
+/* if entry does not have attribute type and val, and neither
+ does the smods, add them to the smods */
+static void
+find_and_add_mod(Slapi_Entry *ent, Slapi_Mods *smods, const char *type,
+ const char *val, size_t vallen, int *do_modify)
+{
+ int found = 1;
+ Slapi_Value *sv = slapi_value_new();
+ LDAPMod *mod = NULL;
+
+ slapi_value_init_string(sv, val);
+ if (!slapi_entry_attr_has_syntax_value(ent, type, sv)) {
+ /* entry doesn't have type val - see if there is already
+ a mod in the mods list that adds it replaces it */
+ found = 0; /* not found in entry - see if in mod list */
+ for (mod = slapi_mods_get_first_mod(smods);
+ !found && mod;
+ mod = slapi_mods_get_next_mod(smods)) {
+ int ii;
+ if (PL_strcasecmp(mod->mod_type, type)) {
+ continue; /* skip - not a mod of this type */
+ }
+ if (!(mod->mod_op & (LDAP_MOD_ADD|LDAP_MOD_REPLACE))) {
+ continue; /* skip - not an add or replace op */
+ }
+ /* now see if val is in the list of vals for this mod op */
+ for (ii = 0;
+ !found && mod->mod_bvalues && mod->mod_bvalues[ii];
+ ++ii) {
+ if (mod->mod_bvalues[ii]->bv_val) {
+ found = !PL_strncasecmp(mod->mod_bvalues[ii]->bv_val,
+ val, vallen);
+ }
+ }
+ }
+ }
+ if (!found) {
+ slapi_mods_add_string(smods, LDAP_MOD_ADD, type, val);
+ if (do_modify) {
+ *do_modify = 1; /* added a mod */
+ }
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "<-- find_and_add_mod - added value [%s] "
+ "to attribute [%s] in entry [%s]\n",
+ val, type, slapi_entry_get_dn_const(ent));
+ }
+ slapi_value_free(&sv);
+
+ return;
+}
+
+/*
+ * If force sync is true, any time an entry is being added or modified
+ * in DS, we must ensure the entry has the ntUser objectclass, and that
+ * it has the ntUserDomainID attribute, and the value of that attribute
+ * corresponds to the samAccountName in the AD entry.
+ * ad_entry - entry from AD
+ * ds_entry - entry from DS
+ *
+ * The appropriate modify operation will be added to the given smods
+ * if it doesn't already exist.
+ */
+static void
+do_force_sync(
+ const Slapi_Entry *ad_entry, /* the AD entry */
+ Slapi_Entry *ds_entry, /* the DS entry */
+ Slapi_Mods *smods, /* the mod list for MODIFYs */
+ int *do_modify /* if not NULL, set to true if mods were added */
+)
+{
+ IPA_WinSync_Config *global_ipaconfig = ipa_winsync_get_config();
+ PRBool forceSync;
+
+ slapi_lock_mutex(global_ipaconfig->lock);
+ forceSync = global_ipaconfig->forceSync;
+ slapi_unlock_mutex(global_ipaconfig->lock);
+
+ if (forceSync == PR_FALSE) {
+ return; /* not supported */
+ }
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, ipa_winsync_plugin_name,
+ "do_force_sync - forcing sync of AD entry [%s] "
+ "with DS entry [%s]\n",
+ slapi_entry_get_dn_const(ad_entry),
+ slapi_entry_get_dn_const(ds_entry));
+
+ find_and_add_mod(ds_entry, smods, "objectClass", "ntUser", (size_t)6, do_modify);
+
+ return;
+}
diff --git a/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h b/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h
new file mode 100644
index 000000000..58a9a6c40
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h
@@ -0,0 +1,160 @@
+/** 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:
+ * Rich Megginson <rmeggins@redhat.com>
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifndef IPA_WINSYNC_H
+#define IPA_WINSYNC_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef WINSYNC_TEST_IPA
+#include <slapi-plugin.h>
+#include "winsync-plugin.h"
+#else /* the default */
+#include <dirsrv/slapi-plugin.h>
+#include <dirsrv/winsync-plugin.h>
+#endif /* WINSYNC_TEST_IPA */
+
+#define IPA_WINSYNC_PLUGIN_NAME "ipa-winsync"
+
+typedef struct ipa_winsync_config_struct {
+ Slapi_Mutex *lock; /* for config access */
+ Slapi_Entry *config_e; /* configuration entry */
+ PRBool flatten; /* flatten AD DNs */
+ char *realm_filter;
+ char *realm_attr;
+ char *new_entry_filter;
+ char *new_user_oc_attr; /* don't care about groups for now */
+ char *homedir_prefix_attr;
+ char *default_group_attr;
+ char *default_group_filter;
+ int acct_disable; /* see below for possible values */
+ char *inactivated_filter;
+ char *activated_filter;
+ PRBool forceSync;
+} IPA_WinSync_Config;
+
+/*
+ This is the structure that holds our domain
+ specific configuration
+*/
+typedef struct ipa_winsync_domain_config {
+ Slapi_Entry *domain_e; /* info is stored in this entry */
+ char *realm_name; /* realm name */
+ char *homedir_prefix;
+ char *inactivated_group_dn; /* DN of inactivated group */
+ char *activated_group_dn; /* DN of activated group */
+} IPA_WinSync_Domain_Config;
+
+void ipa_winsync_set_plugin_identity(void * identity);
+void * ipa_winsync_get_plugin_identity();
+
+int ipa_winsync_config( Slapi_Entry *config_e );
+IPA_WinSync_Config *ipa_winsync_get_config( void );
+
+/*
+ * Agreement/domain specific configuration
+ */
+/* return a new domain specific configuration object */
+void *ipa_winsync_config_new_domain(const Slapi_DN *ds_subtree, const Slapi_DN *ad_subtree);
+/* refresh the domain specific configuration object */
+void ipa_winsync_config_refresh_domain(void *cbdata, const Slapi_DN *ds_subtree, const Slapi_DN *ad_subtree);
+/* destroy the domain specific configuration object */
+void ipa_winsync_config_destroy_domain(void *cbdata, const Slapi_DN *ds_subtree, const Slapi_DN *ad_subtree);
+
+/* name of attribute holding the filter to use to
+ find the ipa realm value
+*/
+#define IPA_WINSYNC_REALM_FILTER_ATTR "ipaWinSyncRealmFilter"
+/* name of attribute holding the name of the attribute
+ which contains the ipa realm value
+*/
+#define IPA_WINSYNC_REALM_ATTR_ATTR "ipaWinSyncRealmAttr"
+/* name of attribute holding the filter to use to
+ find the new user template entry
+*/
+#define IPA_WINSYNC_NEW_ENTRY_FILTER_ATTR "ipaWinSyncNewEntryFilter"
+/* name of attribute holding the name of the attribute
+ in the new user template entry which has the list of objectclasses
+*/
+#define IPA_WINSYNC_NEW_USER_OC_ATTR "ipaWinSyncNewUserOCAttr"
+/* name of attribute holding the new user attributes and values */
+#define IPA_WINSYNC_NEW_USER_ATTRS_VALS "ipaWinSyncUserAttr"
+/* name of attribute holding the name of the attribute which
+ has the homeDirectory prefix - suffix is the uid */
+#define IPA_WINSYNC_HOMEDIR_PREFIX_ATTR "ipaWinsyncHomeDirAttr"
+/* name of attribute holding the name of the attribute which is
+ used to get the default posix gidNumber */
+#define IPA_WINSYNC_DEFAULTGROUP_ATTR "ipaWinSyncDefaultGroupAttr"
+/* filter used to find the group with the gid number whose group name
+ is in the IPA_WINSYNC_DEFAULTGROUP_ATTR - the filter will have
+ cn=valueofIPA_WINSYNC_DEFAULTGROUP_ATTR appended to it */
+#define IPA_WINSYNC_DEFAULTGROUP_FILTER_ATTR "ipaWinSyncDefaultGroupFilter"
+/* name of attribute holding boolean value to flatten user dns or not */
+#define IPA_WINSYNC_USER_FLATTEN "ipaWinSyncUserFlatten"
+/* name of attribute holding account disable sync value */
+#define IPA_WINSYNC_ACCT_DISABLE "ipaWinSyncAcctDisable"
+/* possible values of IPA_WINSYNC_ACCT_DISABLE */
+#define IPA_WINSYNC_ACCT_DISABLE_NONE "none"
+#define IPA_WINSYNC_ACCT_DISABLE_TO_AD "to_ad"
+#define IPA_WINSYNC_ACCT_DISABLE_TO_DS "to_ds"
+#define IPA_WINSYNC_ACCT_DISABLE_BOTH "both"
+/* enum representing the values above */
+enum {
+ ACCT_DISABLE_INVALID, /* the invalid value */
+ ACCT_DISABLE_NONE, /* do not sync acct disable status */
+ ACCT_DISABLE_TO_AD, /* sync only from ds to ad */
+ ACCT_DISABLE_TO_DS, /* sync only from ad to ds */
+ ACCT_DISABLE_BOTH /* bi-directional sync */
+};
+/* name of attributes holding the search filters to use to find
+ the DN of the groups that represent inactivated and activated users */
+#define IPA_WINSYNC_INACTIVATED_FILTER "ipaWinSyncInactivatedFilter"
+#define IPA_WINSYNC_ACTIVATED_FILTER "ipaWinSyncActivatedFilter"
+/* name of attribute holding the value of the forceSync parameter -
+ this is a boolean attribute - if true, all users in AD that have
+ a corresponding entry in the DS will be synced - there will be no
+ way to "turn off sync" on individual entries - if this value is
+ false, only users which have the ntUser objectclass and an
+ ntDomainUserID attribute which corresponds to an AD account
+ with the same value for samAccountName will be synced
+*/
+#define IPA_WINSYNC_FORCE_SYNC "ipaWinSyncForceSync"
+#endif /* IPA_WINSYNC_H */