From e30cd6ba42c256d2016db45146d616f329455e86 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 29 Jan 2009 16:26:07 -0500 Subject: Mass tree reorganization for IPAv2. To view previous history of files use: % git log --follow -- renamed: ipa-server/autogen.sh -> autogen.sh renamed: ipa-server/ipa-kpasswd/Makefile.am -> daemons/ipa-kpasswd/Makefile.am renamed: ipa-server/ipa-kpasswd/README -> daemons/ipa-kpasswd/README renamed: ipa-server/ipa-kpasswd/ipa_kpasswd.c -> daemons/ipa-kpasswd/ipa_kpasswd.c renamed: ipa-server/ipa-kpasswd/ipa_kpasswd.init -> daemons/ipa-kpasswd/ipa_kpasswd.init renamed: ipa-server/ipa-slapi-plugins/Makefile.am -> daemons/ipa-slapi-plugins/Makefile.am renamed: ipa-server/ipa-slapi-plugins/README -> daemons/ipa-slapi-plugins/README renamed: ipa-server/ipa-slapi-plugins/dna/Makefile.am -> daemons/ipa-slapi-plugins/dna/Makefile.am renamed: ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif -> daemons/ipa-slapi-plugins/dna/dna-conf.ldif renamed: ipa-server/ipa-slapi-plugins/dna/dna.c -> daemons/ipa-slapi-plugins/dna/dna.c renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile.am -> daemons/ipa-slapi-plugins/ipa-memberof/Makefile.am renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c -> daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h -> daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c -> daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif -> daemons/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am -> daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README -> daemons/ipa-slapi-plugins/ipa-pwd-extop/README renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c -> daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif -> daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/Makefile.am -> daemons/ipa-slapi-plugins/ipa-winsync/Makefile.am renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/README -> daemons/ipa-slapi-plugins/ipa-winsync/README renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h renamed: ipa-server/xmlrpc-server/ipa-rewrite.conf -> install/conf/ipa-rewrite.conf renamed: ipa-server/xmlrpc-server/ipa.conf -> install/conf/ipa.conf renamed: ipa-server/xmlrpc-server/ssbrowser.html -> install/html/ssbrowser.html renamed: ipa-server/xmlrpc-server/unauthorized.html -> install/html/unauthorized.html renamed: ipa-server/ipa-install/share/60ipaconfig.ldif -> install/share/60ipaconfig.ldif renamed: ipa-server/ipa-install/share/60kerberos.ldif -> install/share/60kerberos.ldif renamed: ipa-server/ipa-install/share/60radius.ldif -> install/share/60radius.ldif renamed: ipa-server/ipa-install/share/60samba.ldif -> install/share/60samba.ldif renamed: ipa-server/ipa-install/share/Makefile.am -> install/share/Makefile.am renamed: ipa-server/ipa-install/share/bind.named.conf.template -> install/share/bind.named.conf.template renamed: ipa-server/ipa-install/share/bind.zone.db.template -> install/share/bind.zone.db.template renamed: ipa-server/ipa-install/share/bootstrap-template.ldif -> install/share/bootstrap-template.ldif renamed: ipa-server/ipa-install/share/certmap.conf.template -> install/share/certmap.conf.template renamed: ipa-server/ipa-install/share/default-aci.ldif -> install/share/default-aci.ldif renamed: ipa-server/ipa-install/share/default-keytypes.ldif -> install/share/default-keytypes.ldif renamed: ipa-server/ipa-install/share/dna-posix.ldif -> install/share/dna-posix.ldif renamed: ipa-server/ipa-install/share/encrypted_attribute.ldif -> install/share/encrypted_attribute.ldif renamed: ipa-server/ipa-install/share/fedora-ds.init.patch -> install/share/fedora-ds.init.patch renamed: ipa-server/ipa-install/share/indices.ldif -> install/share/indices.ldif renamed: ipa-server/ipa-install/share/kdc.conf.template -> install/share/kdc.conf.template renamed: ipa-server/ipa-install/share/kerberos.ldif -> install/share/kerberos.ldif renamed: ipa-server/ipa-install/share/krb.con.template -> install/share/krb.con.template renamed: ipa-server/ipa-install/share/krb5.conf.template -> install/share/krb5.conf.template renamed: ipa-server/ipa-install/share/krb5.ini.template -> install/share/krb5.ini.template renamed: ipa-server/ipa-install/share/krbrealm.con.template -> install/share/krbrealm.con.template renamed: ipa-server/ipa-install/share/master-entry.ldif -> install/share/master-entry.ldif renamed: ipa-server/ipa-install/share/memberof-task.ldif -> install/share/memberof-task.ldif renamed: ipa-server/ipa-install/share/ntp.conf.server.template -> install/share/ntp.conf.server.template renamed: ipa-server/ipa-install/share/ntpd.sysconfig.template -> install/share/ntpd.sysconfig.template renamed: ipa-server/ipa-install/share/preferences.html.template -> install/share/preferences.html.template renamed: ipa-server/ipa-install/share/referint-conf.ldif -> install/share/referint-conf.ldif renamed: ipa-server/ipa-install/share/schema_compat.uldif -> install/share/schema_compat.uldif renamed: ipa-server/ipa-install/share/unique-attributes.ldif -> install/share/unique-attributes.ldif renamed: ipa-server/ipa-install/Makefile.am -> install/tools/Makefile.am renamed: ipa-server/ipa-install/README -> install/tools/README renamed: ipa-server/ipa-compat-manage -> install/tools/ipa-compat-manage renamed: ipa-server/ipa-fix-CVE-2008-3274 -> install/tools/ipa-fix-CVE-2008-3274 renamed: ipa-server/ipa-ldap-updater -> install/tools/ipa-ldap-updater renamed: ipa-server/ipa-install/ipa-replica-install -> install/tools/ipa-replica-install renamed: ipa-server/ipa-install/ipa-replica-manage -> install/tools/ipa-replica-manage renamed: ipa-server/ipa-install/ipa-replica-prepare -> install/tools/ipa-replica-prepare renamed: ipa-server/ipa-install/ipa-server-certinstall -> install/tools/ipa-server-certinstall renamed: ipa-server/ipa-install/ipa-server-install -> install/tools/ipa-server-install renamed: ipa-server/ipa-upgradeconfig -> install/tools/ipa-upgradeconfig renamed: ipa-server/ipa-install/ipactl -> install/tools/ipactl renamed: ipa-server/man/Makefile.am -> install/tools/man/Makefile.am renamed: ipa-server/man/ipa-compat-manage.1 -> install/tools/man/ipa-compat-manage.1 renamed: ipa-server/man/ipa-ldap-updater.1 -> install/tools/man/ipa-ldap-updater.1 renamed: ipa-server/man/ipa-replica-install.1 -> install/tools/man/ipa-replica-install.1 renamed: ipa-server/man/ipa-replica-manage.1 -> install/tools/man/ipa-replica-manage.1 renamed: ipa-server/man/ipa-replica-prepare.1 -> install/tools/man/ipa-replica-prepare.1 renamed: ipa-server/man/ipa-server-certinstall.1 -> install/tools/man/ipa-server-certinstall.1 renamed: ipa-server/man/ipa-server-install.1 -> install/tools/man/ipa-server-install.1 renamed: ipa-server/man/ipa_kpasswd.8 -> install/tools/man/ipa_kpasswd.8 renamed: ipa-server/man/ipa_webgui.8 -> install/tools/man/ipa_webgui.8 renamed: ipa-server/man/ipactl.8 -> install/tools/man/ipactl.8 renamed: ipa-server/ipa-install/updates/Makefile.am -> install/updates/Makefile.am renamed: ipa-server/ipa-install/updates/RFC2307bis.update -> install/updates/RFC2307bis.update renamed: ipa-server/ipa-install/updates/RFC4876.update -> install/updates/RFC4876.update renamed: ipa-server/ipa-install/updates/indices.update -> install/updates/indices.update renamed: ipa-server/ipa-install/updates/nss_ldap.update -> install/updates/nss_ldap.update renamed: ipa-server/ipa-install/updates/replication.update -> install/updates/replication.update renamed: ipa-server/ipa-install/updates/winsync_index.update -> install/updates/winsync_index.update renamed: ipa-server/ipaserver/Makefile.am -> ipaserver/install/Makefile.am renamed: ipa-server/ipaserver/__init__.py -> ipaserver/install/__init__.py renamed: ipa-server/ipaserver/bindinstance.py -> ipaserver/install/bindinstance.py renamed: ipa-server/ipaserver/certs.py -> ipaserver/install/certs.py renamed: ipa-server/ipaserver/dsinstance.py -> ipaserver/install/dsinstance.py renamed: ipa-server/ipaserver/httpinstance.py -> ipaserver/install/httpinstance.py renamed: ipa-server/ipaserver/installutils.py -> ipaserver/install/installutils.py renamed: ipa-server/ipaserver/ipaldap.py -> ipaserver/install/ipaldap.py renamed: ipa-server/ipaserver/krbinstance.py -> ipaserver/install/krbinstance.py renamed: ipa-server/ipaserver/ldapupdate.py -> ipaserver/install/ldapupdate.py renamed: ipa-server/ipaserver/ntpinstance.py -> ipaserver/install/ntpinstance.py renamed: ipa-server/ipaserver/replication.py -> ipaserver/install/replication.py renamed: ipa-server/ipaserver/service.py -> ipaserver/install/service.py renamed: ipa-server/selinux/Makefile -> selinux/Makefile renamed: ipa-server/selinux/ipa-server-selinux.spec.in -> selinux/ipa-server-selinux.spec.in renamed: ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.fc -> selinux/ipa_kpasswd/ipa_kpasswd.fc renamed: ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.te -> selinux/ipa_kpasswd/ipa_kpasswd.te renamed: ipa-server/selinux/ipa_webgui/ipa_webgui.fc -> selinux/ipa_webgui/ipa_webgui.fc renamed: ipa-server/selinux/ipa_webgui/ipa_webgui.te -> selinux/ipa_webgui/ipa_webgui.te renamed: ipa-server/version.m4.in -> version.m4.in --- daemons/ipa-kpasswd/Makefile.am | 58 + daemons/ipa-kpasswd/README | 2 + daemons/ipa-kpasswd/ipa_kpasswd.c | 1388 +++++++ daemons/ipa-kpasswd/ipa_kpasswd.init | 83 + daemons/ipa-slapi-plugins/Makefile.am | 16 + daemons/ipa-slapi-plugins/README | 0 daemons/ipa-slapi-plugins/dna/Makefile.am | 42 + daemons/ipa-slapi-plugins/dna/dna-conf.ldif | 14 + daemons/ipa-slapi-plugins/dna/dna.c | 1462 +++++++ daemons/ipa-slapi-plugins/ipa-memberof/Makefile.am | 43 + .../ipa-slapi-plugins/ipa-memberof/ipa-memberof.c | 2244 +++++++++++ .../ipa-slapi-plugins/ipa-memberof/ipa-memberof.h | 100 + .../ipa-memberof/ipa-memberof_config.c | 312 ++ .../ipa-memberof/memberof-conf.ldif | 14 + .../ipa-slapi-plugins/ipa-pwd-extop/Makefile.am | 46 + daemons/ipa-slapi-plugins/ipa-pwd-extop/README | 0 .../ipa-pwd-extop/ipa_pwd_extop.c | 4058 ++++++++++++++++++++ .../ipa-pwd-extop/pwd-extop-conf.ldif | 16 + daemons/ipa-slapi-plugins/ipa-winsync/Makefile.am | 43 + daemons/ipa-slapi-plugins/ipa-winsync/README | 0 .../ipa-winsync/ipa-winsync-conf.ldif | 27 + .../ipa-winsync/ipa-winsync-config.c | 975 +++++ .../ipa-slapi-plugins/ipa-winsync/ipa-winsync.c | 1177 ++++++ .../ipa-slapi-plugins/ipa-winsync/ipa-winsync.h | 160 + 24 files changed, 12280 insertions(+) create mode 100644 daemons/ipa-kpasswd/Makefile.am create mode 100644 daemons/ipa-kpasswd/README create mode 100644 daemons/ipa-kpasswd/ipa_kpasswd.c create mode 100644 daemons/ipa-kpasswd/ipa_kpasswd.init create mode 100644 daemons/ipa-slapi-plugins/Makefile.am create mode 100644 daemons/ipa-slapi-plugins/README create mode 100644 daemons/ipa-slapi-plugins/dna/Makefile.am create mode 100644 daemons/ipa-slapi-plugins/dna/dna-conf.ldif create mode 100644 daemons/ipa-slapi-plugins/dna/dna.c create mode 100644 daemons/ipa-slapi-plugins/ipa-memberof/Makefile.am create mode 100644 daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c create mode 100644 daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h create mode 100644 daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c create mode 100644 daemons/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/README create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif create mode 100644 daemons/ipa-slapi-plugins/ipa-winsync/Makefile.am create mode 100644 daemons/ipa-slapi-plugins/ipa-winsync/README create mode 100644 daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif create mode 100644 daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c create mode 100644 daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c create mode 100644 daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h (limited to 'daemons') 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WITH_MOZLDAP +#include +#else +#define LDAP_DEPRECATED 1 +#include +#endif +#include +#include + +#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 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 +#endif + + +/** + * Distributed Numeric Assignment plug-in + */ + +#include + +#include +#include +#include +#include +/*#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 +#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 + * + * 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 +#endif + +#include + +#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= + * 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 +#endif + +/* + * ipa-memberof.h - memberOf shared definitions + * + */ + +#ifndef _MEMBEROF_H_ +#define _MEMBEROF_H_ + +#include +#include +#include +#include +#include +#include + +/****** 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 +#endif + +/* + * memberof_config.c - configuration-related code for memberOf plug-in + * + */ + +#include + +#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 diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c new file mode 100644 index 000000000..24acc8875 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c @@ -0,0 +1,4058 @@ +/** BEGIN COPYRIGHT BLOCK + * This Program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; version 2 of the License. + * + * This Program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details + * + * You should have received a copy of the GNU General Public License along with + * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA. + * + * In addition, as a special exception, Red Hat, Inc. gives You the additional + * right to link the code of this Program with code not covered under the GNU + * General Public License ("Non-GPL Code") and to distribute linked combinations + * including the two, subject to the limitations in this paragraph. Non-GPL Code + * permitted under this exception must only link to the code of this Program + * through those well defined interfaces identified in the file named EXCEPTION + * found in the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline functions from + * the Approved Interfaces without causing the resulting work to be covered by + * the GNU General Public License. Only Red Hat, Inc. may make changes or + * additions to the list of Approved Interfaces. You must obey the GNU General + * Public License in all respects for all of the Program code and other code + * used in conjunction with the Program except the Non-GPL Code covered by this + * exception. If you modify this file, you may extend this exception to your + * version of the file, but you are not obligated to do so. If you do not wish + * to provide this exception without modification, you must delete this + * exception statement from your version and license this file solely under the + * GPL without exception. + * + * Authors: + * Simo Sorce + * + * Copyright (C) 2005 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* + * Password Modify - LDAP Extended Operation. + * RFC 3062 + * + * + * This plugin implements the "Password Modify - LDAP3" + * extended operation for LDAP. The plugin function is called by + * the server if an LDAP client request contains the OID: + * "1.3.6.1.4.1.4203.1.11.1". + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#define KRB5_PRIVATE 1 +#include +#include +#include +#include +#include +#include + +/* Type of connection for this operation;*/ +#define LDAP_EXTOP_PASSMOD_CONN_SECURE + +/* Uncomment the following #undef FOR TESTING: + * allows non-SSL connections to use the password change extended op */ +/* #undef LDAP_EXTOP_PASSMOD_CONN_SECURE */ + +/* ber tags for the PasswdModifyRequestValue sequence */ +#define LDAP_EXTOP_PASSMOD_TAG_USERID 0x80U +#define LDAP_EXTOP_PASSMOD_TAG_OLDPWD 0x81U +#define LDAP_EXTOP_PASSMOD_TAG_NEWPWD 0x82U + +/* ber tags for the PasswdModifyResponseValue sequence */ +#define LDAP_EXTOP_PASSMOD_TAG_GENPWD 0x80U + +/* OID of the extended operation handled by this plug-in */ +#define EXOP_PASSWD_OID "1.3.6.1.4.1.4203.1.11.1" + +/* OID to retrieve keytabs */ +#define KEYTAB_SET_OID "2.16.840.1.113730.3.8.3.1" +#define KEYTAB_RET_OID "2.16.840.1.113730.3.8.3.2" + +/* krbTicketFlags */ +#define KTF_DISALLOW_POSTDATED 0x00000001 +#define KTF_DISALLOW_FORWARDABLE 0x00000002 +#define KTF_DISALLOW_TGT_BASED 0x00000004 +#define KTF_DISALLOW_RENEWABLE 0x00000008 +#define KTF_DISALLOW_PROXIABLE 0x00000010 +#define KTF_DISALLOW_DUP_SKEY 0x00000020 +#define KTF_DISALLOW_ALL_TIX 0x00000040 +#define KTF_REQUIRES_PRE_AUTH 0x00000080 +#define KTF_REQUIRES_HW_AUTH 0x00000100 +#define KTF_REQUIRES_PWCHANGE 0x00000200 +#define KTF_DISALLOW_SVR 0x00001000 +#define KTF_PWCHANGE_SERVICE 0x00002000 + +/* These are the default enc:salt types if nothing is defined. + * TODO: retrieve the configure set of ecntypes either from the + * kfc.conf file or by synchronizing the the file content into + * the directory */ + +/* Salt types */ +#define KRB5_KDB_SALTTYPE_NORMAL 0 +#define KRB5_KDB_SALTTYPE_V4 1 +#define KRB5_KDB_SALTTYPE_NOREALM 2 +#define KRB5_KDB_SALTTYPE_ONLYREALM 3 +#define KRB5_KDB_SALTTYPE_SPECIAL 4 +#define KRB5_KDB_SALTTYPE_AFS3 5 + +#define KRB5P_SALT_SIZE 16 + +void krb5int_c_free_keyblock_contents(krb5_context context, register krb5_keyblock *key); + +static const char *ipapwd_def_encsalts[] = { + "des3-hmac-sha1:normal", +/* "arcfour-hmac:normal", + "des-hmac-sha1:normal", + "des-cbc-md5:normal", */ + "des-cbc-crc:normal", +/* "des-cbc-crc:v4", + "des-cbc-crc:afs3", */ + NULL +}; + +struct ipapwd_encsalt { + krb5_int32 enc_type; + krb5_int32 salt_type; +}; + +static const char *ipa_realm_dn; +static const char *ipa_pwd_config_dn; +static const char *ipa_changepw_principal_dn; + +#define IPAPWD_PLUGIN_NAME "ipa-pwd-extop" +#define IPAPWD_FEATURE_DESC "IPA Password Manager" +#define IPAPWD_PLUGIN_DESC "IPA Password Extended Operation plugin" + +static Slapi_PluginDesc pdesc = { + IPAPWD_FEATURE_DESC, + "FreeIPA project", + "FreeIPA/1.0", + IPAPWD_PLUGIN_DESC +}; + +static void *ipapwd_plugin_id; + +#define IPA_CHANGETYPE_NORMAL 0 +#define IPA_CHANGETYPE_ADMIN 1 +#define IPA_CHANGETYPE_DSMGR 2 + +struct ipapwd_krbcfg { + krb5_context krbctx; + char *realm; + krb5_keyblock *kmkey; + int num_supp_encsalts; + struct ipapwd_encsalt *supp_encsalts; + int num_pref_encsalts; + struct ipapwd_encsalt *pref_encsalts; + char **passsync_mgrs; + int num_passsync_mgrs; +}; + +static void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg) +{ + struct ipapwd_krbcfg *c = *cfg; + + if (!c) return; + + krb5_free_default_realm(c->krbctx, c->realm); + krb5_free_context(c->krbctx); + free(c->kmkey->contents); + free(c->kmkey); + free(c->supp_encsalts); + free(c->pref_encsalts); + slapi_ch_array_free(c->passsync_mgrs); + free(c); + *cfg = NULL; +}; + +struct ipapwd_data { + Slapi_Entry *target; + char *dn; + char *password; + time_t timeNow; + time_t lastPwChange; + time_t expireTime; + int changetype; + int pwHistoryLen; +}; + +struct ipapwd_krbkeydata { + int32_t type; + struct berval value; +}; + +struct ipapwd_krbkey { + struct ipapwd_krbkeydata *salt; + struct ipapwd_krbkeydata *ekey; + struct berval s2kparams; +}; + +struct ipapwd_keyset { + uint16_t major_vno; + uint16_t minor_vno; + uint32_t kvno; + uint32_t mkvno; + struct ipapwd_krbkey *keys; + int num_keys; +}; + +static void ipapwd_keyset_free(struct ipapwd_keyset **pkset) +{ + struct ipapwd_keyset *kset = *pkset; + int i; + + if (!kset) return; + + for (i = 0; i < kset->num_keys; i++) { + if (kset->keys[i].salt) { + free(kset->keys[i].salt->value.bv_val); + free(kset->keys[i].salt); + } + if (kset->keys[i].ekey) { + free(kset->keys[i].ekey->value.bv_val); + free(kset->keys[i].ekey); + } + free(kset->keys[i].s2kparams.bv_val); + } + free(kset->keys); + free(kset); + *pkset = NULL; +} + +static int filter_keys(struct ipapwd_krbcfg *krbcfg, struct ipapwd_keyset *kset) +{ + int i, j; + + for (i = 0; i < kset->num_keys; i++) { + for (j = 0; j < krbcfg->num_supp_encsalts; j++) { + if (kset->keys[i].ekey->type == + krbcfg->supp_encsalts[j].enc_type) { + break; + } + } + if (j == krbcfg->num_supp_encsalts) { /* not valid */ + + /* free key */ + if (kset->keys[i].ekey) { + free(kset->keys[i].ekey->value.bv_val); + free(kset->keys[i].ekey); + } + if (kset->keys[i].salt) { + free(kset->keys[i].salt->value.bv_val); + free(kset->keys[i].salt); + } + free(kset->keys[i].s2kparams.bv_val); + + /* move all remaining keys up by one */ + kset->num_keys -= 1; + + for (j = i; j < kset->num_keys; j++) { + kset->keys[j] = kset->keys[j + 1]; + } + + /* new key has been moved to this position, make sure + * we do not skip it, by neutralizing next increment */ + i--; + } + } + + return 0; +} + +/* Novell key-format scheme: + + KrbKeySet ::= SEQUENCE { + attribute-major-vno [0] UInt16, + attribute-minor-vno [1] UInt16, + kvno [2] UInt32, + mkvno [3] UInt32 OPTIONAL, + keys [4] SEQUENCE OF KrbKey, + ... + } + + KrbKey ::= SEQUENCE { + salt [0] KrbSalt OPTIONAL, + key [1] EncryptionKey, + s2kparams [2] OCTET STRING OPTIONAL, + ... + } + + KrbSalt ::= SEQUENCE { + type [0] Int32, + salt [1] OCTET STRING OPTIONAL + } + + EncryptionKey ::= SEQUENCE { + keytype [0] Int32, + keyvalue [1] OCTET STRING + } + + */ + +static struct berval *encode_keys(struct ipapwd_keyset *kset) +{ + BerElement *be = NULL; + struct berval *bval = NULL; + int ret, i; + + be = ber_alloc_t(LBER_USE_DER); + + if (!be) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "memory allocation failed\n"); + return NULL; + } + + ret = ber_printf(be, "{t[i]t[i]t[i]t[i]t[{", + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), kset->major_vno, + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), kset->minor_vno, + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 2), kset->kvno, + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 3), kset->mkvno, + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 4)); + if (ret == -1) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "encoding asn1 vno info failed\n"); + goto done; + } + + for (i = 0; i < kset->num_keys; i++) { + + ret = ber_printf(be, "{"); + if (ret == -1) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "encoding asn1 EncryptionKey failed\n"); + goto done; + } + + if (kset->keys[i].salt) { + ret = ber_printf(be, "t[{t[i]", + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), + kset->keys[i].salt->type); + if ((ret != -1) && kset->keys[i].salt->value.bv_len) { + ret = ber_printf(be, "t[o]", + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), + kset->keys[i].salt->value.bv_val, + kset->keys[i].salt->value.bv_len); + } + if (ret != -1) { + ret = ber_printf(be, "}]"); + } + if (ret == -1) { + goto done; + } + } + + ret = ber_printf(be, "t[{t[i]t[o]}]", + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 0), + kset->keys[i].ekey->type, + (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1), + kset->keys[i].ekey->value.bv_val, + kset->keys[i].ekey->value.bv_len); + if (ret == -1) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "encoding asn1 EncryptionKey failed\n"); + goto done; + } + + /* FIXME: s2kparams not supported yet */ + + ret = ber_printf(be, "}"); + if (ret == -1) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "encoding asn1 EncryptionKey failed\n"); + goto done; + } + } + + ret = ber_printf(be, "}]}"); + if (ret == -1) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "encoding asn1 end of sequences failed\n"); + goto done; + } + + ret = ber_flatten(be, &bval); + if (ret == -1) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "flattening asn1 failed\n"); + goto done; + } +done: + ber_free(be, 1); + + return bval; +} + +static int ipapwd_get_cur_kvno(Slapi_Entry *target) +{ + Slapi_Attr *krbPrincipalKey = NULL; + Slapi_ValueSet *svs; + Slapi_Value *sv; + BerElement *be = NULL; + const struct berval *cbval; + ber_tag_t tag, tmp; + ber_int_t tkvno; + int hint; + int kvno; + int ret; + + /* retrieve current kvno and and keys */ + ret = slapi_entry_attr_find(target, "krbPrincipalKey", &krbPrincipalKey); + if (ret != 0) { + return 0; + } + + kvno = 0; + + slapi_attr_get_valueset(krbPrincipalKey, &svs); + hint = slapi_valueset_first_value(svs, &sv); + while (hint != -1) { + cbval = slapi_value_get_berval(sv); + if (!cbval) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Error retrieving berval from Slapi_Value\n"); + goto next; + } + be = ber_init(cbval); + if (!be) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ber_init() failed!\n"); + goto next; + } + + tag = ber_scanf(be, "{xxt[i]", &tmp, &tkvno); + if (tag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Bad OLD key encoding ?!\n"); + ber_free(be, 1); + goto next; + } + + if (tkvno > kvno) { + kvno = tkvno; + } + + ber_free(be, 1); +next: + hint = slapi_valueset_next_value(svs, hint, &sv); + } + + return kvno; +} + +static inline void encode_int16(unsigned int val, unsigned char *p) +{ + p[1] = (val >> 8) & 0xff; + p[0] = (val ) & 0xff; +} + +static Slapi_Value **encrypt_encode_key(struct ipapwd_krbcfg *krbcfg, + struct ipapwd_data *data) +{ + krb5_context krbctx; + char *krbPrincipalName = NULL; + uint32_t krbMaxTicketLife; + int kvno, i; + int krbTicketFlags; + struct berval *bval = NULL; + Slapi_Value **svals = NULL; + krb5_principal princ; + krb5_error_code krberr; + krb5_data pwd; + struct ipapwd_keyset *kset = NULL; + + krbctx = krbcfg->krbctx; + + svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *)); + if (!svals) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "memory allocation failed\n"); + return NULL; + } + + kvno = ipapwd_get_cur_kvno(data->target); + + krbPrincipalName = slapi_entry_attr_get_charptr(data->target, "krbPrincipalName"); + if (!krbPrincipalName) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "no krbPrincipalName present in this entry\n"); + return NULL; + } + + krberr = krb5_parse_name(krbctx, krbPrincipalName, &princ); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_parse_name failed [%s]\n", + krb5_get_error_message(krbctx, krberr)); + goto enc_error; + } + + krbMaxTicketLife = slapi_entry_attr_get_uint(data->target, "krbMaxTicketLife"); + if (krbMaxTicketLife == 0) { + /* FIXME: retrieve the default from config (max_life from kdc.conf) */ + krbMaxTicketLife = 86400; /* just set the default 24h for now */ + } + + krbTicketFlags = slapi_entry_attr_get_int(data->target, "krbTicketFlags"); + + pwd.data = (char *)data->password; + pwd.length = strlen(data->password); + + kset = malloc(sizeof(struct ipapwd_keyset)); + if (!kset) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto enc_error; + } + + /* this encoding assumes all keys have the same kvno */ + /* major-vno = 1 and minor-vno = 1 */ + kset->major_vno = 1; + kset->minor_vno = 1; + /* increment kvno (will be 1 if this is a new entry) */ + kset->kvno = kvno + 1; + /* we also assum mkvno is 0 */ + kset->mkvno = 0; + + kset->num_keys = krbcfg->num_pref_encsalts; + kset->keys = calloc(kset->num_keys, sizeof(struct ipapwd_krbkey)); + if (!kset->keys) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto enc_error; + } + + for (i = 0; i < kset->num_keys; i++) { + krb5_keyblock key; + krb5_data salt; + krb5_octet *ptr; + krb5_data plain; + krb5_enc_data cipher; + size_t len; + const char *p; + + salt.data = NULL; + + switch (krbcfg->pref_encsalts[i].salt_type) { + + case KRB5_KDB_SALTTYPE_ONLYREALM: + + p = strchr(krbPrincipalName, '@'); + if (!p) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "Invalid principal name, no realm found!\n"); + goto enc_error; + } + p++; + salt.data = strdup(p); + if (!salt.data) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "memory allocation failed\n"); + goto enc_error; + } + salt.length = strlen(salt.data); /* final \0 omitted on purpose */ + break; + + case KRB5_KDB_SALTTYPE_NOREALM: + + krberr = krb5_principal2salt_norealm(krbctx, princ, &salt); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_principal2salt failed [%s]\n", + krb5_get_error_message(krbctx, krberr)); + goto enc_error; + } + break; + + case KRB5_KDB_SALTTYPE_NORMAL: + + /* If pre auth is required we can set a random salt, otherwise + * we have to use a more conservative approach and set the salt + * to be REALMprincipal (the concatenation of REALM and principal + * name without any separator) */ +#if 0 + if (krbTicketFlags & KTF_REQUIRES_PRE_AUTH) { + salt.length = KRB5P_SALT_SIZE; + salt.data = malloc(KRB5P_SALT_SIZE); + if (!salt.data) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "memory allocation failed\n"); + goto enc_error; + } + krberr = krb5_c_random_make_octets(krbctx, &salt); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_c_random_make_octets failed [%s]\n", + krb5_get_error_message(krbctx, krberr)); + goto enc_error; + } + } else { +#endif + krberr = krb5_principal2salt(krbctx, princ, &salt); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_principal2salt failed [%s]\n", + krb5_get_error_message(krbctx, krberr)); + goto enc_error; + } +#if 0 + } +#endif + break; + + case KRB5_KDB_SALTTYPE_V4: + salt.length = 0; + break; + + case KRB5_KDB_SALTTYPE_AFS3: + + p = strchr(krbPrincipalName, '@'); + if (!p) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "Invalid principal name, no realm found!\n"); + goto enc_error; + } + p++; + salt.data = strdup(p); + if (!salt.data) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "memory allocation failed\n"); + goto enc_error; + } + salt.length = SALT_TYPE_AFS_LENGTH; /* special value */ + break; + + default: + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "Invalid salt type [%d]\n", krbcfg->pref_encsalts[i].salt_type); + goto enc_error; + } + + /* need to build the key now to manage the AFS salt.length special case */ + krberr = krb5_c_string_to_key(krbctx, krbcfg->pref_encsalts[i].enc_type, &pwd, &salt, &key); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_c_string_to_key failed [%s]\n", + krb5_get_error_message(krbctx, krberr)); + krb5_free_data_contents(krbctx, &salt); + goto enc_error; + } + if (salt.length == SALT_TYPE_AFS_LENGTH) { + salt.length = strlen(salt.data); + } + + krberr = krb5_c_encrypt_length(krbctx, krbcfg->kmkey->enctype, key.length, &len); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_c_string_to_key failed [%s]\n", + krb5_get_error_message(krbctx, krberr)); + krb5int_c_free_keyblock_contents(krbctx, &key); + krb5_free_data_contents(krbctx, &salt); + goto enc_error; + } + + if ((ptr = (krb5_octet *) malloc(2 + len)) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "memory allocation failed\n"); + krb5int_c_free_keyblock_contents(krbctx, &key); + krb5_free_data_contents(krbctx, &salt); + goto enc_error; + } + + encode_int16(key.length, ptr); + + plain.length = key.length; + plain.data = (char *)key.contents; + + cipher.ciphertext.length = len; + cipher.ciphertext.data = (char *)ptr+2; + + krberr = krb5_c_encrypt(krbctx, krbcfg->kmkey, 0, 0, &plain, &cipher); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_c_encrypt failed [%s]\n", + krb5_get_error_message(krbctx, krberr)); + krb5int_c_free_keyblock_contents(krbctx, &key); + krb5_free_data_contents(krbctx, &salt); + free(ptr); + goto enc_error; + } + + /* KrbSalt */ + kset->keys[i].salt = malloc(sizeof(struct ipapwd_krbkeydata)); + if (!kset->keys[i].salt) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + krb5int_c_free_keyblock_contents(krbctx, &key); + free(ptr); + goto enc_error; + } + + kset->keys[i].salt->type = krbcfg->pref_encsalts[i].salt_type; + + if (salt.length) { + kset->keys[i].salt->value.bv_len = salt.length; + kset->keys[i].salt->value.bv_val = salt.data; + } + + /* EncryptionKey */ + kset->keys[i].ekey = malloc(sizeof(struct ipapwd_krbkeydata)); + if (!kset->keys[i].ekey) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + krb5int_c_free_keyblock_contents(krbctx, &key); + free(ptr); + goto enc_error; + } + kset->keys[i].ekey->type = key.enctype; + kset->keys[i].ekey->value.bv_len = len+2; + kset->keys[i].ekey->value.bv_val = malloc(len+2); + if (!kset->keys[i].ekey->value.bv_val) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + krb5int_c_free_keyblock_contents(krbctx, &key); + free(ptr); + goto enc_error; + } + memcpy(kset->keys[i].ekey->value.bv_val, ptr, len+2); + + /* make sure we free the memory used now that we are done with it */ + krb5int_c_free_keyblock_contents(krbctx, &key); + free(ptr); + } + + bval = encode_keys(kset); + if (!bval) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "encoding asn1 KrbSalt failed\n"); + goto enc_error; + } + + svals[0] = slapi_value_new_berval(bval); + if (!svals[0]) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "Converting berval to Slapi_Value\n"); + goto enc_error; + } + + ipapwd_keyset_free(&kset); + krb5_free_principal(krbctx, princ); + slapi_ch_free_string(&krbPrincipalName); + ber_bvfree(bval); + return svals; + +enc_error: + if (kset) ipapwd_keyset_free(&kset); + krb5_free_principal(krbctx, princ); + slapi_ch_free_string(&krbPrincipalName); + if (bval) ber_bvfree(bval); + free(svals); + return NULL; +} + +static void ipapwd_free_slapi_value_array(Slapi_Value ***svals) +{ + Slapi_Value **sv = *svals; + int i; + + if (sv) { + for (i = 0; sv[i]; i++) { + slapi_value_free(&sv[i]); + } + } + + slapi_ch_free((void **)sv); +} + + +struct ntlm_keys { + uint8_t lm[16]; + uint8_t nt[16]; +}; + +#define KTF_LM_HASH 0x01 +#define KTF_NT_HASH 0x02 +#define KTF_DOS_CHARSET "CP850" /* same default as samba */ +#define KTF_UTF8 "UTF-8" +#define KTF_UCS2 "UCS-2LE" + +static const uint8_t parity_table[128] = { + 1, 2, 4, 7, 8, 11, 13, 14, 16, 19, 21, 22, 25, 26, 28, 31, + 32, 35, 37, 38, 41, 42, 44, 47, 49, 50, 52, 55, 56, 59, 61, 62, + 64, 67, 69, 70, 73, 74, 76, 79, 81, 82, 84, 87, 88, 91, 93, 94, + 97, 98,100,103,104,107,109,110,112,115,117,118,121,122,124,127, + 128,131,133,134,137,138,140,143,145,146,148,151,152,155,157,158, + 161,162,164,167,168,171,173,174,176,179,181,182,185,186,188,191, + 193,194,196,199,200,203,205,206,208,211,213,214,217,218,220,223, + 224,227,229,230,233,234,236,239,241,242,244,247,248,251,253,254}; + +static void lm_shuffle(uint8_t *out, uint8_t *in) +{ + out[0] = parity_table[in[0]>>1]; + out[1] = parity_table[((in[0]<<6)|(in[1]>>2)) & 0x7F]; + out[2] = parity_table[((in[1]<<5)|(in[2]>>3)) & 0x7F]; + out[3] = parity_table[((in[2]<<4)|(in[3]>>4)) & 0x7F]; + out[4] = parity_table[((in[3]<<3)|(in[4]>>5)) & 0x7F]; + out[5] = parity_table[((in[4]<<2)|(in[5]>>6)) & 0x7F]; + out[6] = parity_table[((in[5]<<1)|(in[6]>>7)) & 0x7F]; + out[7] = parity_table[in[6] & 0x7F]; +} + +/* create the lm and nt hashes + newPassword: the clear text utf8 password + flags: KTF_LM_HASH | KTF_NT_HASH +*/ +static int encode_ntlm_keys(char *newPasswd, unsigned int flags, struct ntlm_keys *keys) +{ + int ret = 0; + + /* do lanman first */ + if (flags & KTF_LM_HASH) { + iconv_t cd; + size_t cs, il, ol; + char *inc, *outc; + char *upperPasswd; + char *asciiPasswd; + DES_key_schedule schedule; + DES_cblock deskey; + DES_cblock magic = "KGS!@#$%"; + + /* TODO: must store the dos charset somewhere in the directory */ + cd = iconv_open(KTF_DOS_CHARSET, KTF_UTF8); + if (cd == (iconv_t)(-1)) { + ret = -1; + goto done; + } + + /* the lanman password is upper case */ + upperPasswd = (char *)slapi_utf8StrToUpper((unsigned char *)newPasswd); + if (!upperPasswd) { + ret = -1; + goto done; + } + il = strlen(upperPasswd); + + /* an ascii string can only be smaller than or equal to an utf8 one */ + ol = il; + if (ol < 14) ol = 14; + asciiPasswd = calloc(ol+1, 1); + if (!asciiPasswd) { + slapi_ch_free_string(&upperPasswd); + ret = -1; + goto done; + } + + inc = upperPasswd; + outc = asciiPasswd; + cs = iconv(cd, &inc, &il, &outc, &ol); + if (cs == -1) { + ret = -1; + slapi_ch_free_string(&upperPasswd); + free(asciiPasswd); + iconv_close(cd); + goto done; + } + + /* done with these */ + slapi_ch_free_string(&upperPasswd); + iconv_close(cd); + + /* we are interested only in the first 14 ASCII chars for lanman */ + if (strlen(asciiPasswd) > 14) { + asciiPasswd[14] = '\0'; + } + + /* first half */ + lm_shuffle(deskey, (uint8_t *)asciiPasswd); + + DES_set_key_unchecked(&deskey, &schedule); + DES_ecb_encrypt(&magic, (DES_cblock *)keys->lm, &schedule, DES_ENCRYPT); + + /* second half */ + lm_shuffle(deskey, (uint8_t *)&asciiPasswd[7]); + + DES_set_key_unchecked(&deskey, &schedule); + DES_ecb_encrypt(&magic, (DES_cblock *)&(keys->lm[8]), &schedule, DES_ENCRYPT); + + /* done with it */ + free(asciiPasswd); + + } else { + memset(keys->lm, 0, 16); + } + + if (flags & KTF_NT_HASH) { + iconv_t cd; + size_t cs, il, ol, sl; + char *inc, *outc; + char *ucs2Passwd; + MD4_CTX md4ctx; + + /* TODO: must store the dos charset somewhere in the directory */ + cd = iconv_open(KTF_UCS2, KTF_UTF8); + if (cd == (iconv_t)(-1)) { + ret = -1; + goto done; + } + + il = strlen(newPasswd); + + /* an ucs2 string can be at most double than an utf8 one */ + sl = ol = (il+1)*2; + ucs2Passwd = calloc(ol, 1); + if (!ucs2Passwd) { + ret = -1; + goto done; + } + + inc = newPasswd; + outc = ucs2Passwd; + cs = iconv(cd, &inc, &il, &outc, &ol); + if (cs == -1) { + ret = -1; + free(ucs2Passwd); + iconv_close(cd); + goto done; + } + + /* done with it */ + iconv_close(cd); + + /* get the final ucs2 string length */ + sl -= ol; + /* we are interested only in the first 14 wchars for the nt password */ + if (sl > 28) { + sl = 28; + } + + ret = MD4_Init(&md4ctx); + if (ret == 0) { + ret = -1; + free(ucs2Passwd); + goto done; + } + ret = MD4_Update(&md4ctx, ucs2Passwd, sl); + if (ret == 0) { + ret = -1; + free(ucs2Passwd); + goto done; + } + ret = MD4_Final(keys->nt, &md4ctx); + if (ret == 0) { + ret = -1; + free(ucs2Passwd); + goto done; + } + + } else { + memset(keys->nt, 0, 16); + } + + ret = 0; + +done: + return ret; +} + +/* searches the directory and finds the policy closest to the DN */ +/* return 0 on success, -1 on error or if no policy is found */ +static int ipapwd_getPolicy(const char *dn, Slapi_Entry *target, Slapi_Entry **e) +{ + const char *krbPwdPolicyReference; + const char *pdn; + const Slapi_DN *psdn; + Slapi_Backend *be; + Slapi_PBlock *pb = NULL; + char *attrs[] = { "krbMaxPwdLife", "krbMinPwdLife", + "krbPwdMinDiffChars", "krbPwdMinLength", + "krbPwdHistoryLength", NULL}; + Slapi_Entry **es = NULL; + Slapi_Entry *pe = NULL; + char **edn; + int ret, res, dist, rdnc, scope, i; + Slapi_DN *sdn = NULL; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_getPolicy: Searching policy for [%s]\n", dn); + + sdn = slapi_sdn_new_dn_byref(dn); + if (sdn == NULL) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_getPolicy: Out of memory on [%s]\n", dn); + ret = -1; + goto done; + } + + krbPwdPolicyReference = slapi_entry_attr_get_charptr(target, "krbPwdPolicyReference"); + if (krbPwdPolicyReference) { + pdn = krbPwdPolicyReference; + scope = LDAP_SCOPE_BASE; + } else { + /* Find ancestor base DN */ + be = slapi_be_select(sdn); + psdn = slapi_be_getsuffix(be, 0); + if (psdn == NULL) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_getPolicy: Invalid DN [%s]\n", dn); + ret = -1; + goto done; + } + pdn = slapi_sdn_get_dn(psdn); + scope = LDAP_SCOPE_SUBTREE; + } + + *e = NULL; + + pb = slapi_pblock_new(); + slapi_search_internal_set_pb (pb, + pdn, scope, + "(objectClass=krbPwdPolicy)", + attrs, 0, + NULL, /* Controls */ + NULL, /* UniqueID */ + ipapwd_plugin_id, + 0); /* Flags */ + + /* do search the tree */ + ret = slapi_search_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (ret == -1 || res != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_getPolicy: Couldn't find policy, err (%d)\n", + res?res:ret); + ret = -1; + goto done; + } + + /* get entries */ + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es); + if (!es) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_getPolicy: No entries ?!"); + ret = -1; + goto done; + } + + /* count entries */ + for (i = 0; es[i]; i++) /* count */ ; + + /* if there is only one, return that */ + if (i == 1) { + *e = slapi_entry_dup(es[0]); + + ret = 0; + goto done; + } + + /* count number of RDNs in DN */ + edn = ldap_explode_dn(dn, 0); + if (!edn) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_getPolicy: ldap_explode_dn(dn) failed ?!"); + ret = -1; + goto done; + } + for (rdnc = 0; edn[rdnc]; rdnc++) /* count */ ; + ldap_value_free(edn); + + pe = NULL; + dist = -1; + + /* find closest entry */ + for (i = 0; es[i]; i++) { + const Slapi_DN *esdn; + + esdn = slapi_entry_get_sdn_const(es[i]); + if (esdn == NULL) continue; + if (0 == slapi_sdn_compare(esdn, sdn)) { + pe = es[i]; + dist = 0; + break; + } + if (slapi_sdn_issuffix(sdn, esdn)) { + const char *dn1; + char **e1; + int c1; + + dn1 = slapi_sdn_get_dn(esdn); + if (!dn1) continue; + e1 = ldap_explode_dn(dn1, 0); + if (!e1) continue; + for (c1 = 0; e1[c1]; c1++) /* count */ ; + ldap_value_free(e1); + if ((dist == -1) || + ((rdnc - c1) < dist)) { + dist = rdnc - c1; + pe = es[i]; + } + } + if (dist == 0) break; /* found closest */ + } + + if (pe == NULL) { + ret = -1; + goto done; + } + + *e = slapi_entry_dup(pe); + ret = 0; +done: + if (pb) { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + } + if (sdn) slapi_sdn_free(&sdn); + return ret; +} + +#define GENERALIZED_TIME_LENGTH 15 + +static int ipapwd_sv_pw_cmp(const void *pv1, const void *pv2) +{ + const char *pw1 = slapi_value_get_string(*((Slapi_Value **)pv1)); + const char *pw2 = slapi_value_get_string(*((Slapi_Value **)pv2)); + + return strncmp(pw1, pw2, GENERALIZED_TIME_LENGTH); +} + +static Slapi_Value **ipapwd_setPasswordHistory(Slapi_Mods *smods, struct ipapwd_data *data) +{ + Slapi_Value **pH = NULL; + Slapi_Attr *passwordHistory = NULL; + char timestr[GENERALIZED_TIME_LENGTH+1]; + char *histr, *old_pw; + struct tm utctime; + int ret, pc; + + old_pw = slapi_entry_attr_get_charptr(data->target, "userPassword"); + if (!old_pw) { + /* no old password to store, just return */ + return NULL; + } + + if (!gmtime_r(&(data->timeNow), &utctime)) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "failed to retrieve current date (buggy gmtime_r ?)\n"); + return NULL; + } + strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime); + + histr = slapi_ch_smprintf("%s%s", timestr, old_pw); + if (!histr) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "ipapwd_checkPassword: Out of Memory\n"); + return NULL; + } + + /* retrieve current history */ + ret = slapi_entry_attr_find(data->target, "passwordHistory", &passwordHistory); + if (ret == 0) { + int ret, hint, count, i; + const char *pwstr; + Slapi_Value *pw; + + hint = 0; + count = 0; + ret = slapi_attr_get_numvalues(passwordHistory, &count); + /* if we have one */ + if (count > 0 && data->pwHistoryLen > 0) { + pH = calloc(count + 2, sizeof(Slapi_Value *)); + if (!pH) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "ipapwd_checkPassword: Out of Memory\n"); + free(histr); + return NULL; + } + + i = 0; + hint = slapi_attr_first_value(passwordHistory, &pw); + while (hint != -1) { + pwstr = slapi_value_get_string(pw); + /* if shorter than GENERALIZED_TIME_LENGTH, it + * is garbage, we never set timeless entries */ + if (pwstr && + (strlen(pwstr) > GENERALIZED_TIME_LENGTH)) { + pH[i] = pw; + i++; + } + hint = slapi_attr_next_value(passwordHistory, hint, &pw); + } + + qsort(pH, i, sizeof(Slapi_Value *), ipapwd_sv_pw_cmp); + + if (i >= data->pwHistoryLen) { + i = data->pwHistoryLen; + pH[i] = NULL; + i--; + } + + pc = i; + + /* copy only interesting entries */ + for (i = 0; i < pc; i++) { + pH[i] = slapi_value_dup(pH[i]); + if (pH[i] == NULL) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "ipapwd_checkPassword: Out of Memory\n"); + while (i) { + i--; + slapi_value_free(&pH[i]); + } + free(pH); + free(histr); + return NULL; + } + } + } + } + + if (pH == NULL) { + pH = calloc(2, sizeof(Slapi_Value *)); + if (!pH) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "ipapwd_checkPassword: Out of Memory\n"); + free(histr); + return NULL; + } + pc = 0; + } + + /* add new history value */ + pH[pc] = slapi_value_new_string(histr); + + free(histr); + + return pH; +} + +static Slapi_Value *ipapwd_strip_pw_date(Slapi_Value *pw) +{ + const char *pwstr; + + pwstr = slapi_value_get_string(pw); + return slapi_value_new_string(&pwstr[GENERALIZED_TIME_LENGTH]); +} + +#define IPAPWD_POLICY_MASK 0x0FF +#define IPAPWD_POLICY_ERROR 0x100 +#define IPAPWD_POLICY_OK 0 + +/* 90 days default pwd max lifetime */ +#define IPAPWD_DEFAULT_PWDLIFE (90 * 24 *3600) +#define IPAPWD_DEFAULT_MINLEN 0 + +/* check password strenght and history */ +static int ipapwd_CheckPolicy(struct ipapwd_data *data) +{ + char *krbPrincipalExpiration = NULL; + char *krbLastPwdChange = NULL; + char *krbPasswordExpiration = NULL; + int krbMaxPwdLife = IPAPWD_DEFAULT_PWDLIFE; + int krbPwdMinLength = IPAPWD_DEFAULT_MINLEN; + int krbPwdMinDiffChars = 0; + int krbMinPwdLife = 0; + int pwdCharLen = 0; + Slapi_Entry *policy = NULL; + Slapi_Attr *passwordHistory = NULL; + struct tm tm; + int tmp, ret; + char *old_pw; + + /* check account is not expired. Ignore unixtime = 0 (Jan 1 1970) */ + krbPrincipalExpiration = slapi_entry_attr_get_charptr(data->target, "krbPrincipalExpiration"); + if (krbPrincipalExpiration && + (strcasecmp("19700101000000Z", krbPrincipalExpiration) != 0)) { + /* if expiration date is set check it */ + memset(&tm, 0, sizeof(struct tm)); + ret = sscanf(krbPrincipalExpiration, + "%04u%02u%02u%02u%02u%02u", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec); + + if (ret == 6) { + tm.tm_year -= 1900; + tm.tm_mon -= 1; + + if (data->timeNow > timegm(&tm)) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "Account Expired"); + return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDMODNOTALLOWED; + } + } + /* FIXME: else error out ? */ + } + slapi_ch_free_string(&krbPrincipalExpiration); + + /* find the entry with the password policy */ + ret = ipapwd_getPolicy(data->dn, data->target, &policy); + if (ret) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "No password policy"); + goto no_policy; + } + + /* Retrieve Max History Len */ + data->pwHistoryLen = slapi_entry_attr_get_int(policy, "krbPwdHistoryLength"); + + if (data->changetype != IPA_CHANGETYPE_NORMAL) { + /* We must skip policy checks (Admin change) but + * force a password change on the next login. + * But not if Directory Manager */ + if (data->changetype == IPA_CHANGETYPE_ADMIN) { + data->expireTime = data->timeNow; + } + + /* skip policy checks */ + slapi_entry_free(policy); + goto no_policy; + } + + /* first of all check current password, if any */ + old_pw = slapi_entry_attr_get_charptr(data->target, "userPassword"); + if (old_pw) { + Slapi_Value *cpw[2] = {NULL, NULL}; + Slapi_Value *pw; + + cpw[0] = slapi_value_new_string(old_pw); + pw = slapi_value_new_string(data->password); + if (!pw) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "ipapwd_checkPassword: Out of Memory\n"); + slapi_entry_free(policy); + slapi_ch_free_string(&old_pw); + slapi_value_free(&cpw[0]); + slapi_value_free(&pw); + return LDAP_OPERATIONS_ERROR; + } + + ret = slapi_pw_find_sv(cpw, pw); + slapi_ch_free_string(&old_pw); + slapi_value_free(&cpw[0]); + slapi_value_free(&pw); + + if (ret == 0) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_checkPassword: Password in history\n"); + slapi_entry_free(policy); + return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDINHISTORY; + } + } + + krbPasswordExpiration = slapi_entry_attr_get_charptr(data->target, "krbPasswordExpiration"); + krbLastPwdChange = slapi_entry_attr_get_charptr(data->target, "krbLastPwdChange"); + /* if no previous change, it means this is probably a new account + * or imported, log and just ignore */ + if (krbLastPwdChange) { + + memset(&tm, 0, sizeof(struct tm)); + ret = sscanf(krbLastPwdChange, + "%04u%02u%02u%02u%02u%02u", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec); + + if (ret == 6) { + tm.tm_year -= 1900; + tm.tm_mon -= 1; + data->lastPwChange = timegm(&tm); + } + /* FIXME: *else* report an error ? */ + } else { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Warning: Last Password Change Time is not available"); + } + + /* Check min age */ + krbMinPwdLife = slapi_entry_attr_get_int(policy, "krbMinPwdLife"); + /* if no default then treat it as no limit */ + if (krbMinPwdLife != 0) { + + /* check for reset cases */ + if (krbLastPwdChange == NULL || + ((krbPasswordExpiration != NULL) && + strcmp(krbPasswordExpiration, krbLastPwdChange) == 0)) { + /* Expiration and last change time are the same or + * missing this happens only when a password is reset + * by an admin or the account is new or no expiration + * policy is set, PASS */ + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_checkPolicy: Ignore krbMinPwdLife Expiration, not enough info\n"); + + } else if (data->timeNow < data->lastPwChange + krbMinPwdLife) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_checkPolicy: Too soon to change password\n"); + slapi_entry_free(policy); + slapi_ch_free_string(&krbPasswordExpiration); + slapi_ch_free_string(&krbLastPwdChange); + return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDTOOYOUNG; + } + } + + /* free strings or we leak them */ + slapi_ch_free_string(&krbPasswordExpiration); + slapi_ch_free_string(&krbLastPwdChange); + + /* Retrieve min length */ + tmp = slapi_entry_attr_get_int(policy, "krbPwdMinLength"); + if (tmp != 0) { + krbPwdMinLength = tmp; + } + + /* check complexity */ + /* FIXME: this code is partially based on Directory Server code, + * the plan is to merge this code later making it available + * trough a pulic DS API for slapi plugins */ + krbPwdMinDiffChars = slapi_entry_attr_get_int(policy, "krbPwdMinDiffChars"); + if (krbPwdMinDiffChars != 0) { + int num_digits = 0; + int num_alphas = 0; + int num_uppers = 0; + int num_lowers = 0; + int num_specials = 0; + int num_8bit = 0; + int num_repeated = 0; + int max_repeated = 0; + int num_categories = 0; + char *p, *pwd; + + pwd = strdup(data->password); + + /* check character types */ + p = pwd; + while ( p && *p ) + { + if ( ldap_utf8isdigit( p ) ) { + num_digits++; + } else if ( ldap_utf8isalpha( p ) ) { + num_alphas++; + if ( slapi_utf8isLower( (unsigned char *)p ) ) { + num_lowers++; + } else { + num_uppers++; + } + } else { + /* check if this is an 8-bit char */ + if ( *p & 128 ) { + num_8bit++; + } else { + num_specials++; + } + } + + /* check for repeating characters. If this is the + first char of the password, no need to check */ + if ( pwd != p ) { + int len = ldap_utf8len( p ); + char *prev_p = ldap_utf8prev( p ); + + if ( len == ldap_utf8len( prev_p ) ) + { + if ( memcmp( p, prev_p, len ) == 0 ) + { + num_repeated++; + if ( max_repeated < num_repeated ) { + max_repeated = num_repeated; + } + } else { + num_repeated = 0; + } + } else { + num_repeated = 0; + } + } + + p = ldap_utf8next( p ); + } + + free(pwd); + p = pwd = NULL; + + /* tally up the number of character categories */ + if ( num_digits > 0 ) + ++num_categories; + if ( num_uppers > 0 ) + ++num_categories; + if ( num_lowers > 0 ) + ++num_categories; + if ( num_specials > 0 ) + ++num_categories; + if ( num_8bit > 0 ) + ++num_categories; + + /* FIXME: the kerberos plicy schema does not define separated threshold values, + * so just treat anything as a category, we will fix this when we merge + * with DS policies */ + + if (max_repeated > 1) + --num_categories; + + if (num_categories < krbPwdMinDiffChars) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_checkPassword: Password not complex enough\n"); + slapi_entry_free(policy); + return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_INVALIDPWDSYNTAX; + } + } + + /* Check password history */ + ret = slapi_entry_attr_find(data->target, "passwordHistory", &passwordHistory); + if (ret == 0) { + int ret, hint, count, i, j; + const char *pwstr; + Slapi_Value **pH; + Slapi_Value *pw; + + hint = 0; + count = 0; + ret = slapi_attr_get_numvalues(passwordHistory, &count); + /* check history only if we have one */ + if (count > 0 && data->pwHistoryLen > 0) { + pH = calloc(count + 2, sizeof(Slapi_Value *)); + if (!pH) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "ipapwd_checkPassword: Out of Memory\n"); + slapi_entry_free(policy); + return LDAP_OPERATIONS_ERROR; + } + + i = 0; + hint = slapi_attr_first_value(passwordHistory, &pw); + while (hint != -1) { + pwstr = slapi_value_get_string(pw); + /* if shorter than GENERALIZED_TIME_LENGTH, it + * is garbage, we never set timeless entries */ + if (pwstr && + (strlen(pwstr) > GENERALIZED_TIME_LENGTH)) { + pH[i] = pw; + i++; + } + hint = slapi_attr_next_value(passwordHistory, hint, &pw); + } + + qsort(pH, i, sizeof(Slapi_Value *), ipapwd_sv_pw_cmp); + + if (i > data->pwHistoryLen) { + i = data->pwHistoryLen; + pH[i] = NULL; + } + + for (j = 0; pH[j]; j++) { + pH[j] = ipapwd_strip_pw_date(pH[j]); + } + + pw = slapi_value_new_string(data->password); + if (!pw) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "ipapwd_checkPassword: Out of Memory\n"); + slapi_entry_free(policy); + free(pH); + return LDAP_OPERATIONS_ERROR; + } + + ret = slapi_pw_find_sv(pH, pw); + + for (j = 0; pH[j]; j++) { + slapi_value_free(&pH[j]); + } + slapi_value_free(&pw); + free(pH); + + if (ret == 0) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_checkPassword: Password in history\n"); + slapi_entry_free(policy); + return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDINHISTORY; + } + } + } + + /* Calculate max age */ + tmp = slapi_entry_attr_get_int(policy, "krbMaxPwdLife"); + if (tmp != 0) { + krbMaxPwdLife = tmp; + } + + slapi_entry_free(policy); + +no_policy: + + /* check min lenght */ + pwdCharLen = ldap_utf8characters(data->password); + + if (pwdCharLen < krbPwdMinLength) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_checkPassword: Password too short\n"); + return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDTOOSHORT; + } + + if (data->expireTime == 0) { + data->expireTime = data->timeNow + krbMaxPwdLife; + } + + return IPAPWD_POLICY_OK; +} + + +/* Searches the dn in directory, + * If found : fills in slapi_entry structure and returns 0 + * If NOT found : returns the search result as LDAP_NO_SUCH_OBJECT + */ +static int ipapwd_getEntry(const char *dn, Slapi_Entry **e2, char **attrlist) +{ + Slapi_DN *sdn; + int search_result = 0; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_getEntry\n"); + + sdn = slapi_sdn_new_dn_byref(dn); + if ((search_result = slapi_search_internal_get_entry( sdn, attrlist, e2, + ipapwd_plugin_id)) != LDAP_SUCCESS ){ + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "ipapwd_getEntry: No such entry-(%s), err (%d)\n", + dn, search_result); + } + + slapi_sdn_free( &sdn ); + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "<= ipapwd_getEntry: %d\n", search_result); + return search_result; +} + + +/* Construct Mods pblock and perform the modify operation + * Sets result of operation in SLAPI_PLUGIN_INTOP_RESULT + */ +static int ipapwd_apply_mods(const char *dn, Slapi_Mods *mods) +{ + Slapi_PBlock *pb; + int ret; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_apply_mods\n"); + + if (!mods || (slapi_mods_get_num_mods(mods) == 0)) { + return -1; + } + + pb = slapi_pblock_new(); + slapi_modify_internal_set_pb (pb, dn, + slapi_mods_get_ldapmods_byref(mods), + NULL, /* Controls */ + NULL, /* UniqueID */ + ipapwd_plugin_id, /* PluginID */ + 0); /* Flags */ + + ret = slapi_modify_internal_pb (pb); + if (ret) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "WARNING: modify error %d on entry '%s'\n", + ret, dn); + } else { + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret); + + if (ret != LDAP_SUCCESS){ + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "WARNING: modify error %d on entry '%s'\n", + ret, dn); + } else { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "<= ipapwd_apply_mods: Successful\n"); + } + } + + slapi_pblock_destroy(pb); + + return ret; +} + +/* ascii hex output of bytes in "in" + * out len is 32 (preallocated) + * in len is 16 */ +static const char hexchars[] = "0123456789ABCDEF"; +static void hexbuf(char *out, const uint8_t *in) +{ + int i; + + for (i = 0; i < 16; i++) { + out[i*2] = hexchars[in[i] >> 4]; + out[i*2+1] = hexchars[in[i] & 0x0f]; + } +} + +/* Modify the Password attributes of the entry */ +static int ipapwd_SetPassword(struct ipapwd_krbcfg *krbcfg, + struct ipapwd_data *data) +{ + int ret = 0, i = 0; + Slapi_Mods *smods; + Slapi_Value **svals = NULL; + Slapi_Value **pwvals = NULL; + struct tm utctime; + char timestr[GENERALIZED_TIME_LENGTH+1]; + krb5_context krbctx; + krb5_error_code krberr; + char lm[33], nt[33]; + struct ntlm_keys ntlm; + int ntlm_flags = 0; + Slapi_Value *sambaSamAccount; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_SetPassword\n"); + + smods = slapi_mods_new(); + + /* generate kerberos keys to be put into krbPrincipalKey */ + svals = encrypt_encode_key(krbcfg, data); + if (!svals) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "key encryption/encoding failed\n"); + ret = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbPrincipalKey", svals); + + /* change Last Password Change field with the current date */ + if (!gmtime_r(&(data->timeNow), &utctime)) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "failed to retrieve current date (buggy gmtime_r ?)\n"); + ret = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbLastPwdChange", timestr); + + /* set Password Expiration date */ + if (!gmtime_r(&(data->expireTime), &utctime)) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "failed to convert expiration date\n"); + ret = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbPasswordExpiration", timestr); + + sambaSamAccount = slapi_value_new_string("sambaSamAccount"); + if (slapi_entry_attr_has_syntax_value(data->target, "objectClass", sambaSamAccount)) { + /* TODO: retrieve if we want to store the LM hash or not */ + ntlm_flags = KTF_LM_HASH | KTF_NT_HASH; + } + slapi_value_free(&sambaSamAccount); + + if (ntlm_flags) { + char *password = strdup(data->password); + if (encode_ntlm_keys(password, ntlm_flags, &ntlm) != 0) { + free(password); + ret = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + if (ntlm_flags & KTF_LM_HASH) { + hexbuf(lm, ntlm.lm); + lm[32] = '\0'; + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "sambaLMPassword", lm); + } + if (ntlm_flags & KTF_NT_HASH) { + hexbuf(nt, ntlm.nt); + nt[32] = '\0'; + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "sambaNTPassword", nt); + } + free(password); + } + + /* let DS encode the password itself, this allows also other plugins to + * intercept it to perform operations like synchronization with Active + * Directory domains through the replication plugin */ + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "userPassword", data->password); + + /* set password history */ + pwvals = ipapwd_setPasswordHistory(smods, data); + if (pwvals) { + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "passwordHistory", pwvals); + } + + /* FIXME: + * instead of replace we should use a delete/add so that we are + * completely sure nobody else modified the entry meanwhile and + * fail if that's the case */ + + /* commit changes */ + ret = ipapwd_apply_mods(data->dn, smods); + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "<= ipapwd_SetPassword: %d\n", ret); + +free_and_return: + slapi_mods_free(&smods); + ipapwd_free_slapi_value_array(&svals); + ipapwd_free_slapi_value_array(&pwvals); + + return ret; +} + +static int ipapwd_chpwop(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg) +{ + char *bindDN = NULL; + char *authmethod = NULL; + char *dn = NULL; + char *oldPasswd = NULL; + char *newPasswd = NULL; + char *errMesg = NULL; + int ret=0, rc=0, is_root=0; + ber_tag_t tag=0; + ber_len_t len=-1; + struct berval *extop_value = NULL; + BerElement *ber = NULL; + Slapi_Entry *targetEntry=NULL; + char *attrlist[] = {"*", "passwordHistory", NULL }; + struct ipapwd_data pwdata; + + /* Get the ber value of the extended operation */ + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value); + + if ((ber = ber_init(extop_value)) == NULL) + { + errMesg = "PasswdModify Request decode failed.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + /* Format of request to parse + * + * PasswdModifyRequestValue ::= SEQUENCE { + * userIdentity [0] OCTET STRING OPTIONAL + * oldPasswd [1] OCTET STRING OPTIONAL + * newPasswd [2] OCTET STRING OPTIONAL } + * + * The request value field is optional. If it is + * provided, at least one field must be filled in. + */ + + /* ber parse code */ + if ( ber_scanf( ber, "{") == LBER_ERROR ) + { + /* The request field wasn't provided. We'll + * now try to determine the userid and verify + * knowledge of the old password via other + * means. + */ + goto parse_req_done; + } else { + tag = ber_peek_tag( ber, &len); + } + + /* identify userID field by tags */ + if (tag == LDAP_EXTOP_PASSMOD_TAG_USERID ) + { + if (ber_scanf(ber, "a", &dn) == LBER_ERROR) { + slapi_ch_free_string(&dn); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "ber_scanf failed\n"); + errMesg = "ber_scanf failed at userID parse.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + tag = ber_peek_tag(ber, &len); + } + + /* identify oldPasswd field by tags */ + if (tag == LDAP_EXTOP_PASSMOD_TAG_OLDPWD ) + { + if (ber_scanf(ber, "a", &oldPasswd) == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "ber_scanf failed\n"); + errMesg = "ber_scanf failed at oldPasswd parse.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + tag = ber_peek_tag(ber, &len); + } + + /* identify newPasswd field by tags */ + if (tag == LDAP_EXTOP_PASSMOD_TAG_NEWPWD ) + { + if (ber_scanf(ber, "a", &newPasswd) == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "ber_scanf failed\n"); + errMesg = "ber_scanf failed at newPasswd parse.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + } + +parse_req_done: + /* Uncomment for debugging, otherwise we don't want to leak the + * password values into the log... */ + /* LDAPDebug( LDAP_DEBUG_ARGS, "passwd: dn (%s), oldPasswd (%s), + * newPasswd (%s)\n", dn, oldPasswd, newPasswd); */ + + + /* Get Bind DN */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &bindDN); + + /* If the connection is bound anonymously, we must refuse + * to process this operation. */ + if (bindDN == NULL || *bindDN == '\0') { + /* Refuse the operation because they're bound anonymously */ + errMesg = "Anonymous Binds are not allowed.\n"; + rc = LDAP_INSUFFICIENT_ACCESS; + goto free_and_return; + } + + /* A new password was not supplied in the request, and we do not support + * password generation yet. + */ + if (newPasswd == NULL || *newPasswd == '\0') { + errMesg = "Password generation not implemented.\n"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto free_and_return; + } + + if (oldPasswd == NULL || *oldPasswd == '\0') { + /* If user is authenticated, they already gave their password during + the bind operation (or used sasl or client cert auth or OS creds) */ + slapi_pblock_get(pb, SLAPI_CONN_AUTHMETHOD, &authmethod); + if (!authmethod || !strcmp(authmethod, SLAPD_AUTH_NONE)) { + errMesg = "User must be authenticated to the directory server.\n"; + rc = LDAP_INSUFFICIENT_ACCESS; + goto free_and_return; + } + } + + /* Determine the target DN for this operation */ + /* Did they give us a DN ? */ + if (dn == NULL || *dn == '\0') { + /* Get the DN from the bind identity on this connection */ + dn = slapi_ch_strdup(bindDN); + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Missing userIdentity in request, using the bind DN instead.\n"); + } + + slapi_pblock_set( pb, SLAPI_ORIGINAL_TARGET, dn ); + + /* Now we have the DN, look for the entry */ + ret = ipapwd_getEntry(dn, &targetEntry, attrlist); + /* If we can't find the entry, then that's an error */ + if (ret) { + /* Couldn't find the entry, fail */ + errMesg = "No such Entry exists.\n" ; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + + /* First thing to do is to ask access control if the bound identity has + * rights to modify the userpassword attribute on this entry. If not, + * then we fail immediately with insufficient access. This means that + * we don't leak any useful information to the client such as current + * password wrong, etc. + */ + + is_root = slapi_dn_isroot(bindDN); + slapi_pblock_set(pb, SLAPI_REQUESTOR_ISROOT, &is_root); + + /* In order to perform the access control check, we need to select a + * backend (even though we don't actually need it otherwise). + */ + { + Slapi_Backend *be = NULL; + + be = slapi_be_select(slapi_entry_get_sdn(targetEntry)); + if (NULL == be) { + errMesg = "Failed to find backend for target entry"; + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + slapi_pblock_set(pb, SLAPI_BACKEND, be); + } + + ret = slapi_access_allowed( pb, targetEntry, "krbPrincipalKey", NULL, SLAPI_ACL_WRITE ); + if ( ret != LDAP_SUCCESS ) { + errMesg = "Insufficient access rights\n"; + rc = LDAP_INSUFFICIENT_ACCESS; + goto free_and_return; + } + + /* Now we have the entry which we want to modify + * They gave us a password (old), check it against the target entry + * Is the old password valid ? + */ + if (oldPasswd && *oldPasswd) { + /* If user is authenticated, they already gave their password + * during the bind operation (or used sasl or client cert auth + * or OS creds) */ + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "oldPasswd provided, but we will ignore it"); + } + + memset(&pwdata, 0, sizeof(pwdata)); + pwdata.target = targetEntry; + pwdata.dn = dn; + pwdata.password = newPasswd; + pwdata.timeNow = time(NULL); + pwdata.changetype = IPA_CHANGETYPE_NORMAL; + + /* + * (technically strcasecmp to compare DNs is not absolutely correct, + * but it should work for the cases we care about here) + */ + + /* determine type of password change */ + /* special cases */ + if ((strcasecmp(dn, bindDN) != 0) && + (strcasecmp(ipa_changepw_principal_dn, bindDN) != 0)) { + int i; + + pwdata.changetype = IPA_CHANGETYPE_ADMIN; + + for (i = 0; i < krbcfg->num_passsync_mgrs; i++) { + if (strcasecmp(krbcfg->passsync_mgrs[i], bindDN) == 0) { + pwdata.changetype = IPA_CHANGETYPE_DSMGR; + break; + } + } + } + + /* check the policy */ + ret = ipapwd_CheckPolicy(&pwdata); + if (ret) { + errMesg = "Password Fails to meet minimum strength criteria"; + if (ret & IPAPWD_POLICY_ERROR) { + slapi_pwpolicy_make_response_control(pb, -1, -1, ret & IPAPWD_POLICY_MASK); + rc = LDAP_CONSTRAINT_VIOLATION; + } else { + errMesg = "Internal error"; + rc = ret; + } + goto free_and_return; + } + + /* Now we're ready to set the kerberos key material */ + ret = ipapwd_SetPassword(krbcfg, &pwdata); + if (ret != LDAP_SUCCESS) { + /* Failed to modify the password, + * e.g. because insufficient access allowed */ + errMesg = "Failed to update password"; + if (ret > 0) { + rc = ret; + } else { + rc = LDAP_OPERATIONS_ERROR; + } + goto free_and_return; + } + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "<= ipapwd_extop: %d\n", rc); + + /* Free anything that we allocated above */ +free_and_return: + slapi_ch_free_string(&oldPasswd); + slapi_ch_free_string(&newPasswd); + /* Either this is the same pointer that we allocated and set above, + * or whoever used it should have freed it and allocated a new + * value that we need to free here */ + slapi_pblock_get(pb, SLAPI_ORIGINAL_TARGET, &dn); + slapi_ch_free_string(&dn); + slapi_pblock_set(pb, SLAPI_ORIGINAL_TARGET, NULL); + slapi_ch_free_string(&authmethod); + + if (targetEntry) slapi_entry_free(targetEntry); + if (ber) ber_free(ber, 1); + + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", errMesg ? errMesg : "success"); + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + + return SLAPI_PLUGIN_EXTENDED_SENT_RESULT; + +} + +/* Password Modify Extended operation plugin function */ +static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg) +{ + char *bindDN = NULL; + char *serviceName = NULL; + char *errMesg = NULL; + int ret=0, rc=0, is_root=0; + struct berval *extop_value = NULL; + BerElement *ber = NULL; + Slapi_PBlock *pbte = NULL; + Slapi_Entry *targetEntry=NULL; + struct berval *bval = NULL; + Slapi_Value **svals = NULL; + const char *bdn; + const Slapi_DN *bsdn; + Slapi_DN *sdn; + Slapi_Backend *be; + Slapi_Entry **es = NULL; + int scope, res; + char *filter; + char *attrlist[] = {"krbPrincipalKey", "krbLastPwdChange", NULL }; + krb5_context krbctx = NULL; + krb5_principal krbname = NULL; + krb5_error_code krberr; + int i, kvno; + Slapi_Mods *smods; + ber_tag_t rtag, ttmp; + ber_int_t tint; + ber_len_t tlen; + struct ipapwd_keyset *kset = NULL; + struct tm utctime; + char timestr[GENERALIZED_TIME_LENGTH+1]; + time_t time_now = time(NULL); + + svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *)); + if (!svals) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "memory allocation failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + + krberr = krb5_init_context(&krbctx); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_init_context failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + + /* Get Bind DN */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &bindDN); + + /* If the connection is bound anonymously, we must refuse to process + * this operation. */ + if (bindDN == NULL || *bindDN == '\0') { + /* Refuse the operation because they're bound anonymously */ + errMesg = "Anonymous Binds are not allowed.\n"; + rc = LDAP_INSUFFICIENT_ACCESS; + goto free_and_return; + } + + /* Get the ber value of the extended operation */ + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value); + + if ((ber = ber_init(extop_value)) == NULL) + { + errMesg = "KeytabGet Request decode failed.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + /* Format of request to parse + * + * KeytabGetRequest ::= SEQUENCE { + * serviceIdentity OCTET STRING + * keys SEQUENCE OF KrbKey, + * ... + * } + * + * KrbKey ::= SEQUENCE { + * key [0] EncryptionKey, + * salt [1] KrbSalt OPTIONAL, + * s2kparams [2] OCTET STRING OPTIONAL, + * ... + * } + * + * EncryptionKey ::= SEQUENCE { + * keytype [0] Int32, + * keyvalue [1] OCTET STRING + * } + * + * KrbSalt ::= SEQUENCE { + * type [0] Int32, + * salt [1] OCTET STRING OPTIONAL + * } + */ + + /* ber parse code */ + rtag = ber_scanf(ber, "{a{", &serviceName); + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + /* make sure it is a valid name */ + krberr = krb5_parse_name(krbctx, serviceName, &krbname); + if (krberr) { + slapi_ch_free_string(&serviceName); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_parse_name failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } else { + /* invert so that we get the canonical form + * (add REALM if not present for example) */ + char *canonname; + krberr = krb5_unparse_name(krbctx, krbname, &canonname); + if (krberr) { + slapi_ch_free_string(&serviceName); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "krb5_unparse_name failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + slapi_ch_free_string(&serviceName); + serviceName = canonname; + } + + /* check entry before doing any other decoding */ + + /* Find ancestor base DN */ + sdn = slapi_sdn_new_dn_byval(ipa_realm_dn); + be = slapi_be_select(sdn); + slapi_sdn_free(&sdn); + bsdn = slapi_be_getsuffix(be, 0); + if (bsdn == NULL) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Search for Base DN failed\n"); + errMesg = "PrincipalName not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + bdn = slapi_sdn_get_dn(bsdn); + scope = LDAP_SCOPE_SUBTREE; + + /* get Entry by krbPrincipalName */ + filter = slapi_ch_smprintf("(krbPrincipalName=%s)", serviceName); + + pbte = slapi_pblock_new(); + slapi_search_internal_set_pb(pbte, + bdn, scope, filter, attrlist, 0, + NULL, /* Controls */ + NULL, /* UniqueID */ + ipapwd_plugin_id, + 0); /* Flags */ + + /* do search the tree */ + ret = slapi_search_internal_pb(pbte); + slapi_pblock_get(pbte, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (ret == -1 || res != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Search for Principal failed, err (%d)\n", + res?res:ret); + errMesg = "PrincipalName not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + + /* get entries */ + slapi_pblock_get(pbte, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es); + if (!es) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "No entries ?!"); + errMesg = "PrincipalName not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + + /* count entries */ + for (i = 0; es[i]; i++) /* count */ ; + + /* if there is none or more than one, freak out */ + if (i != 1) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Too many entries, or entry no found (%d)", i); + errMesg = "PrincipalName not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + targetEntry = es[0]; + + /* First thing to do is to ask access control if the bound identity has + * rights to modify the userpassword attribute on this entry. If not, + * then we fail immediately with insufficient access. This means that + * we don't leak any useful information to the client such as current + * password wrong, etc. + */ + + is_root = slapi_dn_isroot(bindDN); + slapi_pblock_set(pb, SLAPI_REQUESTOR_ISROOT, &is_root); + + /* In order to perform the access control check, + * we need to select a backend (even though + * we don't actually need it otherwise). + */ + slapi_pblock_set(pb, SLAPI_BACKEND, be); + + /* Access Strategy: + * If the user has WRITE-ONLY access, a new keytab is set on the entry. + */ + + ret = slapi_access_allowed(pb, targetEntry, "krbPrincipalKey", NULL, SLAPI_ACL_WRITE); + if (ret != LDAP_SUCCESS) { + errMesg = "Insufficient access rights\n"; + rc = LDAP_INSUFFICIENT_ACCESS; + goto free_and_return; + } + + /* increment kvno (will be 1 if this is a new entry) */ + kvno = ipapwd_get_cur_kvno(targetEntry) + 1; + + /* ok access allowed, init kset and continue to parse ber buffer */ + + errMesg = "Unable to set key\n"; + rc = LDAP_OPERATIONS_ERROR; + + kset = malloc(sizeof(struct ipapwd_keyset)); + if (!kset) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + + /* this encoding assumes all keys have the same kvno */ + /* major-vno = 1 and minor-vno = 1 */ + kset->major_vno = 1; + kset->minor_vno = 1; + kset->kvno = kvno; + /* we also assum mkvno is 0 */ + kset->mkvno = 0; + + kset->keys = NULL; + kset->num_keys = 0; + + rtag = ber_peek_tag(ber, &tlen); + while (rtag == LBER_SEQUENCE) { + krb5_data plain; + krb5_enc_data cipher; + struct berval tval; + krb5_octet *kdata; + size_t klen; + + i = kset->num_keys; + + if (kset->keys) { + struct ipapwd_krbkey *newset; + + newset = realloc(kset->keys, sizeof(struct ipapwd_krbkey) * (i + 1)); + if (!newset) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + kset->keys = newset; + } else { + kset->keys = malloc(sizeof(struct ipapwd_krbkey)); + if (!kset->keys) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + } + kset->num_keys += 1; + + kset->keys[i].salt = NULL; + kset->keys[i].ekey = NULL; + kset->keys[i].s2kparams.bv_len = 0; + kset->keys[i].s2kparams.bv_val = NULL; + + /* EncryptionKey */ + rtag = ber_scanf(ber, "{t[{t[i]t[o]}]", &ttmp, &ttmp, &tint, &ttmp, &tval); + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + kset->keys[i].ekey = calloc(1, sizeof(struct ipapwd_krbkeydata)); + if (!kset->keys[i].ekey) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + + kset->keys[i].ekey->type = tint; + + plain.length = tval.bv_len; + plain.data = tval.bv_val; + + krberr = krb5_c_encrypt_length(krbctx, krbcfg->kmkey->enctype, plain.length, &klen); + if (krberr) { + free(tval.bv_val); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "krb encryption failed!\n"); + goto free_and_return; + } + + kdata = malloc(2 + klen); + if (!kdata) { + free(tval.bv_val); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + encode_int16(plain.length, kdata); + + kset->keys[i].ekey->value.bv_len = 2 + klen; + kset->keys[i].ekey->value.bv_val = (char *)kdata; + + cipher.ciphertext.length = klen; + cipher.ciphertext.data = (char *)kdata + 2; + + krberr = krb5_c_encrypt(krbctx, krbcfg->kmkey, 0, 0, &plain, &cipher); + if (krberr) { + free(tval.bv_val); + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "krb encryption failed!\n"); + goto free_and_return; + } + + free(tval.bv_val); + + rtag = ber_peek_tag(ber, &tlen); + + /* KrbSalt */ + if (rtag == (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1)) { + + rtag = ber_scanf(ber, "t[{t[i]", &ttmp, &ttmp, &tint); + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + kset->keys[i].salt = calloc(1, sizeof(struct ipapwd_krbkeydata)); + if (!kset->keys[i].salt) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "malloc failed!\n"); + goto free_and_return; + } + + kset->keys[i].salt->type = tint; + + rtag = ber_peek_tag(ber, &tlen); + if (rtag == (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 1)) { + + rtag = ber_scanf(ber, "t[o]}]", &ttmp, &tval); + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + kset->keys[i].salt->value = tval; + + rtag = ber_peek_tag(ber, &tlen); + } + } + + /* FIXME: s2kparams - NOT implemented yet */ + if (rtag == (ber_tag_t)(LBER_CONSTRUCTED | LBER_CLASS_CONTEXT | 2)) { + rtag = ber_scanf(ber, "t[x]}", &ttmp); + } else { + rtag = ber_scanf(ber, "}", &ttmp); + } + if (rtag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", "ber_scanf failed\n"); + errMesg = "Invalid payload, failed to decode.\n"; + rc = LDAP_PROTOCOL_ERROR; + goto free_and_return; + } + + rtag = ber_peek_tag(ber, &tlen); + } + + ber_free(ber, 1); + ber = NULL; + + /* filter un-supported encodings */ + ret = filter_keys(krbcfg, kset); + if (ret) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "keyset filtering failed\n"); + goto free_and_return; + } + + /* check if we have any left */ + if (kset->num_keys == 0) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "keyset filtering rejected all proposed keys\n"); + errMesg = "All enctypes provided are unsupported"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto free_and_return; + } + + smods = slapi_mods_new(); + + /* change Last Password Change field with the current date */ + if (!gmtime_r(&(time_now), &utctime)) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "failed to retrieve current date (buggy gmtime_r ?)\n"); + slapi_mods_free(&smods); + goto free_and_return; + } + strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbLastPwdChange", timestr); + + /* FIXME: set Password Expiration date ? */ +#if 0 + if (!gmtime_r(&(data->expireTime), &utctime)) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "failed to convert expiration date\n"); + slapi_ch_free_string(&randPasswd); + slapi_mods_free(&smods); + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbPasswordExpiration", timestr); +#endif + + bval = encode_keys(kset); + if (!bval) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "encoding asn1 KrbSalt failed\n"); + slapi_mods_free(&smods); + goto free_and_return; + } + + svals[0] = slapi_value_new_berval(bval); + if (!svals[0]) { + slapi_log_error(SLAPI_LOG_FATAL, "ipa_pwd_extop", + "Converting berval to Slapi_Value\n"); + slapi_mods_free(&smods); + goto free_and_return; + } + + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbPrincipalKey", svals); + + /* commit changes */ + ret = ipapwd_apply_mods(slapi_entry_get_dn_const(targetEntry), smods); + + if (ret != LDAP_SUCCESS) { + slapi_mods_free(&smods); + goto free_and_return; + + } + slapi_mods_free(&smods); + + /* Format of response + * + * KeytabGetRequest ::= SEQUENCE { + * new_kvno Int32 + * SEQUENCE OF KeyTypes + * } + * + * * List of accepted enctypes * + * KeyTypes ::= SEQUENCE { + * enctype Int32 + * } + */ + + errMesg = "Internal Error\n"; + rc = LDAP_OPERATIONS_ERROR; + + ber = ber_alloc(); + if (!ber) { + goto free_and_return; + } + + ret = ber_printf(ber, "{i{", (ber_int_t)kvno); + if (ret == -1) { + goto free_and_return; + } + + for (i = 0; i < kset->num_keys; i++) { + ret = ber_printf(ber, "{i}", (ber_int_t)kset->keys[i].ekey->type); + if (ret == -1) { + goto free_and_return; + } + } + ret = ber_printf(ber, "}}"); + if (ret == -1) { + goto free_and_return; + } + + if (ret != -1) { + struct berval *bvp; + LDAPControl new_ctrl = {0}; + + ret = ber_flatten(ber, &bvp); + if (ret == -1) { + goto free_and_return; + } + + new_ctrl.ldctl_oid = KEYTAB_RET_OID; + new_ctrl.ldctl_value = *bvp; + new_ctrl.ldctl_iscritical = 0; + rc= slapi_pblock_set(pb, SLAPI_ADD_RESCONTROL, &new_ctrl); + ber_bvfree(bvp); + } + + /* Free anything that we allocated above */ +free_and_return: + free(serviceName); + if (kset) ipapwd_keyset_free(&kset); + + if (bval) ber_bvfree(bval); + if (ber) ber_free(ber, 1); + + if (pbte) { + slapi_free_search_results_internal(pbte); + slapi_pblock_destroy(pbte); + } + if (svals) { + for (i = 0; svals[i]; i++) { + slapi_value_free(&svals[i]); + } + free(svals); + } + + if (krbname) krb5_free_principal(krbctx, krbname); + if (krbctx) krb5_free_context(krbctx); + + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", errMesg ? errMesg : "success"); + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + + return SLAPI_PLUGIN_EXTENDED_SENT_RESULT; +} + +static int new_ipapwd_encsalt(krb5_context krbctx, const char * const *encsalts, + struct ipapwd_encsalt **es_types, int *num_es_types) +{ + struct ipapwd_encsalt *es; + int nes, i; + + for (i = 0; encsalts[i]; i++) /* count */ ; + es = calloc(i + 1, sizeof(struct ipapwd_encsalt)); + if (!es) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_start", "Out of memory!\n"); + return LDAP_OPERATIONS_ERROR; + } + + for (i = 0, nes = 0; encsalts[i]; i++) { + char *enc, *salt; + krb5_int32 tmpsalt; + krb5_enctype tmpenc; + krb5_boolean similar; + krb5_error_code krberr; + int j; + + enc = strdup(encsalts[i]); + if (!enc) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_start", + "Allocation error\n"); + return LDAP_OPERATIONS_ERROR; + } + salt = strchr(enc, ':'); + if (!salt) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_start", + "Invalid krb5 enc string\n"); + free(enc); + continue; + } + *salt = '\0'; /* null terminate the enc type */ + salt++; /* skip : */ + + krberr = krb5_string_to_enctype(enc, &tmpenc); + if (krberr) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_start", + "Invalid krb5 enctype\n"); + free(enc); + continue; + } + + krberr = krb5_string_to_salttype(salt, &tmpsalt); + for (j = 0; j < nes; j++) { + krb5_c_enctype_compare(krbctx, es[j].enc_type, tmpenc, &similar); + if (similar && (es[j].salt_type == tmpsalt)) { + break; + } + } + + if (j == nes) { + /* not found */ + es[j].enc_type = tmpenc; + es[j].salt_type = tmpsalt; + nes++; + } + + free(enc); + } + + *es_types = es; + *num_es_types = nes; + + return LDAP_SUCCESS; +} + +static struct ipapwd_krbcfg *ipapwd_getConfig(void) +{ + krb5_error_code krberr; + struct ipapwd_krbcfg *config = NULL; + krb5_keyblock *kmkey = NULL; + Slapi_Entry *realm_entry = NULL; + Slapi_Entry *config_entry = NULL; + Slapi_Attr *a; + Slapi_Value *v; + BerElement *be = NULL; + ber_tag_t tag, tmp; + ber_int_t ttype; + const struct berval *bval; + struct berval *mkey = NULL; + char **encsalts; + char *tmpstr; + int i, ret; + + config = calloc(1, sizeof(struct ipapwd_krbcfg)); + if (!config) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "Out of memory!\n"); + goto free_and_error; + } + kmkey = calloc(1, sizeof(krb5_keyblock)); + if (!kmkey) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "Out of memory!\n"); + goto free_and_error; + } + config->kmkey = kmkey; + + krberr = krb5_init_context(&config->krbctx); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "krb5_init_context failed\n"); + goto free_and_error; + } + + ret = krb5_get_default_realm(config->krbctx, &config->realm); + if (ret) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "Failed to get default realm?!\n"); + goto free_and_error; + } + + /* get the Realm Container entry */ + ret = ipapwd_getEntry(ipa_realm_dn, &realm_entry, NULL); + if (ret != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "No realm Entry?\n"); + goto free_and_error; + } + + /*** get the Kerberos Master Key ***/ + + ret = slapi_entry_attr_find(realm_entry, "krbMKey", &a); + if (ret == -1) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "No master key??\n"); + goto free_and_error; + } + + /* there should be only one value here */ + ret = slapi_attr_first_value(a, &v); + if (ret == -1) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "No master key??\n"); + goto free_and_error; + } + + bval = slapi_value_get_berval(v); + if (!bval) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "Error retrieving master key berval\n"); + goto free_and_error; + } + + be = ber_init(bval); + if (!bval) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "ber_init() failed!\n"); + goto free_and_error; + } + + tag = ber_scanf(be, "{i{iO}}", &tmp, &ttype, &mkey); + if (tag == LBER_ERROR) { + slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig", + "Bad Master key encoding ?!\n"); + goto free_and_error; + } + + kmkey->magic = KV5M_KEYBLOCK; + kmkey->enctype = ttype; + kmkey->length = mkey->bv_len; + kmkey->contents = malloc(mkey->bv_len); + if (!kmkey->contents) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "Out of memory!\n"); + goto free_and_error; + } + memcpy(kmkey->contents, mkey->bv_val, mkey->bv_len); + ber_bvfree(mkey); + ber_free(be, 1); + mkey = NULL; + be = NULL; + + /*** get the Supported Enc/Salt types ***/ + + encsalts = slapi_entry_attr_get_charray(realm_entry, "krbSupportedEncSaltTypes"); + if (encsalts) { + ret = new_ipapwd_encsalt(config->krbctx, + (const char * const *)encsalts, + &config->supp_encsalts, + &config->num_supp_encsalts); + slapi_ch_array_free(encsalts); + } else { + slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig", + "No configured salt types use defaults\n"); + ret = new_ipapwd_encsalt(config->krbctx, + ipapwd_def_encsalts, + &config->supp_encsalts, + &config->num_supp_encsalts); + } + if (ret) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "Can't get Supported EncSalt Types\n"); + goto free_and_error; + } + + /*** get the Preferred Enc/Salt types ***/ + + encsalts = slapi_entry_attr_get_charray(realm_entry, "krbDefaultEncSaltTypes"); + if (encsalts) { + ret = new_ipapwd_encsalt(config->krbctx, + (const char * const *)encsalts, + &config->pref_encsalts, + &config->num_pref_encsalts); + slapi_ch_array_free(encsalts); + } else { + slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig", + "No configured salt types use defaults\n"); + ret = new_ipapwd_encsalt(config->krbctx, + ipapwd_def_encsalts, + &config->pref_encsalts, + &config->num_pref_encsalts); + } + if (ret) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "Can't get Preferred EncSalt Types\n"); + goto free_and_error; + } + + slapi_entry_free(realm_entry); + + /* get the Realm Container entry */ + ret = ipapwd_getEntry(ipa_pwd_config_dn, &config_entry, NULL); + if (ret != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "No config Entry? Impossible!\n"); + goto free_and_error; + } + config->passsync_mgrs = slapi_entry_attr_get_charray(config_entry, "passSyncManagersDNs"); + /* now add Directory Manager, it is always added by default */ + tmpstr = slapi_ch_strdup("cn=Directory Manager"); + slapi_ch_array_add(&config->passsync_mgrs, tmpstr); + if (config->passsync_mgrs == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig", + "Out of memory!\n"); + goto free_and_error; + } + for (i = 0; config->passsync_mgrs[i]; i++) /* count */ ; + config->num_passsync_mgrs = i; + + return config; + +free_and_error: + if (mkey) ber_bvfree(mkey); + if (be) ber_free(be, 1); + if (kmkey) { + free(kmkey->contents); + free(kmkey); + } + if (config) { + if (config->krbctx) { + if (config->realm) + krb5_free_default_realm(config->krbctx, config->realm); + krb5_free_context(config->krbctx); + } + free(config->pref_encsalts); + free(config->supp_encsalts); + slapi_ch_array_free(config->passsync_mgrs); + free(config); + } + slapi_entry_free(config_entry); + slapi_entry_free(realm_entry); + return NULL; +} + +#define IPAPWD_CHECK_CONN_SECURE 0x00000001 +#define IPAPWD_CHECK_DN 0x00000002 + +static int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg, + struct ipapwd_krbcfg **config, + int check_flags) +{ + int ret, sasl_ssf, is_ssl; + int rc = LDAP_SUCCESS; + Slapi_Backend *be; + const Slapi_DN *psdn; + Slapi_DN *sdn; + char *dn = NULL; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_gen_checks\n"); + +#ifdef LDAP_EXTOP_PASSMOD_CONN_SECURE + if (check_flags & IPAPWD_CHECK_CONN_SECURE) { + /* Allow password modify only for SSL/TLS established connections and + * connections using SASL privacy layers */ + if (slapi_pblock_get(pb, SLAPI_CONN_SASL_SSF, &sasl_ssf) != 0) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "Could not get SASL SSF from connection\n"); + *errMesg = "Operation requires a secure connection.\n"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (slapi_pblock_get(pb, SLAPI_CONN_IS_SSL_SESSION, &is_ssl) != 0) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "Could not get IS SSL from connection\n"); + *errMesg = "Operation requires a secure connection.\n"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + if ((0 == is_ssl) && (sasl_ssf <= 1)) { + *errMesg = "Operation requires a secure connection.\n"; + rc = LDAP_CONFIDENTIALITY_REQUIRED; + goto done; + } + } +#endif + + if (check_flags & IPAPWD_CHECK_DN) { + /* check we have a valid DN in the pblock or just abort */ + ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + if (ret) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "Tried to change password for an invalid DN [%s]\n", + dn?dn:""); + *errMesg = "Invalid DN"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + sdn = slapi_sdn_new_dn_byref(dn); + if (!sdn) { + *errMesg = "Internal Error"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + be = slapi_be_select(sdn); + slapi_sdn_free(&sdn); + + psdn = slapi_be_getsuffix(be, 0); + if (!psdn) { + *errMesg = "Invalid DN"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + } + + /* get the kerberos context and master key */ + *config = ipapwd_getConfig(); + if (NULL == *config) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "Error Retrieving Master Key"); + *errMesg = "Fatal Internal Error"; + rc = LDAP_OPERATIONS_ERROR; + } + +done: + return rc; +} + +static int ipapwd_extop(Slapi_PBlock *pb) +{ + struct ipapwd_krbcfg *krbcfg = NULL; + char *errMesg = NULL; + char *oid = NULL; + int rc, ret; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_extop\n"); + + rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_CONN_SECURE); + if (rc) { + goto free_and_return; + } + + /* Before going any further, we'll make sure that the right extended + * operation plugin has been called: i.e., the OID shipped whithin the + * extended operation request must match this very plugin's OIDs: + * EXOP_PASSWD_OID or KEYTAB_SET_OID. */ + if (slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &oid) != 0) { + errMesg = "Could not get OID value from request.\n"; + rc = LDAP_OPERATIONS_ERROR; + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", errMesg); + goto free_and_return; + } else { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", + "Received extended operation request with OID %s\n", oid); + } + + if (strcasecmp(oid, EXOP_PASSWD_OID) == 0) { + ret = ipapwd_chpwop(pb, krbcfg); + free_ipapwd_krbcfg(&krbcfg); + return ret; + } + if (strcasecmp(oid, KEYTAB_SET_OID) == 0) { + ret = ipapwd_setkeytab(pb, krbcfg); + free_ipapwd_krbcfg(&krbcfg); + return ret; + } + + errMesg = "Request OID does not match supported OIDs.\n"; + rc = LDAP_OPERATIONS_ERROR; + +free_and_return: + if (krbcfg) free_ipapwd_krbcfg(&krbcfg); + + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", errMesg); + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + + return SLAPI_PLUGIN_EXTENDED_SENT_RESULT; +} + +/***************************************************************************** + * pre/post operations to intercept writes to userPassword + ****************************************************************************/ + +#define IPAPWD_OP_NULL 0 +#define IPAPWD_OP_ADD 1 +#define IPAPWD_OP_MOD 2 +struct ipapwd_operation { + struct ipapwd_data pwdata; + int pwd_op; + int is_krb; +}; + +/* structure with information for each extension */ +struct ipapwd_op_ext { + char *object_name; /* name of the object extended */ + int object_type; /* handle to the extended object */ + int handle; /* extension handle */ +}; + +static struct ipapwd_op_ext ipapwd_op_ext_list; + +static void *ipapwd_op_ext_constructor(void *object, void *parent) +{ + struct ipapwd_operation *ext; + + ext = (struct ipapwd_operation *)slapi_ch_calloc(1, sizeof(struct ipapwd_operation)); + return ext; +} + +static void ipapwd_op_ext_destructor(void *ext, void *object, void *parent) +{ + struct ipapwd_operation *pwdop = (struct ipapwd_operation *)ext; + if (!pwdop) + return; + if (pwdop->pwd_op != IPAPWD_OP_NULL) { + slapi_ch_free_string(&(pwdop->pwdata.dn)); + slapi_ch_free_string(&(pwdop->pwdata.password)); + } + slapi_ch_free((void **)&pwdop); +} + +static int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e, + int *is_root, int *is_krb, int *is_smb, + char *attr, int access) +{ + Slapi_Value *sval; + int rc; + + /* Check ACIs */ + slapi_pblock_get(pb, SLAPI_REQUESTOR_ISROOT, is_root); + + if (!*is_root) { + /* verify this user is allowed to write a user password */ + rc = slapi_access_allowed(pb, e, attr, NULL, access); + if (rc != LDAP_SUCCESS) { + /* we have no business here, the operation will be denied anyway */ + rc = LDAP_SUCCESS; + goto done; + } + } + + /* Check if this is a krbPrincial and therefore needs us to generate other + * hashes */ + sval = slapi_value_new_string("krbPrincipalAux"); + if (!sval) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + *is_krb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval); + slapi_value_free(&sval); + + sval = slapi_value_new_string("sambaSamAccount"); + if (!sval) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + *is_smb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval); + slapi_value_free(&sval); + + rc = LDAP_SUCCESS; + +done: + return rc; +} + +static int ipapwd_preop_gen_hashes(struct ipapwd_krbcfg *krbcfg, + struct ipapwd_operation *pwdop, + char *userpw, + int is_krb, int is_smb, + Slapi_Value ***svals, + char **nthash, char **lmhash) +{ + int rc; + + if (is_krb) { + + pwdop->is_krb = 1; + + *svals = encrypt_encode_key(krbcfg, &pwdop->pwdata); + + if (!*svals) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "key encryption/encoding failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + } + + if (is_smb) { + char lm[33], nt[33]; + struct ntlm_keys ntlm; + int ntlm_flags = 0; + int ret; + + /* TODO: retrieve if we want to store the LM hash or not */ + ntlm_flags = KTF_LM_HASH | KTF_NT_HASH; + + ret = encode_ntlm_keys(userpw, ntlm_flags, &ntlm); + if (ret) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "Failed to generate NT/LM hashes\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + if (ntlm_flags & KTF_LM_HASH) { + hexbuf(lm, ntlm.lm); + lm[32] = '\0'; + *lmhash = slapi_ch_strdup(lm); + } + if (ntlm_flags & KTF_NT_HASH) { + hexbuf(nt, ntlm.nt); + nt[32] = '\0'; + *nthash = slapi_ch_strdup(nt); + } + } + + rc = LDAP_SUCCESS; + +done: + + return rc; +} + +/* PRE ADD Operation: + * Gets the clean text password (fail the operation if the password came + * pre-hashed, unless this is a replicated operation). + * Check user is authorized to add it otherwise just returns, operation will + * fail later anyway. + * Run a password policy check. + * Check if krb or smb hashes are required by testing if the krb or smb + * objectclasses are present. + * store information for the post operation + */ +static int ipapwd_pre_add(Slapi_PBlock *pb) +{ + struct ipapwd_krbcfg *krbcfg = NULL; + char *errMesg = "Internal operations error\n"; + struct slapi_entry *e = NULL; + char *userpw = NULL; + char *dn = NULL; + struct ipapwd_operation *pwdop = NULL; + void *op; + int is_repl_op, is_root, is_krb, is_smb; + int ret, rc; + + slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME, "=> ipapwd_pre_add\n"); + + ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op); + if (ret != 0) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "slapi_pblock_get failed!?\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* pass through if this is a replicated operation */ + if (is_repl_op) + return 0; + + /* retrieve the entry */ + slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); + if (NULL == e) + return 0; + + /* check this is something interesting for us first */ + userpw = slapi_entry_attr_get_charptr(e, SLAPI_USERPWD_ATTR); + if (!userpw) { + /* nothing interesting here */ + return 0; + } + + /* Ok this is interesting, + * Check this is a clear text password, or refuse operation */ + if ('{' == userpw[0]) { + if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) { + char *tmp = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]); + if (NULL == tmp) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "Strdup failed, Out of memory\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + slapi_ch_free_string(&userpw); + userpw = tmp; + } else if (slapi_is_encoded(userpw)) { + + slapi_ch_free_string(&userpw); + + /* check if we have access to the unhashed user password */ + userpw = slapi_entry_attr_get_charptr(e, "unhashed#user#password"); + if (!userpw) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Pre-Encoded passwords are not valid\n"); + errMesg = "Pre-Encoded passwords are not valid\n"; + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + } + } + + rc = ipapwd_entry_checks(pb, e, + &is_root, &is_krb, &is_smb, + NULL, SLAPI_ACL_ADD); + if (rc) { + goto done; + } + + rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN); + if (rc) { + goto done; + } + + /* Get target DN */ + ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + if (ret) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* time to get the operation handler */ + ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if (ret != 0) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "slapi_pblock_get failed!?\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type, + op, ipapwd_op_ext_list.handle); + if (NULL == pwdop) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + pwdop->pwd_op = IPAPWD_OP_ADD; + pwdop->pwdata.password = slapi_ch_strdup(userpw); + + if (is_root) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; + } else { + char *binddn; + int i; + + pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN; + + /* Check Bind DN */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn); + + /* if it is a passsync manager we also need to skip resets */ + for (i = 0; i < krbcfg->num_passsync_mgrs; i++) { + if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; + break; + } + } + } + + pwdop->pwdata.dn = slapi_ch_strdup(dn); + pwdop->pwdata.timeNow = time(NULL); + pwdop->pwdata.target = e; + + ret = ipapwd_CheckPolicy(&pwdop->pwdata); + if (ret) { + errMesg = "Password Fails to meet minimum strength criteria"; + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if (is_krb || is_smb) { + + Slapi_Value **svals = NULL; + char *nt = NULL; + char *lm = NULL; + + rc = ipapwd_preop_gen_hashes(krbcfg, + pwdop, userpw, + is_krb, is_smb, + &svals, &nt, &lm); + if (rc) { + goto done; + } + + if (svals) { + /* add/replace values in existing entry */ + ret = slapi_entry_attr_replace_sv(e, "krbPrincipalKey", svals); + if (ret) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "failed to set encoded values in entry\n"); + rc = LDAP_OPERATIONS_ERROR; + ipapwd_free_slapi_value_array(&svals); + goto done; + } + + ipapwd_free_slapi_value_array(&svals); + } + + if (lm) { + /* set value */ + slapi_entry_attr_set_charptr(e, "sambaLMPassword", lm); + slapi_ch_free_string(&lm); + } + if (nt) { + /* set value */ + slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt); + slapi_ch_free_string(&nt); + } + } + + rc = LDAP_SUCCESS; + +done: + if (pwdop) pwdop->pwdata.target = NULL; + free_ipapwd_krbcfg(&krbcfg); + slapi_ch_free_string(&userpw); + if (rc != LDAP_SUCCESS) { + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + return -1; + } + return 0; +} + +/* PRE MOD Operation: + * Gets the clean text password (fail the operation if the password came + * pre-hashed, unless this is a replicated operation). + * Check user is authorized to add it otherwise just returns, operation will + * fail later anyway. + * Check if krb or smb hashes are required by testing if the krb or smb + * objectclasses are present. + * Run a password policy check. + * store information for the post operation + */ +static int ipapwd_pre_mod(Slapi_PBlock *pb) +{ + struct ipapwd_krbcfg *krbcfg = NULL; + char *errMesg = NULL; + LDAPMod **mods; + Slapi_Mod *smod, *tmod; + Slapi_Mods *smods = NULL; + char *userpw = NULL; + char *unhashedpw = NULL; + char *dn = NULL; + Slapi_DN *tmp_dn; + struct slapi_entry *e = NULL; + struct ipapwd_operation *pwdop = NULL; + void *op; + int is_repl_op, is_pwd_op, is_root, is_krb, is_smb; + int ret, rc; + + slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME, "=> ipapwd_pre_mod\n"); + + ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op); + if (ret != 0) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "slapi_pblock_get failed!?\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* pass through if this is a replicated operation */ + if (is_repl_op) { + rc = LDAP_SUCCESS; + goto done; + } + + /* grab the mods - we'll put them back later with + * our modifications appended + */ + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + smods = slapi_mods_new(); + slapi_mods_init_passin(smods, mods); + + /* In the first pass, + * only check there is anything we are interested in */ + is_pwd_op = 0; + tmod = slapi_mod_new(); + smod = slapi_mods_get_first_smod(smods, tmod); + while (smod) { + struct berval *bv; + const char *type; + int mop; + + type = slapi_mod_get_type(smod); + if (slapi_attr_types_equivalent(type, SLAPI_USERPWD_ATTR)) { + mop = slapi_mod_get_operation(smod); + /* check op filtering out LDAP_MOD_BVALUES */ + switch (mop & 0x0f) { + case LDAP_MOD_ADD: + case LDAP_MOD_REPLACE: + is_pwd_op = 1; + default: + break; + } + } + + /* we check for unahsehd password here so that we are sure to catch them + * early, before further checks go on, this helps checking + * LDAP_MOD_DELETE operations in some corner cases later */ + /* we keep only the last one if multiple are provided for any absurd + * reason */ + if (slapi_attr_types_equivalent(type, "unhashed#user#password")) { + bv = slapi_mod_get_first_value(smod); + if (!bv) { + slapi_mod_free(&tmod); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + slapi_ch_free_string(&unhashedpw); + unhashedpw = slapi_ch_malloc(bv->bv_len+1); + if (!unhashedpw) { + slapi_mod_free(&tmod); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + memcpy(unhashedpw, bv->bv_val, bv->bv_len); + unhashedpw[bv->bv_len] = '\0'; + } + slapi_mod_done(tmod); + smod = slapi_mods_get_next_smod(smods, tmod); + } + slapi_mod_free(&tmod); + + /* If userPassword is not modified we are done here */ + if (! is_pwd_op) { + rc = LDAP_SUCCESS; + goto done; + } + + /* OK swe have something interesting here, start checking for + * pre-requisites */ + + /* Get target DN */ + ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + if (ret) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + tmp_dn = slapi_sdn_new_dn_byref(dn); + if (tmp_dn) { + /* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be + * available but it turns out that is only true if you are + * a dbm backend pre-op plugin - lucky dbm backend pre-op + * plugins. + * I think that is wrong since the entry is useful for filter + * tests and schema checks and this plugin shouldn't be limited + * to a single backend type, but I don't want that fight right + * now so we go get the entry here + * + slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e); + */ + ret = slapi_search_internal_get_entry(tmp_dn, 0, &e, ipapwd_plugin_id); + slapi_sdn_free(&tmp_dn); + if (ret != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Failed tpo retrieve entry?!?\n"); + rc = LDAP_NO_SUCH_OBJECT; + goto done; + } + } + + rc = ipapwd_entry_checks(pb, e, + &is_root, &is_krb, &is_smb, + SLAPI_USERPWD_ATTR, SLAPI_ACL_WRITE); + if (rc) { + goto done; + } + + rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN); + if (rc) { + goto done; + } + + /* run through the mods again and adjust flags if operations affect them */ + tmod = slapi_mod_new(); + smod = slapi_mods_get_first_smod(smods, tmod); + while (smod) { + struct berval *bv; + const char *type; + int mop; + + type = slapi_mod_get_type(smod); + if (slapi_attr_types_equivalent(type, SLAPI_USERPWD_ATTR)) { + mop = slapi_mod_get_operation(smod); + /* check op filtering out LDAP_MOD_BVALUES */ + switch (mop & 0x0f) { + case LDAP_MOD_ADD: + /* FIXME: should we try to track cases where we would end up + * with multiple userPassword entries ?? */ + case LDAP_MOD_REPLACE: + is_pwd_op = 1; + bv = slapi_mod_get_first_value(smod); + if (!bv) { + slapi_mod_free(&tmod); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + slapi_ch_free_string(&userpw); + userpw = slapi_ch_malloc(bv->bv_len+1); + if (!userpw) { + slapi_mod_free(&tmod); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + memcpy(userpw, bv->bv_val, bv->bv_len); + userpw[bv->bv_len] = '\0'; + break; + case LDAP_MOD_DELETE: + /* reset only if we are deleting all values, or the exact + * same value previously set, otherwise we are just trying to + * add a new value and delete an existing one */ + bv = slapi_mod_get_first_value(smod); + if (!bv) { + is_pwd_op = 0; + } else { + if (0 == strncmp(userpw, bv->bv_val, bv->bv_len) || + 0 == strncmp(unhashedpw, bv->bv_val, bv->bv_len)) + is_pwd_op = 0; + } + default: + break; + } + } + + if (slapi_attr_types_equivalent(type, SLAPI_ATTR_OBJECTCLASS)) { + mop = slapi_mod_get_operation(smod); + /* check op filtering out LDAP_MOD_BVALUES */ + switch (mop & 0x0f) { + case LDAP_MOD_REPLACE: + /* if objectclasses are replaced we need to start clean with + * flags, so we sero them out and see if they get set again */ + is_krb = 0; + is_smb = 0; + + case LDAP_MOD_ADD: + bv = slapi_mod_get_first_value(smod); + if (!bv) { + slapi_mod_free(&tmod); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + do { + if (0 == strncasecmp("krbPrincipalAux", bv->bv_val, bv->bv_len)) + is_krb = 1; + if (0 == strncasecmp("sambaSamAccount", bv->bv_val, bv->bv_len)) + is_smb = 1; + } while ((bv = slapi_mod_get_next_value(smod)) != NULL); + + break; + + case LDAP_MOD_DELETE: + /* can this happen for objectclasses ? */ + is_krb = 0; + is_smb = 0; + + default: + break; + } + } + + slapi_mod_done(tmod); + smod = slapi_mods_get_next_smod(smods, tmod); + } + slapi_mod_free(&tmod); + + /* It seem like we have determined that the end result will be deletion of + * the userPassword attribute, so we have no more business here */ + if (! is_pwd_op) { + rc = LDAP_SUCCESS; + goto done; + } + + /* Check this is a clear text password, or refuse operation (only if we need + * to comput other hashes */ + if (! unhashedpw) { + if ('{' == userpw[0]) { + if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) { + unhashedpw = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]); + if (NULL == unhashedpw) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "Strdup failed, Out of memory\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + slapi_ch_free_string(&userpw); + + } else if (slapi_is_encoded(userpw)) { + + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Pre-Encoded passwords are not valid\n"); + errMesg = "Pre-Encoded passwords are not valid\n"; + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + } + } + + /* time to get the operation handler */ + ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if (ret != 0) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "slapi_pblock_get failed!?\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type, + op, ipapwd_op_ext_list.handle); + if (NULL == pwdop) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + pwdop->pwd_op = IPAPWD_OP_MOD; + pwdop->pwdata.password = slapi_ch_strdup(unhashedpw); + pwdop->pwdata.changetype = IPA_CHANGETYPE_NORMAL; + + if (is_root) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; + } else { + char *binddn; + Slapi_DN *bdn, *tdn; + int i; + + /* Check Bind DN */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn); + bdn = slapi_sdn_new_dn_byref(binddn); + tdn = slapi_sdn_new_dn_byref(dn); + + /* if the change is performed by someone else, + * it is an admin change that will require a new + * password change immediately as per our IPA policy */ + if (slapi_sdn_compare(bdn, tdn)) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN; + + /* if it is a passsync manager we also need to skip resets */ + for (i = 0; i < krbcfg->num_passsync_mgrs; i++) { + if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; + break; + } + } + + } + + slapi_sdn_free(&bdn); + slapi_sdn_free(&tdn); + + } + + pwdop->pwdata.dn = slapi_ch_strdup(dn); + pwdop->pwdata.timeNow = time(NULL); + pwdop->pwdata.target = e; + + ret = ipapwd_CheckPolicy(&pwdop->pwdata); + if (ret) { + errMesg = "Password Fails to meet minimum strength criteria"; + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if (is_krb || is_smb) { + + Slapi_Value **svals = NULL; + char *nt = NULL; + char *lm = NULL; + + rc = ipapwd_preop_gen_hashes(krbcfg, + pwdop, unhashedpw, + is_krb, is_smb, + &svals, &nt, &lm); + if (rc) { + goto done; + } + + if (svals) { + /* replace values */ + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, + "krbPrincipalKey", svals); + ipapwd_free_slapi_value_array(&svals); + } + + if (lm) { + /* replace value */ + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "sambaLMPassword", lm); + slapi_ch_free_string(&lm); + } + if (nt) { + /* replace value */ + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "sambaNTPassword", nt); + slapi_ch_free_string(&nt); + } + } + + rc = LDAP_SUCCESS; + +done: + free_ipapwd_krbcfg(&krbcfg); + slapi_ch_free_string(&userpw); /* just to be sure */ + slapi_ch_free_string(&unhashedpw); /* we copied it to pwdop */ + if (e) slapi_entry_free(e); /* this is a copy in this function */ + if (pwdop) pwdop->pwdata.target = NULL; + + /* put back a, possibly modified, set of mods */ + if (smods) { + mods = slapi_mods_get_ldapmods_passout(smods); + slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods); + slapi_mods_free(&smods); + } + + if (rc != LDAP_SUCCESS) { + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + return -1; + } + + return 0; +} + +static int ipapwd_post_op(Slapi_PBlock *pb) +{ + char *errMesg = "Internal operations error\n"; + void *op; + struct ipapwd_operation *pwdop = NULL; + Slapi_Mods *smods; + Slapi_Value **pwvals; + struct tm utctime; + char timestr[GENERALIZED_TIME_LENGTH+1]; + int ret; + + slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME, + "=> ipapwd_post_add\n"); + + /* time to get the operation handler */ + ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if (ret != 0) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "slapi_pblock_get failed!?\n"); + return 0; + } + + pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type, + op, ipapwd_op_ext_list.handle); + if (NULL == pwdop) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Internal error, couldn't find pluginextension ?!\n"); + return 0; + } + + /* not interesting */ + if (IPAPWD_OP_NULL == pwdop->pwd_op) + return 0; + + if ( ! (pwdop->is_krb)) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Not a kerberos user, ignore krb attributes\n"); + return 0; + } + + /* prepare changes that can be made only as root */ + smods = slapi_mods_new(); + + /* change Last Password Change field with the current date */ + if (!gmtime_r(&(pwdop->pwdata.timeNow), &utctime)) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "failed to parse current date (buggy gmtime_r ?)\n"); + goto done; + } + strftime(timestr, GENERALIZED_TIME_LENGTH+1, + "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "krbLastPwdChange", timestr); + + /* set Password Expiration date */ + if (!gmtime_r(&(pwdop->pwdata.expireTime), &utctime)) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "failed to parse expiration date (buggy gmtime_r ?)\n"); + goto done; + } + strftime(timestr, GENERALIZED_TIME_LENGTH+1, + "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "krbPasswordExpiration", timestr); + + /* This was a mod operation on an existing entry, make sure we also update + * the password history based on the entry we saved from the pre-op */ + if (IPAPWD_OP_MOD == pwdop->pwd_op) { + Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(pwdop->pwdata.dn); + if (tmp_dn) { + ret = slapi_search_internal_get_entry(tmp_dn, 0, + &pwdop->pwdata.target, + ipapwd_plugin_id); + slapi_sdn_free(&tmp_dn); + if (ret != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Failed tpo retrieve entry?!?\n"); + goto done; + } + } + pwvals = ipapwd_setPasswordHistory(smods, &pwdop->pwdata); + if (pwvals) { + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, + "passwordHistory", pwvals); + } + } + + ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods); + if (ret) + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Failed to set additional password attributes in the post-op!\n"); + +done: + if (pwdop && pwdop->pwdata.target) slapi_entry_free(pwdop->pwdata.target); + slapi_mods_free(&smods); + return 0; +} + +/* Copied from ipamo_string2filter() + * + * ipapwd_string2filter() + * + * For some reason slapi_str2filter writes to its input + * which means you cannot pass in a string constant + * so this is a fix up function for that + */ +Slapi_Filter *ipapwd_string2filter(char *strfilter) +{ + Slapi_Filter *ret = NULL; + char *idontbelieveit = slapi_ch_strdup(strfilter); + + ret = slapi_str2filter(idontbelieveit); + + slapi_ch_free_string(&idontbelieveit); + + return ret; +} + +/* Init data structs */ +static int ipapwd_start( Slapi_PBlock *pb ) +{ + krb5_context krbctx; + krb5_error_code krberr; + char *realm = NULL; + char *config_dn; + char *partition_dn; + Slapi_Entry *config_entry = NULL; + int ret; + + krberr = krb5_init_context(&krbctx); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_start", "krb5_init_context failed\n"); + return LDAP_OPERATIONS_ERROR; + } + + if (slapi_pblock_get(pb, SLAPI_TARGET_DN, &config_dn) != 0) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "No config DN?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (ipapwd_getEntry(config_dn, &config_entry, NULL) != LDAP_SUCCESS) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "No config Entry?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + partition_dn = slapi_entry_attr_get_charptr(config_entry, "nsslapd-realmtree"); + if (!partition_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Missing partition configuration entry (nsslapd-realmTree)!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = krb5_get_default_realm(krbctx, &realm); + if (ret) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Failed to get default realm?!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + ipa_realm_dn = slapi_ch_smprintf("cn=%s,cn=kerberos,%s", realm, partition_dn); + if (!ipa_realm_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Out of memory ?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ipa_pwd_config_dn = slapi_ch_strdup(config_dn); + if (!ipa_pwd_config_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Out of memory ?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + ipa_changepw_principal_dn = + slapi_ch_smprintf("krbprincipalname=kadmin/changepw@%s,%s", + realm, ipa_realm_dn); + if (!ipa_changepw_principal_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Out of memory ?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = LDAP_SUCCESS; + +done: + free(realm); + krb5_free_context(krbctx); + if (config_entry) slapi_entry_free(config_entry); + return ret; +} + + +static int ipapwd_ext_init() +{ + int ret; + + ipapwd_op_ext_list.object_name = SLAPI_EXT_OPERATION; + + ret = slapi_register_object_extension(IPAPWD_PLUGIN_NAME, + SLAPI_EXT_OPERATION, + ipapwd_op_ext_constructor, + ipapwd_op_ext_destructor, + &ipapwd_op_ext_list.object_type, + &ipapwd_op_ext_list.handle); + + return ret; +} + + +static char *ipapwd_oid_list[] = { + EXOP_PASSWD_OID, + KEYTAB_SET_OID, + NULL +}; + + +static char *ipapwd_name_list[] = { + "Password Change Extended Operation", + "Keytab Retrieval Extended Operation", + NULL +}; + +/* Init pre ops */ +static int ipapwd_pre_init(Slapi_PBlock *pb) +{ + int ret; + + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *)ipapwd_pre_add); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod); + + return ret; +} + +/* Init post ops */ +static int ipapwd_post_init(Slapi_PBlock *pb) +{ + int ret; + + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_op); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op); + + return ret; +} + +/* Initialization function */ +int ipapwd_init( Slapi_PBlock *pb ) +{ + int ret; + + /* Get the arguments appended to the plugin extendedop directive. The first argument + * (after the standard arguments for the directive) should contain the OID of the + * extended operation. */ + + ret = slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &ipapwd_plugin_id); + if ((ret != 0) || (NULL == ipapwd_plugin_id)) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_init", + "Could not get identity or identity was NULL\n"); + return -1; + } + + if (ipapwd_ext_init() != 0) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Object Extension Operation failed\n"); + return -1; + } + + /* Register the plug-in function as an extended operation + * plug-in function that handles the operation identified by + * OID 1.3.6.1.4.1.4203.1.11.1 . Also specify the version of the server + * plug-in */ + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)ipapwd_start); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipapwd_oid_list); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipapwd_name_list); + if (!ret) slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)ipapwd_extop); + if (ret) { + slapi_log_error( SLAPI_LOG_PLUGIN, "ipapwd_init", + "Failed to set plug-in version, function, and OID.\n" ); + return -1; + } + + slapi_register_plugin("preoperation", 1, + "ipapwd_pre_init", ipapwd_pre_init, + "IPA pwd pre ops", NULL, + ipapwd_plugin_id); + + slapi_register_plugin("postoperation", 1, + "ipapwd_post_init", ipapwd_post_init, + "IPA pwd post ops", NULL, + ipapwd_plugin_id); + + return 0; +} diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif b/daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif new file mode 100644 index 000000000..e31a8e79b --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif @@ -0,0 +1,16 @@ +dn: cn=ipa_pwd_extop,cn=plugins,cn=config +changetype: add +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: ipa_pwd_extop +nsslapd-pluginpath: libipa_pwd_extop +nsslapd-plugininitfunc: ipapwd_init +nsslapd-plugintype: extendedop +nsslapd-pluginenabled: on +nsslapd-pluginid: ipa_pwd_extop +nsslapd-pluginversion: 1.0 +nsslapd-pluginvendor: RedHat +nsslapd-plugindescription: Support saving passwords in multiple formats for different consumers (krb5, samba, freeradius, etc.) +nsslapd-plugin-depends-on-type: database +nsslapd-realmTree: $SUFFIX 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 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 + * + * Copyright (C) 2008 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#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 +#include "winsync-plugin.h" +#else +#include +#include +#endif +#include "ipa-winsync.h" + +#include + +#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 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 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 + * + * Copyright (C) 2008 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#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 +#include "winsync-plugin.h" +#else +#include +#include +#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 + * + * 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 +#endif + +#ifdef WINSYNC_TEST_IPA +#include +#include "winsync-plugin.h" +#else /* the default */ +#include +#include +#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 */ -- cgit