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 --- autogen.sh | 196 + 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 + install/conf/ipa-rewrite.conf | 19 + install/conf/ipa.conf | 109 + install/html/ssbrowser.html | 68 + install/html/unauthorized.html | 28 + install/share/60ipaconfig.ldif | 42 + install/share/60kerberos.ldif | 283 ++ install/share/60radius.ldif | 559 +++ install/share/60samba.ldif | 152 + install/share/Makefile.am | 39 + install/share/bind.named.conf.template | 41 + install/share/bind.zone.db.template | 28 + install/share/bootstrap-template.ldif | 202 + install/share/certmap.conf.template | 82 + install/share/default-aci.ldif | 38 + install/share/default-keytypes.ldif | 25 + install/share/dna-posix.ldif | 39 + install/share/encrypted_attribute.ldif | 6 + install/share/fedora-ds.init.patch | 12 + install/share/indices.ldif | 93 + install/share/kdc.conf.template | 15 + install/share/kerberos.ldif | 16 + install/share/krb.con.template | 2 + install/share/krb5.conf.template | 42 + install/share/krb5.ini.template | 19 + install/share/krbrealm.con.template | 3 + install/share/master-entry.ldif | 7 + install/share/memberof-task.ldif | 8 + install/share/ntp.conf.server.template | 50 + install/share/ntpd.sysconfig.template | 8 + install/share/preferences.html.template | 33 + install/share/referint-conf.ldif | 11 + install/share/schema_compat.uldif | 50 + install/share/unique-attributes.ldif | 35 + install/tools/Makefile.am | 24 + install/tools/README | 67 + install/tools/ipa-compat-manage | 171 + install/tools/ipa-fix-CVE-2008-3274 | 524 +++ install/tools/ipa-ldap-updater | 126 + install/tools/ipa-replica-install | 312 ++ install/tools/ipa-replica-manage | 218 ++ install/tools/ipa-replica-prepare | 294 ++ install/tools/ipa-server-certinstall | 157 + install/tools/ipa-server-install | 622 +++ install/tools/ipa-upgradeconfig | 130 + install/tools/ipactl | 57 + install/tools/man/Makefile.am | 27 + install/tools/man/ipa-compat-manage.1 | 45 + install/tools/man/ipa-ldap-updater.1 | 78 + install/tools/man/ipa-replica-install.1 | 41 + install/tools/man/ipa-replica-manage.1 | 70 + install/tools/man/ipa-replica-prepare.1 | 48 + install/tools/man/ipa-server-certinstall.1 | 48 + install/tools/man/ipa-server-install.1 | 81 + install/tools/man/ipa_kpasswd.8 | 36 + install/tools/man/ipa_webgui.8 | 37 + install/tools/man/ipactl.8 | 37 + install/updates/Makefile.am | 19 + install/updates/RFC2307bis.update | 65 + install/updates/RFC4876.update | 146 + install/updates/indices.update | 18 + install/updates/nss_ldap.update | 33 + install/updates/replication.update | 9 + install/updates/winsync_index.update | 10 + ipa-server/autogen.sh | 196 - ipa-server/ipa-compat-manage | 171 - ipa-server/ipa-fix-CVE-2008-3274 | 524 --- ipa-server/ipa-install/Makefile.am | 24 - ipa-server/ipa-install/README | 67 - ipa-server/ipa-install/ipa-replica-install | 312 -- ipa-server/ipa-install/ipa-replica-manage | 218 -- ipa-server/ipa-install/ipa-replica-prepare | 294 -- ipa-server/ipa-install/ipa-server-certinstall | 157 - ipa-server/ipa-install/ipa-server-install | 622 --- ipa-server/ipa-install/ipactl | 57 - ipa-server/ipa-install/share/60ipaconfig.ldif | 42 - ipa-server/ipa-install/share/60kerberos.ldif | 283 -- ipa-server/ipa-install/share/60radius.ldif | 559 --- ipa-server/ipa-install/share/60samba.ldif | 152 - ipa-server/ipa-install/share/Makefile.am | 39 - .../ipa-install/share/bind.named.conf.template | 41 - ipa-server/ipa-install/share/bind.zone.db.template | 28 - .../ipa-install/share/bootstrap-template.ldif | 202 - ipa-server/ipa-install/share/certmap.conf.template | 82 - ipa-server/ipa-install/share/default-aci.ldif | 38 - ipa-server/ipa-install/share/default-keytypes.ldif | 25 - ipa-server/ipa-install/share/dna-posix.ldif | 39 - .../ipa-install/share/encrypted_attribute.ldif | 6 - ipa-server/ipa-install/share/fedora-ds.init.patch | 12 - ipa-server/ipa-install/share/indices.ldif | 93 - ipa-server/ipa-install/share/kdc.conf.template | 15 - ipa-server/ipa-install/share/kerberos.ldif | 16 - ipa-server/ipa-install/share/krb.con.template | 2 - ipa-server/ipa-install/share/krb5.conf.template | 42 - ipa-server/ipa-install/share/krb5.ini.template | 19 - ipa-server/ipa-install/share/krbrealm.con.template | 3 - ipa-server/ipa-install/share/master-entry.ldif | 7 - ipa-server/ipa-install/share/memberof-task.ldif | 8 - .../ipa-install/share/ntp.conf.server.template | 50 - .../ipa-install/share/ntpd.sysconfig.template | 8 - .../ipa-install/share/preferences.html.template | 33 - ipa-server/ipa-install/share/referint-conf.ldif | 11 - ipa-server/ipa-install/share/schema_compat.uldif | 50 - .../ipa-install/share/unique-attributes.ldif | 35 - ipa-server/ipa-install/updates/Makefile.am | 19 - ipa-server/ipa-install/updates/RFC2307bis.update | 65 - ipa-server/ipa-install/updates/RFC4876.update | 146 - ipa-server/ipa-install/updates/indices.update | 18 - ipa-server/ipa-install/updates/nss_ldap.update | 33 - ipa-server/ipa-install/updates/replication.update | 9 - .../ipa-install/updates/winsync_index.update | 10 - ipa-server/ipa-kpasswd/Makefile.am | 58 - ipa-server/ipa-kpasswd/README | 2 - ipa-server/ipa-kpasswd/ipa_kpasswd.c | 1388 ------- ipa-server/ipa-kpasswd/ipa_kpasswd.init | 83 - ipa-server/ipa-ldap-updater | 126 - ipa-server/ipa-slapi-plugins/Makefile.am | 16 - ipa-server/ipa-slapi-plugins/README | 0 ipa-server/ipa-slapi-plugins/dna/Makefile.am | 42 - ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif | 14 - ipa-server/ipa-slapi-plugins/dna/dna.c | 1462 ------- .../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 - ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README | 0 .../ipa-pwd-extop/ipa_pwd_extop.c | 4058 -------------------- .../ipa-pwd-extop/pwd-extop-conf.ldif | 16 - .../ipa-slapi-plugins/ipa-winsync/Makefile.am | 43 - ipa-server/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 - ipa-server/ipa-upgradeconfig | 130 - ipa-server/ipaserver/Makefile.am | 24 - ipa-server/ipaserver/__init__.py | 21 - ipa-server/ipaserver/bindinstance.py | 156 - ipa-server/ipaserver/certs.py | 424 -- ipa-server/ipaserver/dsinstance.py | 479 --- ipa-server/ipaserver/httpinstance.py | 231 -- ipa-server/ipaserver/installutils.py | 248 -- ipa-server/ipaserver/ipaldap.py | 701 ---- ipa-server/ipaserver/krbinstance.py | 428 --- ipa-server/ipaserver/ldapupdate.py | 593 --- ipa-server/ipaserver/ntpinstance.py | 107 - ipa-server/ipaserver/replication.py | 532 --- ipa-server/ipaserver/service.py | 169 - ipa-server/man/Makefile.am | 27 - ipa-server/man/ipa-compat-manage.1 | 45 - ipa-server/man/ipa-ldap-updater.1 | 78 - ipa-server/man/ipa-replica-install.1 | 41 - ipa-server/man/ipa-replica-manage.1 | 70 - ipa-server/man/ipa-replica-prepare.1 | 48 - ipa-server/man/ipa-server-certinstall.1 | 48 - ipa-server/man/ipa-server-install.1 | 81 - ipa-server/man/ipa_kpasswd.8 | 36 - ipa-server/man/ipa_webgui.8 | 37 - ipa-server/man/ipactl.8 | 37 - ipa-server/selinux/Makefile | 28 - ipa-server/selinux/ipa-server-selinux.spec.in | 86 - ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.fc | 9 - ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.te | 71 - ipa-server/selinux/ipa_webgui/ipa_webgui.fc | 11 - ipa-server/selinux/ipa_webgui/ipa_webgui.te | 97 - ipa-server/version.m4.in | 1 - ipa-server/xmlrpc-server/ipa-rewrite.conf | 19 - ipa-server/xmlrpc-server/ipa.conf | 109 - ipa-server/xmlrpc-server/ssbrowser.html | 68 - ipa-server/xmlrpc-server/unauthorized.html | 28 - ipaserver/install/Makefile.am | 24 + ipaserver/install/__init__.py | 21 + ipaserver/install/bindinstance.py | 156 + ipaserver/install/certs.py | 424 ++ ipaserver/install/dsinstance.py | 479 +++ ipaserver/install/httpinstance.py | 231 ++ ipaserver/install/installutils.py | 248 ++ ipaserver/install/ipaldap.py | 701 ++++ ipaserver/install/krbinstance.py | 428 +++ ipaserver/install/ldapupdate.py | 593 +++ ipaserver/install/ntpinstance.py | 107 + ipaserver/install/replication.py | 532 +++ ipaserver/install/service.py | 169 + selinux/Makefile | 28 + selinux/ipa-server-selinux.spec.in | 86 + selinux/ipa_kpasswd/ipa_kpasswd.fc | 9 + selinux/ipa_kpasswd/ipa_kpasswd.te | 71 + selinux/ipa_webgui/ipa_webgui.fc | 11 + selinux/ipa_webgui/ipa_webgui.te | 97 + version.m4.in | 1 + 216 files changed, 22606 insertions(+), 22606 deletions(-) create mode 100755 autogen.sh 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 create mode 100644 install/conf/ipa-rewrite.conf create mode 100644 install/conf/ipa.conf create mode 100644 install/html/ssbrowser.html create mode 100644 install/html/unauthorized.html create mode 100644 install/share/60ipaconfig.ldif create mode 100644 install/share/60kerberos.ldif create mode 100644 install/share/60radius.ldif create mode 100644 install/share/60samba.ldif create mode 100644 install/share/Makefile.am create mode 100644 install/share/bind.named.conf.template create mode 100644 install/share/bind.zone.db.template create mode 100644 install/share/bootstrap-template.ldif create mode 100644 install/share/certmap.conf.template create mode 100644 install/share/default-aci.ldif create mode 100644 install/share/default-keytypes.ldif create mode 100644 install/share/dna-posix.ldif create mode 100644 install/share/encrypted_attribute.ldif create mode 100644 install/share/fedora-ds.init.patch create mode 100644 install/share/indices.ldif create mode 100644 install/share/kdc.conf.template create mode 100644 install/share/kerberos.ldif create mode 100644 install/share/krb.con.template create mode 100644 install/share/krb5.conf.template create mode 100644 install/share/krb5.ini.template create mode 100644 install/share/krbrealm.con.template create mode 100644 install/share/master-entry.ldif create mode 100644 install/share/memberof-task.ldif create mode 100644 install/share/ntp.conf.server.template create mode 100644 install/share/ntpd.sysconfig.template create mode 100644 install/share/preferences.html.template create mode 100644 install/share/referint-conf.ldif create mode 100644 install/share/schema_compat.uldif create mode 100644 install/share/unique-attributes.ldif create mode 100644 install/tools/Makefile.am create mode 100644 install/tools/README create mode 100755 install/tools/ipa-compat-manage create mode 100644 install/tools/ipa-fix-CVE-2008-3274 create mode 100755 install/tools/ipa-ldap-updater create mode 100644 install/tools/ipa-replica-install create mode 100755 install/tools/ipa-replica-manage create mode 100644 install/tools/ipa-replica-prepare create mode 100644 install/tools/ipa-server-certinstall create mode 100644 install/tools/ipa-server-install create mode 100644 install/tools/ipa-upgradeconfig create mode 100644 install/tools/ipactl create mode 100644 install/tools/man/Makefile.am create mode 100644 install/tools/man/ipa-compat-manage.1 create mode 100644 install/tools/man/ipa-ldap-updater.1 create mode 100644 install/tools/man/ipa-replica-install.1 create mode 100644 install/tools/man/ipa-replica-manage.1 create mode 100644 install/tools/man/ipa-replica-prepare.1 create mode 100644 install/tools/man/ipa-server-certinstall.1 create mode 100644 install/tools/man/ipa-server-install.1 create mode 100644 install/tools/man/ipa_kpasswd.8 create mode 100644 install/tools/man/ipa_webgui.8 create mode 100644 install/tools/man/ipactl.8 create mode 100644 install/updates/Makefile.am create mode 100644 install/updates/RFC2307bis.update create mode 100644 install/updates/RFC4876.update create mode 100644 install/updates/indices.update create mode 100644 install/updates/nss_ldap.update create mode 100644 install/updates/replication.update create mode 100644 install/updates/winsync_index.update delete mode 100755 ipa-server/autogen.sh delete mode 100755 ipa-server/ipa-compat-manage delete mode 100644 ipa-server/ipa-fix-CVE-2008-3274 delete mode 100644 ipa-server/ipa-install/Makefile.am delete mode 100644 ipa-server/ipa-install/README delete mode 100644 ipa-server/ipa-install/ipa-replica-install delete mode 100755 ipa-server/ipa-install/ipa-replica-manage delete mode 100644 ipa-server/ipa-install/ipa-replica-prepare delete mode 100644 ipa-server/ipa-install/ipa-server-certinstall delete mode 100644 ipa-server/ipa-install/ipa-server-install delete mode 100644 ipa-server/ipa-install/ipactl delete mode 100644 ipa-server/ipa-install/share/60ipaconfig.ldif delete mode 100644 ipa-server/ipa-install/share/60kerberos.ldif delete mode 100644 ipa-server/ipa-install/share/60radius.ldif delete mode 100644 ipa-server/ipa-install/share/60samba.ldif delete mode 100644 ipa-server/ipa-install/share/Makefile.am delete mode 100644 ipa-server/ipa-install/share/bind.named.conf.template delete mode 100644 ipa-server/ipa-install/share/bind.zone.db.template delete mode 100644 ipa-server/ipa-install/share/bootstrap-template.ldif delete mode 100644 ipa-server/ipa-install/share/certmap.conf.template delete mode 100644 ipa-server/ipa-install/share/default-aci.ldif delete mode 100644 ipa-server/ipa-install/share/default-keytypes.ldif delete mode 100644 ipa-server/ipa-install/share/dna-posix.ldif delete mode 100644 ipa-server/ipa-install/share/encrypted_attribute.ldif delete mode 100644 ipa-server/ipa-install/share/fedora-ds.init.patch delete mode 100644 ipa-server/ipa-install/share/indices.ldif delete mode 100644 ipa-server/ipa-install/share/kdc.conf.template delete mode 100644 ipa-server/ipa-install/share/kerberos.ldif delete mode 100644 ipa-server/ipa-install/share/krb.con.template delete mode 100644 ipa-server/ipa-install/share/krb5.conf.template delete mode 100644 ipa-server/ipa-install/share/krb5.ini.template delete mode 100644 ipa-server/ipa-install/share/krbrealm.con.template delete mode 100644 ipa-server/ipa-install/share/master-entry.ldif delete mode 100644 ipa-server/ipa-install/share/memberof-task.ldif delete mode 100644 ipa-server/ipa-install/share/ntp.conf.server.template delete mode 100644 ipa-server/ipa-install/share/ntpd.sysconfig.template delete mode 100644 ipa-server/ipa-install/share/preferences.html.template delete mode 100644 ipa-server/ipa-install/share/referint-conf.ldif delete mode 100644 ipa-server/ipa-install/share/schema_compat.uldif delete mode 100644 ipa-server/ipa-install/share/unique-attributes.ldif delete mode 100644 ipa-server/ipa-install/updates/Makefile.am delete mode 100644 ipa-server/ipa-install/updates/RFC2307bis.update delete mode 100644 ipa-server/ipa-install/updates/RFC4876.update delete mode 100644 ipa-server/ipa-install/updates/indices.update delete mode 100644 ipa-server/ipa-install/updates/nss_ldap.update delete mode 100644 ipa-server/ipa-install/updates/replication.update delete mode 100644 ipa-server/ipa-install/updates/winsync_index.update delete mode 100644 ipa-server/ipa-kpasswd/Makefile.am delete mode 100644 ipa-server/ipa-kpasswd/README delete mode 100644 ipa-server/ipa-kpasswd/ipa_kpasswd.c delete mode 100644 ipa-server/ipa-kpasswd/ipa_kpasswd.init delete mode 100755 ipa-server/ipa-ldap-updater delete mode 100644 ipa-server/ipa-slapi-plugins/Makefile.am delete mode 100644 ipa-server/ipa-slapi-plugins/README delete mode 100644 ipa-server/ipa-slapi-plugins/dna/Makefile.am delete mode 100644 ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif delete mode 100644 ipa-server/ipa-slapi-plugins/dna/dna.c delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile.am delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-winsync/Makefile.am delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-winsync/README delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c delete mode 100644 ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h delete mode 100644 ipa-server/ipa-upgradeconfig delete mode 100644 ipa-server/ipaserver/Makefile.am delete mode 100644 ipa-server/ipaserver/__init__.py delete mode 100644 ipa-server/ipaserver/bindinstance.py delete mode 100644 ipa-server/ipaserver/certs.py delete mode 100644 ipa-server/ipaserver/dsinstance.py delete mode 100644 ipa-server/ipaserver/httpinstance.py delete mode 100644 ipa-server/ipaserver/installutils.py delete mode 100644 ipa-server/ipaserver/ipaldap.py delete mode 100644 ipa-server/ipaserver/krbinstance.py delete mode 100755 ipa-server/ipaserver/ldapupdate.py delete mode 100644 ipa-server/ipaserver/ntpinstance.py delete mode 100644 ipa-server/ipaserver/replication.py delete mode 100644 ipa-server/ipaserver/service.py delete mode 100644 ipa-server/man/Makefile.am delete mode 100644 ipa-server/man/ipa-compat-manage.1 delete mode 100644 ipa-server/man/ipa-ldap-updater.1 delete mode 100644 ipa-server/man/ipa-replica-install.1 delete mode 100644 ipa-server/man/ipa-replica-manage.1 delete mode 100644 ipa-server/man/ipa-replica-prepare.1 delete mode 100644 ipa-server/man/ipa-server-certinstall.1 delete mode 100644 ipa-server/man/ipa-server-install.1 delete mode 100644 ipa-server/man/ipa_kpasswd.8 delete mode 100644 ipa-server/man/ipa_webgui.8 delete mode 100644 ipa-server/man/ipactl.8 delete mode 100644 ipa-server/selinux/Makefile delete mode 100644 ipa-server/selinux/ipa-server-selinux.spec.in delete mode 100644 ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.fc delete mode 100644 ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.te delete mode 100644 ipa-server/selinux/ipa_webgui/ipa_webgui.fc delete mode 100644 ipa-server/selinux/ipa_webgui/ipa_webgui.te delete mode 100644 ipa-server/version.m4.in delete mode 100644 ipa-server/xmlrpc-server/ipa-rewrite.conf delete mode 100644 ipa-server/xmlrpc-server/ipa.conf delete mode 100644 ipa-server/xmlrpc-server/ssbrowser.html delete mode 100644 ipa-server/xmlrpc-server/unauthorized.html create mode 100644 ipaserver/install/Makefile.am create mode 100644 ipaserver/install/__init__.py create mode 100644 ipaserver/install/bindinstance.py create mode 100644 ipaserver/install/certs.py create mode 100644 ipaserver/install/dsinstance.py create mode 100644 ipaserver/install/httpinstance.py create mode 100644 ipaserver/install/installutils.py create mode 100644 ipaserver/install/ipaldap.py create mode 100644 ipaserver/install/krbinstance.py create mode 100755 ipaserver/install/ldapupdate.py create mode 100644 ipaserver/install/ntpinstance.py create mode 100644 ipaserver/install/replication.py create mode 100644 ipaserver/install/service.py create mode 100644 selinux/Makefile create mode 100644 selinux/ipa-server-selinux.spec.in create mode 100644 selinux/ipa_kpasswd/ipa_kpasswd.fc create mode 100644 selinux/ipa_kpasswd/ipa_kpasswd.te create mode 100644 selinux/ipa_webgui/ipa_webgui.fc create mode 100644 selinux/ipa_webgui/ipa_webgui.te create mode 100644 version.m4.in diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..c95b6dbc --- /dev/null +++ b/autogen.sh @@ -0,0 +1,196 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. +set -e + +PACKAGE=freeipa-server + +LIBTOOLIZE=${LIBTOOLIZE-libtoolize} +LIBTOOLIZE_FLAGS="--copy --force" +AUTOHEADER=${AUTOHEADER-autoheader} +AUTOMAKE_FLAGS="--add-missing --gnu" +AUTOCONF=${AUTOCONF-autoconf} + +# automake 1.8 requires autoconf 2.58 +# automake 1.7 requires autoconf 2.54 +automake_min_vers=1.7 +aclocal_min_vers=$automake_min_vers +autoconf_min_vers=2.54 +libtoolize_min_vers=1.4 + +# The awk-based string->number conversion we use needs a C locale to work +# as expected. Setting LC_ALL overrides whether the user set LC_ALL, +# LC_NUMERIC, or LANG. +LC_ALL=C + +ARGV0=$0 + +# Allow invocation from a separate build directory; in that case, we change +# to the source directory to run the auto*, then change back before running configure +srcdir=`dirname $ARGV0` +test -z "$srcdir" && srcdir=. + +ORIGDIR=`pwd` + +cd $srcdir + +# Usage: +# compare_versions MIN_VERSION ACTUAL_VERSION +# returns true if ACTUAL_VERSION >= MIN_VERSION +compare_versions() { + ch_min_version=$1 + ch_actual_version=$2 + ch_status=0 + IFS="${IFS= }"; ch_save_IFS="$IFS"; IFS="." + set $ch_actual_version + for ch_min in $ch_min_version; do + ch_cur=`echo $1 | sed 's/[^0-9].*$//'`; shift # remove letter suffixes + if [ -z "$ch_min" ]; then break; fi + if [ -z "$ch_cur" ]; then ch_status=1; break; fi + if [ $ch_cur -gt $ch_min ]; then break; fi + if [ $ch_cur -lt $ch_min ]; then ch_status=1; break; fi + done + IFS="$ch_save_IFS" + return $ch_status +} + +if ($AUTOCONF --version) < /dev/null > /dev/null 2>&1 ; then + if ($AUTOCONF --version | head -n 1 | awk 'NR==1 { if( $(NF) >= '$autoconf_min_vers') \ + exit 1; exit 0; }'); + then + echo "$ARGV0: ERROR: \`$AUTOCONF' is too old." + $AUTOCONF --version + echo " (version $autoconf_min_vers or newer is required)" + DIE="yes" + fi +else + echo $AUTOCONF: command not found + echo + echo "$ARGV0: ERROR: You must have \`autoconf' installed to compile $PACKAGE." + echo " (version $autoconf_min_vers or newer is required)" + DIE="yes" +fi + +# +# Hunt for an appropriate version of automake and aclocal; we can't +# assume that 'automake' is necessarily the most recent installed version +# +# We check automake first to allow it to be a newer version than we know about. +# +if test x"$AUTOMAKE" = x || test x"$ACLOCAL" = x ; then + am_ver="" + for ver in "" "-1.9" "-1.8" "-1.7" ; do + am="automake$ver" + if ($am --version) < /dev/null > /dev/null 2>&1 ; then + if ($am --version | head -n 1 | awk 'NR==1 { if( $(NF) >= '$automake_min_vers') \ + exit 1; exit 0; }'); then : ; else + am_ver=$ver + break; + fi + fi + done + + AUTOMAKE=${AUTOMAKE-automake$am_ver} + ACLOCAL=${ACLOCAL-aclocal$am_ver} +fi + +# +# Now repeat the tests with the copies we decided upon and error out if they +# aren't sufficiently new. +# +if ($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 ; then + automake_actual_version=`$AUTOMAKE --version | head -n 1 | \ + sed 's/^.*[ ]\([0-9.]*[a-z]*\).*$/\1/'` + if ! compare_versions $automake_min_vers $automake_actual_version; then + echo "$ARGV0: ERROR: \`$AUTOMAKE' is too old." + $AUTOMAKE --version + echo " (version $automake_min_vers or newer is required)" + DIE="yes" + fi + if ($ACLOCAL --version) < /dev/null > /dev/null 2>&1; then + aclocal_actual_version=`$ACLOCAL --version | head -n 1 | \ + sed 's/^.*[ ]\([0-9.]*[a-z]*\).*$/\1/'` + + if ! compare_versions $aclocal_min_vers $aclocal_actual_version; then + echo "$ARGV0: ERROR: \`$ACLOCAL' is too old." + $ACLOCAL --version + echo " (version $aclocal_min_vers or newer is required)" + DIE="yes" + fi + else + echo $ACLOCAL: command not found + echo + echo "$ARGV0: ERROR: Missing \`$ACLOCAL'" + echo " The version of $AUTOMAKE installed doesn't appear recent enough." + DIE="yes" + fi +else + echo $AUTOMAKE: command not found + echo + echo "$ARGV0: ERROR: You must have \`automake' installed to compile $PACKAGE." + echo " (version $automake_min_vers or newer is required)" + DIE="yes" +fi + +if ($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 ; then + if ($LIBTOOLIZE --version | awk 'NR==1 { if( $4 >= '$libtoolize_min_vers') \ + exit 1; exit 0; }'); + then + echo "$ARGV0: ERROR: \`$LIBTOOLIZE' is too old." + echo " (version $libtoolize_min_vers or newer is required)" + DIE="yes" + fi +else + echo $LIBTOOLIZE: command not found + echo + echo "$ARGV0: ERROR: You must have \`libtoolize' installed to compile $PACKAGE." + echo " (version $libtoolize_min_vers or newer is required)" + DIE="yes" +fi + +if test -z "$ACLOCAL_FLAGS"; then + acdir=`$ACLOCAL --print-ac-dir` + if [ ! -f $acdir/pkg.m4 ]; then + echo "$ARGV0: Error: Could not find pkg-config macros." + echo " (Looked in $acdir/pkg.m4)" + echo " If pkg.m4 is available in /another/directory, please set" + echo " ACLOCAL_FLAGS=\"-I /another/directory\"" + echo " Otherwise, please install pkg-config." + echo "" + echo "pkg-config is available from:" + echo "http://www.freedesktop.org/software/pkgconfig/" + DIE=yes + fi +fi + +if test "X$DIE" != X; then + exit 1 +fi + + +if test -z "$*"; then + echo "$ARGV0: Note: \`./configure' will be run with no arguments." + echo " If you wish to pass any to it, please specify them on the" + echo " \`$0' command line." + echo +fi + +do_cmd() { + echo "$ARGV0: running \`$@'" + $@ +} + +do_cmd $LIBTOOLIZE $LIBTOOLIZE_FLAGS + +do_cmd $ACLOCAL $ACLOCAL_FLAGS + +do_cmd $AUTOHEADER + +do_cmd $AUTOMAKE $AUTOMAKE_FLAGS + +do_cmd $AUTOCONF + +cd $ORIGDIR || exit 1 + +rm -f config.cache + +do_cmd $srcdir/configure --cache-file=config.cache --disable-static --enable-maintainer-mode --enable-gtk-doc ${1+"$@"} && echo "Now type \`make' to compile" || exit 1 diff --git a/daemons/ipa-kpasswd/Makefile.am b/daemons/ipa-kpasswd/Makefile.am new file mode 100644 index 00000000..5f95fdef --- /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 00000000..c0a2767a --- /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 00000000..f2d3490f --- /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 00000000..d7244bed --- /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 00000000..f316371c --- /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 00000000..e69de29b diff --git a/daemons/ipa-slapi-plugins/dna/Makefile.am b/daemons/ipa-slapi-plugins/dna/Makefile.am new file mode 100644 index 00000000..4a54b8d5 --- /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 00000000..02532b4e --- /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 00000000..cb6a0629 --- /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 00000000..d0ac7f93 --- /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 00000000..3baf2f6c --- /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 00000000..3e7b5cf4 --- /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 00000000..b2bd374a --- /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 00000000..1441afea --- /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 00000000..540646f0 --- /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 00000000..e69de29b 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 00000000..24acc887 --- /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 00000000..e31a8e79 --- /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 00000000..94bc2dc6 --- /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 00000000..e69de29b 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 00000000..5b5c56ac --- /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 00000000..45efa6df --- /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 00000000..9ee8805b --- /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 00000000..58a9a6c4 --- /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 */ diff --git a/install/conf/ipa-rewrite.conf b/install/conf/ipa-rewrite.conf new file mode 100644 index 00000000..ef494300 --- /dev/null +++ b/install/conf/ipa-rewrite.conf @@ -0,0 +1,19 @@ +# VERSION 2 - DO NOT REMOVE THIS LINE + +RewriteEngine on + +# By default forward all requests to /ipa. If you don't want IPA +# to be the default on your web server comment this line out. You will +# need to modify ipa_webgui.cfg as well. +RewriteRule ^/$$ https://$FQDN/ipa/ui [L,NC,R=301] + +# Redirect to the fully-qualified hostname. Not redirecting to secure +# port so configuration files can be retrieved without requiring SSL. +RewriteCond %{HTTP_HOST} !^$FQDN$$ [NC] +RewriteRule ^/ipa/(.*) http://$FQDN/ipa/$$1 [L,R=301] + +# Redirect to the secure port if not displaying an error or retrieving +# configuration. +RewriteCond %{SERVER_PORT} !^443$$ +RewriteCond %{REQUEST_URI} !^/ipa/(errors|config) +RewriteRule ^/ipa/(.*) https://$FQDN/ipa/$$1 [L,R=301,NC] diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf new file mode 100644 index 00000000..85b4543a --- /dev/null +++ b/install/conf/ipa.conf @@ -0,0 +1,109 @@ +# +# VERSION 2 - DO NOT REMOVE THIS LINE +# +# LoadModule auth_kerb_module modules/mod_auth_kerb.so + +ProxyRequests Off + +# ipa-rewrite.conf is loaded separately + +# This is required so the auto-configuration works with Firefox 2+ +AddType application/java-archive jar + + + AuthType Kerberos + AuthName "Kerberos Login" + KrbMethodNegotiate on + KrbMethodK5Passwd off + KrbServiceName HTTP + KrbAuthRealms $REALM + Krb5KeyTab /etc/httpd/conf/ipa.keytab + KrbSaveCredentials on + Require valid-user + ErrorDocument 401 /ipa/errors/unauthorized.html + RewriteEngine on + Order deny,allow + Allow from all + + RequestHeader set X-Forwarded-Keytab %{KRB5CCNAME}e + + # RequestHeader unset Authorization + + +# The URI's with a trailing ! are those that aren't handled by the proxy +ProxyPass /ipa/ui http://localhost:8080/ipa/ui +ProxyPassReverse /ipa/ui http://localhost:8080/ipa/ui + +# Configure the XML-RPC service +Alias /ipa/xml "/usr/share/ipa/ipaserver/XMLRPC" + +# This is where we redirect on failed auth +Alias /ipa/errors "/usr/share/ipa/html" + +# For the MIT Windows config files +Alias /ipa/config "/usr/share/ipa/html" + + + AuthType Kerberos + AuthName "Kerberos Login" + KrbMethodNegotiate on + KrbMethodK5Passwd off + KrbServiceName HTTP + KrbAuthRealms $REALM + Krb5KeyTab /etc/httpd/conf/ipa.keytab + KrbSaveCredentials on + Require valid-user + ErrorDocument 401 /ipa/errors/unauthorized.html + + SetHandler mod_python + PythonHandler ipaxmlrpc + + PythonDebug Off + + PythonOption IPADebug Off + + # this is pointless to use since it would just reload ipaxmlrpc.py + PythonAutoReload Off + + +# Do no authentication on the directory that contains error messages + + AllowOverride None + Satisfy Any + Allow from all + + +# Protect our CGIs + + AuthType Kerberos + AuthName "Kerberos Login" + KrbMethodNegotiate on + KrbMethodK5Passwd off + KrbServiceName HTTP + KrbAuthRealms $REALM + Krb5KeyTab /etc/httpd/conf/ipa.keytab + KrbSaveCredentials on + Require valid-user + ErrorDocument 401 /ipa/errors/unauthorized.html + + +#Alias /ipatest "/usr/share/ipa/ipatest" + +# +# AuthType Kerberos +# AuthName "Kerberos Login" +# KrbMethodNegotiate on +# KrbMethodK5Passwd off +# KrbServiceName HTTP +# KrbAuthRealms $REALM +# Krb5KeyTab /etc/httpd/conf/ipa.keytab +# KrbSaveCredentials on +# Require valid-user +# ErrorDocument 401 /ipa/errors/unauthorized.html +# +# SetHandler mod_python +# PythonHandler test_mod_python +# +# PythonDebug Off +# +# diff --git a/install/html/ssbrowser.html b/install/html/ssbrowser.html new file mode 100644 index 00000000..37dbcb40 --- /dev/null +++ b/install/html/ssbrowser.html @@ -0,0 +1,68 @@ + + + +Browser Kerberos Setup + + +

Browser Kerberos Setup

+

Internet Explorer Configuration

+

Once you are able to log into the workstation with your kerberos key you should be able to use that ticket in Internet Explorer. For illustration purposes his page will use EXAMPLE.COM as the sample realm and example.com for the domain. +

+
  • Login to the Windows machine using an account of domain EXAMPLE.COM + +
  • In Internet Explorer, click Tools, and then click Internet Options. +
+
  1. Click the Security tab. +
  2. Click Local intranet. +
  3. Click Sites +
  4. Click Advanced +
  5. Add *.example.com to the list + +
+
  • In Internet Explorer, click Tools, and then click Internet Options. +
+
  1. Click the Security tab. +
  2. Click Local intranet. +
  3. Click Custom Level +
  4. Select Automatic logon only in Intranet zone. +
+
  • Visit a kerberized web site using IE. You must use the fully-qualified DN in the URL. +
  • If all went right, it should work. + +
+

Firefox Configuration

+

+You can configure Firefox to use Kerberos for Single Sign-on. In order for this functionality to work correctly, you need to configure your web browser to send your Kerberos credentials to the appropriate KDC.The following section describes the configuration changes and other requirements to achieve this. +

+
    +
  1. +

    +In the address bar of Firefox, type about:config to display the list of current configuration options. +

    +
  2. + +
  3. +

    +In the Filter field, type negotiate to restrict the list of options. +

    +
  4. +
  5. +

    +Double-click the network.negotiate-auth.trusted-uris entry to display the Enter string value dialog box. + +

    +
  6. +
  7. +

    +Enter the name of the domain against which you want to authenticate, for example, .example.com. +

    +
  8. +
  9. +

    +Repeat the above procedure for the network.negotiate-auth.delegation-uris entry, using the same domain. +

    +
  10. + +
+ + diff --git a/install/html/unauthorized.html b/install/html/unauthorized.html new file mode 100644 index 00000000..6ba8a99e --- /dev/null +++ b/install/html/unauthorized.html @@ -0,0 +1,28 @@ + +Kerberos Authentication Failed</h2> +<body> +<h2>Kerberos Authentication Failed</h2> +<p> +Unable to verify your Kerberos credentials. Please make sure +that you have valid Kerberos tickets (obtainable via kinit), and that you +have <a href="/ipa/errors/ssbrowser.html">configured your +browser correctly</a>. If you are still unable to access +the IPA Web interface, please contact the helpdesk on for additional assistance. +</p> +<p> +Import the <a href="/ipa/errors/ca.crt">IPA Certificate Authority</a>. +</p> +<p> +<script type="text/javascript"> + if (navigator.userAgent.indexOf("Firefox") != -1 || + navigator.userAgent.indexOf("SeaMonkey") != -1) + { + document.write("<p>You can automatically configure your browser to work with Kerberos by importing the Certificate Authority above and clicking on the Configure Browser button.</p>"); + document.write("<p>You <strong>must</strong> reload this page after importing the Certificate Authority for the automatic settings to work</p>"); + document.write("<object data=\"jar:/ipa/errors/configure.jar!/preferences.html\" type=\"text/html\"><\/object"); + } +</script> +</p> +</ul> +</body> +</html> diff --git a/install/share/60ipaconfig.ldif b/install/share/60ipaconfig.ldif new file mode 100644 index 00000000..f4edbcc9 --- /dev/null +++ b/install/share/60ipaconfig.ldif @@ -0,0 +1,42 @@ +## schema file for ipa configuration +## +## IPA Base OID: 2.16.840.1.113730.3.8 +## +## Attributes: 2.16.840.1.113730.3.8.1 +## ObjectClasses: 2.16.840.1.113730.3.8.2 +dn: cn=schema +############################################### +## +## Attributes +## +## ipaUserSearchFields - attribute names to search against when looking for users +attributetypes: ( 2.16.840.1.113730.3.8.1.1 NAME 'ipaUserSearchFields' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) +## ipaGroupSearchFields - attribute names to search against when looking for groups +attributetypes: ( 2.16.840.1.113730.3.8.1.2 NAME 'ipaGroupSearchFields' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) +## ipaSearchTimeLimit - search time limit in seconds +attributetypes: ( 2.16.840.1.113730.3.8.1.3 NAME 'ipaSearchTimeLimit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +## ipaSearchRecordsLimit - maximum number of records to return +attributetypes: ( 2.16.840.1.113730.3.8.1.4 NAME 'ipaSearchRecordsLimit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +## ipaCustomFields - custom fields to show in the UI in addition to pre-defined ones +attributetypes: ( 2.16.840.1.113730.3.8.1.5 NAME 'ipaCustomFields' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) +## ipaHomesRootDir - default posix home directory root dir to use when creating new accounts +attributetypes: ( 2.16.840.1.113730.3.8.1.6 NAME 'ipaHomesRootDir' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) +## ipaDefaultLoginShell - default posix login shell to use when creating new accounts +attributetypes: ( 2.16.840.1.113730.3.8.1.7 NAME 'ipaDefaultLoginShell' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) +## ipaDefaultPrimaryGroup - default posix primary group to assign when creating new accounts +attributetypes: ( 2.16.840.1.113730.3.8.1.8 NAME 'ipaDefaultPrimaryGroup' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) +## ipaMaxUsernameLength - maximum username length to allow in the UI +attributetypes: ( 2.16.840.1.113730.3.8.1.9 NAME 'ipaMaxUsernameLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +## ipaPwdExpAdvNotify - time in days to send out paswword expiration notification before passwpord actually expires +attributetypes: ( 2.16.840.1.113730.3.8.1.10 NAME 'ipaPwdExpAdvNotify' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +# ipaUserObjectClasses - required objectclasses for users +attributetypes: ( 2.16.840.1.113730.3.8.1.11 NAME 'ipaUserObjectClasses' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) +# ipaGroupObjectClasses - required objectclasses for groups +attributetypes: ( 2.16.840.1.113730.3.8.1.12 NAME 'ipaGroupObjectClasses' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) +attributetypes: ( 2.16.840.1.113730.3.8.1.13 NAME 'ipaDefaultEmailDomain' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) +############################################### +## +## ObjectClasses +## +## ipaGuiConfig - GUI config parameters objectclass +objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain) ) diff --git a/install/share/60kerberos.ldif b/install/share/60kerberos.ldif new file mode 100644 index 00000000..3431d22e --- /dev/null +++ b/install/share/60kerberos.ldif @@ -0,0 +1,283 @@ +dn: cn=schema +# Novell Kerberos Schema Definitions +# Novell Inc. +# 1800 South Novell Place +# Provo, UT 84606 +# +# VeRsIoN=1.0 +# CoPyRiGhT=(c) Copyright 2006, Novell, Inc. All rights reserved +# +# OIDs: +# joint-iso-ccitt(2) +# country(16) +# us(840) +# organization(1) +# Novell(113719) +# applications(1) +# kerberos(301) +# Kerberos Attribute Type(4) attr# version# +# specific attribute definitions +# Kerberos Attribute Syntax(5) +# specific syntax definitions +# Kerberos Object Class(6) class# version# +# specific class definitions +######################################################################## +######################################################################## +# Attribute Type Definitions # +######################################################################## +##### This is the principal name in the RFC 1964 specified format +attributetypes: ( 2.16.840.1.113719.1.301.4.1.1 NAME 'krbPrincipalName' EQUALITY caseExactIA5Match SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) +##### This specifies the type of the principal, the types could be any of +##### the types mentioned in section 6.2 of RFC 4120 +attributetypes: ( 2.16.840.1.113719.1.301.4.3.1 NAME 'krbPrincipalType' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### This flag is used to find whether directory User Password has to be used +##### as kerberos password. +##### TRUE, if User Password is to be used as the kerberos password. +##### FALSE, if User Password and the kerberos password are different. +attributetypes: ( 2.16.840.1.113719.1.301.4.5.1 NAME 'krbUPEnabled' DESC 'Boolean' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE) +##### The time at which the principal expires +attributetypes: ( 2.16.840.1.113719.1.301.4.6.1 NAME 'krbPrincipalExpiration' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) +##### The krbTicketFlags attribute holds information about the kerberos flags for a principal +##### The values (0x00000001 - 0x00800000) are reserved for standards and +##### values (0x01000000 - 0x80000000) can be used for proprietary extensions. +##### The flags and values as per RFC 4120 and MIT implementation are, +##### DISALLOW_POSTDATED 0x00000001 +##### DISALLOW_FORWARDABLE 0x00000002 +##### DISALLOW_TGT_BASED 0x00000004 +##### DISALLOW_RENEWABLE 0x00000008 +##### DISALLOW_PROXIABLE 0x00000010 +##### DISALLOW_DUP_SKEY 0x00000020 +##### DISALLOW_ALL_TIX 0x00000040 +##### REQUIRES_PRE_AUTH 0x00000080 +##### REQUIRES_HW_AUTH 0x00000100 +##### REQUIRES_PWCHANGE 0x00000200 +##### DISALLOW_SVR 0x00001000 +##### PWCHANGE_SERVICE 0x00002000 +attributetypes: ( 2.16.840.1.113719.1.301.4.8.1 NAME 'krbTicketFlags' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### The maximum ticket lifetime for a principal in seconds +attributetypes: ( 2.16.840.1.113719.1.301.4.9.1 NAME 'krbMaxTicketLife' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### Maximum renewable lifetime for a principal's ticket in seconds +attributetypes: ( 2.16.840.1.113719.1.301.4.10.1 NAME 'krbMaxRenewableAge' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### Forward reference to the Realm object. +##### (FDN of the krbRealmContainer object). +##### Example: cn=ACME.COM, cn=Kerberos, cn=Security +attributetypes: ( 2.16.840.1.113719.1.301.4.14.1 NAME 'krbRealmReferences' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) +##### List of LDAP servers that kerberos servers can contact. +##### The attribute holds data in the ldap uri format, +##### Example: ldaps://acme.com:636 +##### +##### The values of this attribute need to be updated, when +##### the LDAP servers listed here are renamed, moved or deleted. +attributetypes: ( 2.16.840.1.113719.1.301.4.15.1 NAME 'krbLdapServers' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) +##### A set of forward references to the KDC Service objects. +##### (FDNs of the krbKdcService objects). +##### Example: cn=kdc - server 1, ou=uvw, o=xyz +attributetypes: ( 2.16.840.1.113719.1.301.4.17.1 NAME 'krbKdcServers' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) +##### A set of forward references to the Password Service objects. +##### (FDNs of the krbPwdService objects). +##### Example: cn=kpasswdd - server 1, ou=uvw, o=xyz +attributetypes: ( 2.16.840.1.113719.1.301.4.18.1 NAME 'krbPwdServers' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) +##### This attribute holds the Host Name or the ip address, +##### transport protocol and ports of the kerberos service host +##### The format is host_name-or-ip_address#protocol#port +##### Protocol can be 0 or 1. 0 is for UDP. 1 is for TCP. +attributetypes: ( 2.16.840.1.113719.1.301.4.24.1 NAME 'krbHostServer' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) +##### This attribute holds the scope for searching the principals +##### under krbSubTree attribute of krbRealmContainer +##### The value can either be 1 (ONE) or 2 (SUB_TREE). +attributetypes: ( 2.16.840.1.113719.1.301.4.25.1 NAME 'krbSearchScope' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### FDNs pointing to Kerberos principals +attributetypes: ( 2.16.840.1.113719.1.301.4.26.1 NAME 'krbPrincipalReferences' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) +##### This attribute specifies which attribute of the user objects +##### be used as the principal name component for Kerberos. +##### The allowed values are cn, sn, uid, givenname, fullname. +attributetypes: ( 2.16.840.1.113719.1.301.4.28.1 NAME 'krbPrincNamingAttr' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) +##### A set of forward references to the Administration Service objects. +##### (FDNs of the krbAdmService objects). +##### Example: cn=kadmindd - server 1, ou=uvw, o=xyz +attributetypes: ( 2.16.840.1.113719.1.301.4.29.1 NAME 'krbAdmServers' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) +##### Maximum lifetime of a principal's password +attributetypes: ( 2.16.840.1.113719.1.301.4.30.1 NAME 'krbMaxPwdLife' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### Minimum lifetime of a principal's password +attributetypes: ( 2.16.840.1.113719.1.301.4.31.1 NAME 'krbMinPwdLife' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### Minimum number of character clases allowed in a password +attributetypes: ( 2.16.840.1.113719.1.301.4.32.1 NAME 'krbPwdMinDiffChars' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### Minimum length of the password +attributetypes: ( 2.16.840.1.113719.1.301.4.33.1 NAME 'krbPwdMinLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### Number of previous versions of passwords that are stored +attributetypes: ( 2.16.840.1.113719.1.301.4.34.1 NAME 'krbPwdHistoryLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### FDN pointing to a Kerberos Password Policy object +attributetypes: ( 2.16.840.1.113719.1.301.4.36.1 NAME 'krbPwdPolicyReference' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE) +##### The time at which the principal's password expires +attributetypes: ( 2.16.840.1.113719.1.301.4.37.1 NAME 'krbPasswordExpiration' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) +##### This attribute holds the principal's key (krbPrincipalKey) that is encrypted with +##### the master key (krbMKey). +##### The attribute is ASN.1 encoded. +##### +##### The format of the value for this attribute is explained below, +##### 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 +##### } +attributetypes: ( 2.16.840.1.113719.1.301.4.39.1 NAME 'krbPrincipalKey' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40) +##### FDN pointing to a Kerberos Ticket Policy object. +attributetypes: ( 2.16.840.1.113719.1.301.4.40.1 NAME 'krbTicketPolicyReference' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE) +##### Forward reference to an entry that starts sub-trees +##### where principals and other kerberos objects in the realm are configured. +##### Example: ou=acme, ou=pq, o=xyz +attributetypes: ( 2.16.840.1.113719.1.301.4.41.1 NAME 'krbSubTrees' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) +##### Holds the default encryption/salt type combinations of principals for +##### the Realm. Stores in the form of key:salt strings. +##### Example: des-cbc-crc:normal +attributetypes: ( 2.16.840.1.113719.1.301.4.42.1 NAME 'krbDefaultEncSaltTypes' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) +##### Holds the Supported encryption/salt type combinations of principals for +##### the Realm. Stores in the form of key:salt strings. +##### The supported encryption types are mentioned in RFC 3961 +##### The supported salt types are, +##### NORMAL +##### V4 +##### NOREALM +##### ONLYREALM +##### SPECIAL +##### AFS3 +##### Example: des-cbc-crc:normal +##### +##### This attribute obsoletes the krbSupportedEncTypes and krbSupportedSaltTypes +##### attributes. +attributetypes: ( 2.16.840.1.113719.1.301.4.43.1 NAME 'krbSupportedEncSaltTypes' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) +##### This attribute holds the principal's old keys (krbPwdHistory) that is encrypted with +##### the kadmin/history key. +##### The attribute is ASN.1 encoded. +##### +##### The format of the value for this attribute is explained below, +##### KrbKeySet ::= SEQUENCE { +##### attribute-major-vno [0] UInt16, +##### attribute-minor-vno [1] UInt16, +##### kvno [2] UInt32, +##### mkvno [3] UInt32 OPTIONAL -- actually kadmin/history key, +##### 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 +##### } +attributetypes: ( 2.16.840.1.113719.1.301.4.44.1 NAME 'krbPwdHistory' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40) +##### The time at which the principal's password last password change happened. +attributetypes: ( 2.16.840.1.113719.1.301.4.45.1 NAME 'krbLastPwdChange' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) +##### This attribute holds the kerberos master key. +##### This can be used to encrypt principal keys. +##### This attribute has to be secured in directory. +##### +##### This attribute is ASN.1 encoded. +##### The format of the value for this attribute is explained below, +##### KrbMKey ::= SEQUENCE { +##### kvno [0] UInt32, +##### key [1] MasterKey +##### } +##### +##### MasterKey ::= SEQUENCE { +##### keytype [0] Int32, +##### keyvalue [1] OCTET STRING +##### } +attributetypes: ( 2.16.840.1.113719.1.301.4.46.1 NAME 'krbMKey' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40) +##### This stores the alternate principal names for the principal in the RFC 1961 specified format +attributetypes: ( 2.16.840.1.113719.1.301.4.47.1 NAME 'krbPrincipalAliases' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) +##### The time at which the principal's last successful authentication happened. +attributetypes: ( 2.16.840.1.113719.1.301.4.48.1 NAME 'krbLastSuccessfulAuth' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) +##### The time at which the principal's last failed authentication happened. +attributetypes: ( 2.16.840.1.113719.1.301.4.49.1 NAME 'krbLastFailedAuth' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) +##### This attribute stores the number of failed authentication attempts +##### happened for the principal since the last successful authentication. +attributetypes: ( 2.16.840.1.113719.1.301.4.50.1 NAME 'krbLoginFailedCount' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) +##### This attribute holds the application specific data. +attributetypes: ( 2.16.840.1.113719.1.301.4.51.1 NAME 'krbExtraData' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40) +##### This attributes holds references to the set of directory objects. +##### This stores the DNs of the directory objects to which the +##### principal object belongs to. +attributetypes: ( 2.16.840.1.113719.1.301.4.52.1 NAME 'krbObjectReferences' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) +##### This attribute holds references to a Container object where +##### the additional principal objects and stand alone principal +##### objects (krbPrincipal) can be created. +attributetypes: ( 2.16.840.1.113719.1.301.4.53.1 NAME 'krbPrincContainerRef' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) +######################################################################## +######################################################################## +# Object Class Definitions # +######################################################################## +#### This is a kerberos container for all the realms in a tree. +objectClasses: ( 2.16.840.1.113719.1.301.6.1.1 NAME 'krbContainer' SUP top MUST ( cn ) ) +##### The krbRealmContainer is created per realm and holds realm specific data. +objectClasses: ( 2.16.840.1.113719.1.301.6.2.1 NAME 'krbRealmContainer' SUP top MUST ( cn ) MAY ( krbMKey $ krbUPEnabled $ krbSubTrees $ krbSearchScope $ krbLdapServers $ krbSupportedEncSaltTypes $ krbDefaultEncSaltTypes $ krbTicketPolicyReference $ krbKdcServers $ krbPwdServers $ krbAdmServers $ krbPrincNamingAttr $krbPwdPolicyReference $ krbPrincContainerRef ) ) +##### An instance of a class derived from krbService is created per +##### kerberos authentication or administration server in an realm and holds +##### references to the realm objects. These references is used to further read +##### realm specific data to service AS/TGS requests. Additionally this object +##### contains some server specific data like pathnames and ports that the +##### server uses. This is the identity the kerberos server logs in with. A key +##### pair for the same is created and the kerberos server logs in with the same. +##### +##### krbKdcService, krbAdmService and krbPwdService derive from this class. +objectClasses: ( 2.16.840.1.113719.1.301.6.3.1 NAME 'krbService' ABSTRACT SUP ( top ) MUST ( cn ) MAY ( krbHostServer $ krbRealmReferences ) ) +##### Representative object for the KDC server to bind into a LDAP directory +##### and have a connection to access Kerberos data with the required +##### access rights. +objectClasses: ( 2.16.840.1.113719.1.301.6.4.1 NAME 'krbKdcService' SUP ( krbService ) ) +##### Representative object for the Kerberos Password server to bind into a LDAP directory +##### and have a connection to access Kerberos data with the required +##### access rights. +objectClasses: ( 2.16.840.1.113719.1.301.6.5.1 NAME 'krbPwdService' SUP ( krbService ) ) +###### The principal data auxiliary class. Holds principal information +###### and is used to store principal information for Person, Service objects. +objectClasses: ( 2.16.840.1.113719.1.301.6.8.1 NAME 'krbPrincipalAux' AUXILIARY MAY ( krbPrincipalName $ krbUPEnabled $ krbPrincipalKey $ krbTicketPolicyReference $ krbPrincipalExpiration $ krbPasswordExpiration $ krbPwdPolicyReference $ krbPrincipalType $ krbPwdHistory $ krbLastPwdChange $ krbPrincipalAliases $ krbLastSuccessfulAuth $ krbLastFailedAuth $ krbLoginFailedCount $ krbExtraData ) ) +###### This class is used to create additional principals and stand alone principals. +objectClasses: ( 2.16.840.1.113719.1.301.6.9.1 NAME 'krbPrincipal' SUP ( top ) MUST ( krbPrincipalName ) MAY ( krbObjectReferences ) ) +###### The principal references auxiliary class. Holds all principals referred +###### from a service +objectClasses: ( 2.16.840.1.113719.1.301.6.11.1 NAME 'krbPrincRefAux' SUP top AUXILIARY MAY krbPrincipalReferences ) +##### Representative object for the Kerberos Administration server to bind into a LDAP directory +##### and have a connection Id to access Kerberos data with the required access rights. +objectClasses: ( 2.16.840.1.113719.1.301.6.13.1 NAME 'krbAdmService' SUP ( krbService ) ) +##### The krbPwdPolicy object is a template password policy that +##### can be applied to principals when they are created. +##### These policy attributes will be in effect, when the Kerberos +##### passwords are different from users' passwords (UP). +objectClasses: ( 2.16.840.1.113719.1.301.6.14.1 NAME 'krbPwdPolicy' SUP top MUST ( cn ) MAY ( krbMaxPwdLife $ krbMinPwdLife $ krbPwdMinDiffChars $ krbPwdMinLength $ krbPwdHistoryLength ) ) +##### The krbTicketPolicyAux holds Kerberos ticket policy attributes. +##### This class can be attached to a principal object or realm object. +objectClasses: ( 2.16.840.1.113719.1.301.6.16.1 NAME 'krbTicketPolicyAux' AUXILIARY MAY ( krbTicketFlags $ krbMaxTicketLife $ krbMaxRenewableAge ) ) +##### The krbTicketPolicy object is an effective ticket policy that is associated with a realm or a principal +objectClasses: ( 2.16.840.1.113719.1.301.6.17.1 NAME 'krbTicketPolicy' SUP top MUST ( cn ) ) diff --git a/install/share/60radius.ldif b/install/share/60radius.ldif new file mode 100644 index 00000000..93a5ba31 --- /dev/null +++ b/install/share/60radius.ldif @@ -0,0 +1,559 @@ +# This is a LDAPv3 schema for RADIUS attributes. +# Tested on OpenLDAP 2.0.7 +# Posted by Javier Fernandez-Sanguino Pena <jfernandez@sgi.es> +# LDAP v3 version by Jochen Friedrich <jochen@scram.de> +# Updates by Adrian Pavlykevych <pam@polynet.lviv.ua> +# Modified by John Dennis <jdennis@redhat.com> for use with Directory Sever/IPA +# +# Note: These OID's do not seem to be registered, the closest I could find +# was 1.3.6.1.4.1.3317 +# {iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) gnome(3317)} +# +############## +dn: cn=schema +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.1 + NAME 'radiusArapFeatures' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.2 + NAME 'radiusArapSecurity' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.3 + NAME 'radiusArapZoneAccess' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.44 + NAME 'radiusAuthType' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.4 + NAME 'radiusCallbackId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.5 + NAME 'radiusCallbackNumber' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.6 + NAME 'radiusCalledStationId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.7 + NAME 'radiusCallingStationId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.8 + NAME 'radiusClass' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.45 + NAME 'radiusClientIPAddress' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.9 + NAME 'radiusFilterId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.10 + NAME 'radiusFramedAppleTalkLink' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.11 + NAME 'radiusFramedAppleTalkNetwork' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.12 + NAME 'radiusFramedAppleTalkZone' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.13 + NAME 'radiusFramedCompression' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.14 + NAME 'radiusFramedIPAddress' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.15 + NAME 'radiusFramedIPNetmask' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.16 + NAME 'radiusFramedIPXNetwork' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.17 + NAME 'radiusFramedMTU' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.18 + NAME 'radiusFramedProtocol' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.19 + NAME 'radiusFramedRoute' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.20 + NAME 'radiusFramedRouting' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.46 + NAME 'radiusGroupName' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.47 + NAME 'radiusHint' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.48 + NAME 'radiusHuntgroupName' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.21 + NAME 'radiusIdleTimeout' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.22 + NAME 'radiusLoginIPHost' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.23 + NAME 'radiusLoginLATGroup' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.24 + NAME 'radiusLoginLATNode' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.25 + NAME 'radiusLoginLATPort' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.26 + NAME 'radiusLoginLATService' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.27 + NAME 'radiusLoginService' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.28 + NAME 'radiusLoginTCPPort' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.29 + NAME 'radiusPasswordRetry' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.30 + NAME 'radiusPortLimit' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.49 + NAME 'radiusProfileDn' + DESC '' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.31 + NAME 'radiusPrompt' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.50 + NAME 'radiusProxyToRealm' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.51 + NAME 'radiusReplicateToRealm' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.52 + NAME 'radiusRealm' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.32 + NAME 'radiusServiceType' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.33 + NAME 'radiusSessionTimeout' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.34 + NAME 'radiusTerminationAction' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.35 + NAME 'radiusTunnelAssignmentId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.36 + NAME 'radiusTunnelMediumType' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.37 + NAME 'radiusTunnelPassword' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.38 + NAME 'radiusTunnelPreference' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.39 + NAME 'radiusTunnelPrivateGroupId' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.40 + NAME 'radiusTunnelServerEndpoint' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.41 + NAME 'radiusTunnelType' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.42 + NAME 'radiusVSA' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.43 + NAME 'radiusTunnelClientEndpoint' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +#need to change asn1.id +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.53 + NAME 'radiusSimultaneousUse' + DESC '' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.54 + NAME 'radiusLoginTime' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.55 + NAME 'radiusUserCategory' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.56 + NAME 'radiusStripUserName' + DESC '' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.57 + NAME 'dialupAccess' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.58 + NAME 'radiusExpiration' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.59 + NAME 'radiusCheckItem' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.60 + NAME 'radiusReplyItem' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.61 + NAME 'radiusNASIpAddress' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.62 + NAME 'radiusReplyMessage' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +objectClasses: + ( 1.3.6.1.4.1.3317.4.3.2.1 + NAME 'radiusprofile' + SUP top AUXILIARY + DESC '' + MUST uid + MAY ( radiusArapFeatures $ radiusArapSecurity $ radiusArapZoneAccess $ + radiusAuthType $ radiusCallbackId $ radiusCallbackNumber $ + radiusCalledStationId $ radiusCallingStationId $ radiusClass $ + radiusClientIPAddress $ radiusFilterId $ radiusFramedAppleTalkLink $ + radiusFramedAppleTalkNetwork $ radiusFramedAppleTalkZone $ + radiusFramedCompression $ radiusFramedIPAddress $ + radiusFramedIPNetmask $ radiusFramedIPXNetwork $ + radiusFramedMTU $ radiusFramedProtocol $ + radiusCheckItem $ radiusReplyItem $ + radiusFramedRoute $ radiusFramedRouting $ radiusIdleTimeout $ + radiusGroupName $ radiusHint $ radiusHuntgroupName $ + radiusLoginIPHost $ radiusLoginLATGroup $ radiusLoginLATNode $ + radiusLoginLATPort $ radiusLoginLATService $ radiusLoginService $ + radiusLoginTCPPort $ radiusLoginTime $ radiusPasswordRetry $ + radiusPortLimit $ radiusPrompt $ radiusProxyToRealm $ + radiusRealm $ radiusReplicateToRealm $ radiusServiceType $ + radiusSessionTimeout $ radiusStripUserName $ + radiusTerminationAction $ radiusTunnelClientEndpoint $ radiusProfileDn $ + radiusSimultaneousUse $ radiusTunnelAssignmentId $ + radiusTunnelMediumType $ radiusTunnelPassword $ radiusTunnelPreference $ + radiusTunnelPrivateGroupId $ radiusTunnelServerEndpoint $ + radiusTunnelType $ radiusUserCategory $ radiusVSA $ + radiusExpiration $ dialupAccess $ radiusNASIpAddress $ + radiusReplyMessage ) + ) +objectClasses: + ( 1.3.6.1.4.1.3317.4.3.2.2 + NAME 'radiusObjectProfile' + SUP top STRUCTURAL + DESC 'A Container Objectclass to be used for creating radius profile object' + MUST cn + MAY ( uid $ userPassword $ description ) + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.64 + NAME 'radiusClientSecret' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.65 + NAME 'radiusClientNASType' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + ) +attributeTypes: + ( 1.3.6.1.4.1.3317.4.3.1.66 + NAME 'radiusClientShortName' + DESC '' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + ) +objectClasses: + ( 1.3.6.1.4.1.3317.4.3.2.3 + NAME 'radiusClientProfile' + SUP top STRUCTURAL + DESC 'A Container Objectclass to be used for describing radius clients' + MUST (radiusClientIPAddress $ radiusClientSecret) + MAY ( radiusClientNASType $ radiusClientShortName $ description ) + ) diff --git a/install/share/60samba.ldif b/install/share/60samba.ldif new file mode 100644 index 00000000..d3a6d31b --- /dev/null +++ b/install/share/60samba.ldif @@ -0,0 +1,152 @@ +## schema file for Fedora DS +## +## Schema for storing Samba user accounts and group maps in LDAP +## OIDs are owned by the Samba Team +## +## Prerequisite schemas - uid (cosine.schema) +## - displayName (inetorgperson.schema) +## - gidNumber (nis.schema) +## +## 1.3.6.1.4.1.7165.2.1.x - attributeTypess +## 1.3.6.1.4.1.7165.2.2.x - objectClasseses +## +## Printer support +## 1.3.6.1.4.1.7165.2.3.1.x - attributeTypess +## 1.3.6.1.4.1.7165.2.3.2.x - objectClasseses +## +## Samba4 +## 1.3.6.1.4.1.7165.4.1.x - attributeTypess +## 1.3.6.1.4.1.7165.4.2.x - objectClasseses +## 1.3.6.1.4.1.7165.4.3.x - LDB/LDAP Controls +## 1.3.6.1.4.1.7165.4.4.x - LDB/LDAP Extended Operations +## 1.3.6.1.4.1.7165.4.255.x - mapped OIDs due to conflicts between AD and standards-track +## +dn: cn=schema +## +####################################################################### +## Attributes used by Samba 3.0 schema ## +####################################################################### +## +## Password hashes## +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.24 NAME 'sambaLMPassword' DESC 'LanManager Password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.25 NAME 'sambaNTPassword' DESC 'MD4 hash of the unicode password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) +## +## Account flags in string format ([UWDX ]) +## +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.26 NAME 'sambaAcctFlags' DESC 'Account Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{16} SINGLE-VALUE ) +## +## Password timestamps & policies +## +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.27 NAME 'sambaPwdLastSet' DESC 'Timestamp of the last password update' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.28 NAME 'sambaPwdCanChange' DESC 'Timestamp of when the user is allowed to update the password' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.29 NAME 'sambaPwdMustChange' DESC 'Timestamp of when the password will expire' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.30 NAME 'sambaLogonTime' DESC 'Timestamp of last logon' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.31 NAME 'sambaLogoffTime' DESC 'Timestamp of last logoff' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.32 NAME 'sambaKickoffTime' DESC 'Timestamp of when the user will be logged off automatically' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.48 NAME 'sambaBadPasswordCount' DESC 'Bad password attempt count' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.49 NAME 'sambaBadPasswordTime' DESC 'Time of the last bad password attempt' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.55 NAME 'sambaLogonHours' DESC 'Logon Hours' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{42} SINGLE-VALUE ) +## +## string settings +## +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.33 NAME 'sambaHomeDrive' DESC 'Driver letter of home directory mapping' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.34 NAME 'sambaLogonScript' DESC 'Logon script path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.35 NAME 'sambaProfilePath' DESC 'Roaming profile path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.36 NAME 'sambaUserWorkstations' DESC 'List of user workstations the user is allowed to logon to' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.37 NAME 'sambaHomePath' DESC 'Home directory UNC path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.38 NAME 'sambaDomainName' DESC 'Windows NT domain to which the user belongs' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.47 NAME 'sambaMungedDial' DESC 'Base64 encoded user parameter string' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.54 NAME 'sambaPasswordHistory' DESC 'Concatenated MD5 hashes of the salted NT passwords used on this account' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} ) +## +## SID, of any type +## +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.20 NAME 'sambaSID' DESC 'Security ID' EQUALITY caseIgnoreIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE ) +## +## Primary group SID, compatible with ntSid +## +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.23 NAME 'sambaPrimaryGroupSID' DESC 'Primary Group Security ID' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.51 NAME 'sambaSIDList' DESC 'Security ID List' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} ) +## +## group mapping attributes +## +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.19 NAME 'sambaGroupType' DESC 'NT Group Type' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +## +## Store info on the domain +## +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.21 NAME 'sambaNextUserRid' DESC 'Next NT rid to give our for users' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.22 NAME 'sambaNextGroupRid' DESC 'Next NT rid to give out for groups' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.39 NAME 'sambaNextRid' DESC 'Next NT rid to give out for anything' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.40 NAME 'sambaAlgorithmicRidBase' DESC 'Base at which the samba RID generation algorithm should operate' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.41 NAME 'sambaShareName' DESC 'Share Name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.42 NAME 'sambaOptionName' DESC 'Option Name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.43 NAME 'sambaBoolOption' DESC 'A boolean option' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.44 NAME 'sambaIntegerOption' DESC 'An integer option' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.45 NAME 'sambaStringOption' DESC 'A string option' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.46 NAME 'sambaStringListOption' DESC 'A string list option' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +##attributeTypes: ( 1.3.6.1.4.1.7165.2.1.50 NAME 'sambaPrivName' +## SUP name ) +## +##attributeTypes: ( 1.3.6.1.4.1.7165.2.1.52 NAME 'sambaPrivilegeList' +## DESC 'Privileges List' +## EQUALITY caseIgnoreIA5Match +## SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} ) +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.53 NAME 'sambaTrustFlags' DESC 'Trust Password Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +# "min password length" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.58 NAME 'sambaMinPwdLength' DESC 'Minimal password length (default: 5)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +# "password history" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.59 NAME 'sambaPwdHistoryLength' DESC 'Length of Password History Entries (default: 0 => off)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +# "user must logon to change password" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.60 NAME 'sambaLogonToChgPwd' DESC 'Force Users to logon for password change (default: 0 => off, 2 => on)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +# "maximum password age" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.61 NAME 'sambaMaxPwdAge' DESC 'Maximum password age, in seconds (default: -1 => never expire passwords)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +# "minimum password age" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.62 NAME 'sambaMinPwdAge' DESC 'Minimum password age, in seconds (default: 0 => allow immediate password change)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +# "lockout duration" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.63 NAME 'sambaLockoutDuration' DESC 'Lockout duration in minutes (default: 30, -1 => forever)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +# "reset count minutes" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.64 NAME 'sambaLockoutObservationWindow' DESC 'Reset time after lockout in minutes (default: 30)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +# "bad lockout attempt" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.65 NAME 'sambaLockoutThreshold' DESC 'Lockout users after bad logon attempts (default: 0 => off)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +# "disconnect time" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.66 NAME 'sambaForceLogoff' DESC 'Disconnect Users outside logon hours (default: -1 => off, 0 => on)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +# "refuse machine password change" +attributeTypes: ( 1.3.6.1.4.1.7165.2.1.67 NAME 'sambaRefuseMachinePwdChange' DESC 'Allow Machine Password changes (default: 0 => off)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +## +####################################################################### +## objectClasses: used by Samba 3.0 schema ## +####################################################################### +## +## The X.500 data model (and therefore LDAPv3) says that each entry can +## only have one structural objectClasses. OpenLDAP 2.0 does not enforce +## this currently but will in v2.1 +## +## added new objectClasses: (and OID) for 3.0 to help us deal with backwards +## compatibility with 2.2 installations (e.g. ldapsam_compat) --jerry +## +objectClasses: ( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' SUP top AUXILIARY DESC 'Samba 3.0 Auxilary SAM Account' MUST ( uid $ sambaSID ) MAY ( cn $ sambaLMPassword $ sambaNTPassword $ sambaPwdLastSet $ sambaLogonTime $ sambaLogoffTime $ sambaKickoffTime $ sambaPwdCanChange $ sambaPwdMustChange $ sambaAcctFlags $ displayName $ sambaHomePath $ sambaHomeDrive $ sambaLogonScript $ sambaProfilePath $ description $ sambaUserWorkstations $ sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $ sambaBadPasswordCount $ sambaBadPasswordTime $ sambaPasswordHistory $ sambaLogonHours)) +## +## Group mapping info +## +objectClasses: ( 1.3.6.1.4.1.7165.2.2.4 NAME 'sambaGroupMapping' SUP top AUXILIARY DESC 'Samba Group Mapping' MUST ( gidNumber $ sambaSID $ sambaGroupType ) MAY ( displayName $ description $ sambaSIDList )) +## +## Trust password for trust relationships (any kind) +## +objectClasses: ( 1.3.6.1.4.1.7165.2.2.14 NAME 'sambaTrustPassword' SUP top STRUCTURAL DESC 'Samba Trust Password' MUST ( sambaDomainName $ sambaNTPassword $ sambaTrustFlags ) MAY ( sambaSID $ sambaPwdLastSet )) +## +## Whole-of-domain info +## +objectClasses: ( 1.3.6.1.4.1.7165.2.2.5 NAME 'sambaDomain' SUP top STRUCTURAL DESC 'Samba Domain Information' MUST ( sambaDomainName $ sambaSID ) MAY ( sambaNextRid $ sambaNextGroupRid $ sambaNextUserRid $ sambaAlgorithmicRidBase $ sambaMinPwdLength $ sambaPwdHistoryLength $ sambaLogonToChgPwd $ sambaMaxPwdAge $ sambaMinPwdAge $ sambaLockoutDuration $ sambaLockoutObservationWindow $ sambaLockoutThreshold $ sambaForceLogoff $ sambaRefuseMachinePwdChange )) +## +## used for idmap_ldap module +## +objectClasses: ( 1.3.6.1.4.1.7165.2.2.7 NAME 'sambaUnixIdPool' SUP top AUXILIARY DESC 'Pool for allocating UNIX uids/gids' MUST ( uidNumber $ gidNumber ) ) +objectClasses: ( 1.3.6.1.4.1.7165.2.2.8 NAME 'sambaIdmapEntry' SUP top AUXILIARY DESC 'Mapping from a SID to an ID' MUST ( sambaSID ) MAY ( uidNumber $ gidNumber ) ) +objectClasses: ( 1.3.6.1.4.1.7165.2.2.9 NAME 'sambaSidEntry' SUP top STRUCTURAL DESC 'Structural Class for a SID' MUST ( sambaSID ) ) +objectClasses: ( 1.3.6.1.4.1.7165.2.2.10 NAME 'sambaConfig' SUP top AUXILIARY DESC 'Samba Configuration Section' MAY ( description ) ) +objectClasses: ( 1.3.6.1.4.1.7165.2.2.11 NAME 'sambaShare' SUP top STRUCTURAL DESC 'Samba Share Section' MUST ( sambaShareName ) MAY ( description ) ) +objectClasses: ( 1.3.6.1.4.1.7165.2.2.12 NAME 'sambaConfigOption' SUP top STRUCTURAL DESC 'Samba Configuration Option' MUST ( sambaOptionName ) MAY ( sambaBoolOption $ sambaIntegerOption $ sambaStringOption $ sambaStringListoption $ description ) ) +## retired during privilege rewrite +##objectClasses: ( 1.3.6.1.4.1.7165.2.2.13 NAME 'sambaPrivilege' SUP top AUXILIARY +## DESC 'Samba Privilege' +## MUST ( sambaSID ) +## MAY ( sambaPrivilegeList ) ) diff --git a/install/share/Makefile.am b/install/share/Makefile.am new file mode 100644 index 00000000..6be2e13d --- /dev/null +++ b/install/share/Makefile.am @@ -0,0 +1,39 @@ +NULL = + +appdir = $(IPA_DATA_DIR) +app_DATA = \ + 60kerberos.ldif \ + 60samba.ldif \ + 60radius.ldif \ + 60ipaconfig.ldif \ + bootstrap-template.ldif \ + default-aci.ldif \ + default-keytypes.ldif \ + kerberos.ldif \ + indices.ldif \ + bind.named.conf.template \ + bind.zone.db.template \ + certmap.conf.template \ + kdc.conf.template \ + krb5.conf.template \ + krb5.ini.template \ + krb.con.template \ + krbrealm.con.template \ + ntp.conf.server.template \ + ntpd.sysconfig.template \ + preferences.html.template \ + referint-conf.ldif \ + dna-posix.ldif \ + master-entry.ldif \ + memberof-task.ldif \ + unique-attributes.ldif \ + schema_compat.uldif \ + $(NULL) + +EXTRA_DIST = \ + $(app_DATA) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/install/share/bind.named.conf.template b/install/share/bind.named.conf.template new file mode 100644 index 00000000..c1d2817e --- /dev/null +++ b/install/share/bind.named.conf.template @@ -0,0 +1,41 @@ +options { + /* make named use port 53 for the source of all queries, to allow + * firewalls to block all ports except 53: + */ + query-source port 53; + query-source-v6 port 53; + + // Put files that named is allowed to write in the data/ directory: + directory "/var/named"; // the default + dump-file "data/cache_dump.db"; + statistics-file "data/named_stats.txt"; + memstatistics-file "data/named_mem_stats.txt"; + + /* Not used yet, support only on very recent bind versions */ +# tkey-gssapi-credential "DNS/$FQDN"; +# tkey-domain "$REALM"; +}; + +logging { +/* If you want to enable debugging, eg. using the 'rndc trace' command, + * By default, SELinux policy does not allow named to modify the /var/named directory, + * so put the default debug log file in data/ : + */ + channel default_debug { + file "data/named.run"; + severity dynamic; + }; +}; + +zone "." IN { + type hint; + file "named.ca"; +}; + +include "/etc/named.rfc1912.zones"; + +zone "$DOMAIN" { + type master; + file "$DOMAIN.zone.db"; +}; + diff --git a/install/share/bind.zone.db.template b/install/share/bind.zone.db.template new file mode 100644 index 00000000..aca7d2d2 --- /dev/null +++ b/install/share/bind.zone.db.template @@ -0,0 +1,28 @@ +$$ORIGIN $DOMAIN. +$$TTL 86400 +@ IN SOA $DOMAIN. root.$DOMAIN. ( + 01 ; serial + 3H ; refresh + 15M ; retry + 1W ; expiry + 1D ) ; minimum + + IN NS $HOST +$HOST IN A $IP +; +; ldap servers +_ldap._tcp IN SRV 0 100 389 $HOST + +;kerberos realm +_kerberos IN TXT $REALM + +; kerberos servers +_kerberos._tcp IN SRV 0 100 88 $HOST +_kerberos._udp IN SRV 0 100 88 $HOST +_kerberos-master._tcp IN SRV 0 100 88 $HOST +_kerberos-master._udp IN SRV 0 100 88 $HOST +_kpasswd._tcp IN SRV 0 100 464 $HOST +_kpasswd._udp IN SRV 0 100 464 $HOST + +;ntp server +_ntp._udp IN SRV 0 100 123 $HOST diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif new file mode 100644 index 00000000..eb69ae4d --- /dev/null +++ b/install/share/bootstrap-template.ldif @@ -0,0 +1,202 @@ +dn: cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: nsContainer +objectClass: krbPwdPolicy +cn: accounts +krbMinPwdLife: 3600 +krbPwdMinDiffChars: 0 +krbPwdMinLength: 8 +krbPwdHistoryLength: 0 +krbMaxPwdLife: 7776000 + +dn: cn=users,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: nsContainer +cn: users + +dn: cn=groups,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: nsContainer +cn: groups + +dn: cn=services,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: nsContainer +cn: services + +dn: cn=computers,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: nsContainer +cn: computers + +dn: cn=etc,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: etc + +dn: cn=sysaccounts,cn=etc,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: sysaccounts + +dn: cn=ipa,cn=etc,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: ipa + +dn: cn=masters,cn=ipa,cn=etc,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: masters + +dn: uid=admin,cn=users,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: person +objectClass: posixAccount +objectClass: KrbPrincipalAux +objectClass: inetUser +uid: admin +krbPrincipalName: admin@$REALM +cn: Administrator +sn: Administrator +uidNumber: 999 +gidNumber: 1001 +homeDirectory: /home/admin +loginShell: /bin/bash +gecos: Administrator +nsAccountLock: False + +dn: cn=radius,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: radius + +dn: cn=clients,cn=radius,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: clients + +dn: cn=profiles,cn=radius,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: profiles + +dn: uid=ipa_default, cn=profiles,cn=radius,$SUFFIX +changetype: add +objectClass: top +objectClass: radiusprofile +uid: ipa_default + +dn: cn=admins,cn=groups,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: groupofnames +objectClass: posixGroup +cn: admins +description: Account administrators group +gidNumber: 1001 +member: uid=admin,cn=users,cn=accounts,$SUFFIX +nsAccountLock: False + +dn: cn=ipausers,cn=groups,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: groupofnames +objectClass: posixGroup +gidNumber: 1002 +description: Default group for all users +cn: ipausers + +dn: cn=editors,cn=groups,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: groupofnames +objectClass: posixGroup +gidNumber: 1003 +description: Limited admins who can edit other users +cn: editors + +dn: cn=ipaConfig,cn=etc,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +objectClass: ipaGuiConfig +ipaUserSearchFields: uid,givenName,sn,telephoneNumber,ou,title +ipaGroupSearchFields: cn,description +ipaSearchTimeLimit: 2 +ipaSearchRecordsLimit: 0 +ipaHomesRootDir: /home +ipaDefaultLoginShell: /bin/sh +ipaDefaultPrimaryGroup: ipausers +ipaMaxUsernameLength: 8 +ipaPwdExpAdvNotify: 4 +ipaGroupObjectClasses: top +ipaGroupObjectClasses: groupofnames +ipaGroupObjectClasses: posixGroup +ipaGroupObjectClasses: inetUser +ipaUserObjectClasses: top +ipaUserObjectClasses: person +ipaUserObjectClasses: organizationalPerson +ipaUserObjectClasses: inetOrgPerson +ipaUserObjectClasses: inetUser +ipaUserObjectClasses: posixAccount +ipaUserObjectClasses: krbPrincipalAux +ipaUserObjectClasses: radiusprofile +ipaDefaultEmailDomain: $DOMAIN + +dn: cn=account inactivation,cn=accounts,$SUFFIX +changetype: add +description: Lock accounts based on group membership +objectClass: top +objectClass: ldapsubentry +objectClass: cosSuperDefinition +objectClass: cosClassicDefinition +cosTemplateDn: cn=cosTemplates,cn=accounts,$SUFFIX +cosAttribute: nsAccountLock operational +cosSpecifier: memberOf +cn: Account Inactivation + +dn: cn=cosTemplates,cn=accounts,$SUFFIX +changetype: add +objectclass: top +objectclass: nsContainer +cn: cosTemplates + +dn: cn="cn=inactivated,cn=account inactivation,cn=accounts,$SUFFIX", cn=cosTemplates,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: cosTemplate +objectClass: extensibleobject +nsAccountLock: true +cosPriority: 1 + +dn: cn=inactivated,cn=account inactivation,cn=accounts,$SUFFIX +changetype: add +objectclass: top +objectclass: groupofnames + +dn: cn="cn=activated,cn=account inactivation,cn=accounts,$SUFFIX", cn=cosTemplates,cn=accounts,$SUFFIX +changetype: add +objectClass: top +objectClass: cosTemplate +objectClass: extensibleobject +nsAccountLock: false +cosPriority: 0 + +dn: cn=Activated,cn=Account Inactivation,cn=accounts,$SUFFIX +changetype: add +objectclass: top +objectclass: groupofnames diff --git a/install/share/certmap.conf.template b/install/share/certmap.conf.template new file mode 100644 index 00000000..676d3ef3 --- /dev/null +++ b/install/share/certmap.conf.template @@ -0,0 +1,82 @@ +# +# 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) 2001 Sun Microsystems, Inc. Used by permission. +# Copyright (C) 2005 Red Hat, Inc. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# This file configures how a certificate is mapped to an LDAP entry. See the +# documentation for more information on this file. +# +# The format of this file is as follows: +# certmap <name> <issuerDN> +# <name>:<prop1> [<val1>] +# <name>:<prop2> [<val2>] +# +# Notes: +# +# 1. Mapping can be defined per issuer of a certificate. If mapping doesn't +# exists for a particular 'issuerDN' then the server uses the default +# mapping. +# +# 2. There must be an entry for <name>=default and issuerDN "default". +# This mapping is the default mapping. +# +# 3. '#' can be used to comment out a line. +# +# 4. DNComps & FilterComps are used to form the base DN and filter resp. for +# performing an LDAP search while mapping the cert to a user entry. +# +# 5. DNComps can be one of the following: +# commented out - take the user's DN from the cert as is +# empty - search the entire LDAP tree (DN == suffix) +# attr names - a comma separated list of attributes to form DN +# +# 6. FilterComps can be one of the following: +# commented out - set the filter to "objectclass=*" +# empty - set the filter to "objectclass=*" +# attr names - a comma separated list of attributes to form the filter +# + +certmap default default +#default:DNComps +#default:FilterComps e, uid +#default:verifycert on +#default:CmapLdapAttr certSubjectDN +#default:library <path_to_shared_lib_or_dll> +#default:InitFn <Init function's name> +default:DNComps +default:FilterComps uid diff --git a/install/share/default-aci.ldif b/install/share/default-aci.ldif new file mode 100644 index 00000000..25bd3b22 --- /dev/null +++ b/install/share/default-aci.ldif @@ -0,0 +1,38 @@ +# $SUFFIX (base entry) +# FIXME: We need to allow truly anonymous access only to NIS data for older clients. We need to allow broad access to most attributes only to authenticated users +dn: $SUFFIX +changetype: modify +add: aci +aci: (targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey")(version 3.0; acl "Enable Anonymous access"; allow (read, search, compare) userdn = "ldap:///anyone";) +aci: (targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey")(version 3.0; acl "Admin can manage any entry"; allow (all) userdn = "ldap:///uid=admin,cn=users,cn=accounts,$SUFFIX";) +aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword")(version 3.0; acl "Self can write own password"; allow (write) userdn="ldap:///self";) +aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Admins can write passwords"; allow (add,delete,write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) +aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Password change service can read/write passwords"; allow (read, write) userdn="ldap:///krbprincipalname=kadmin/changepw@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";) +aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "KDC System Account can access passwords"; allow (all) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) +aci: (targetattr = "krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCount")(version 3.0; acl "KDC System Account can update some fields"; allow (write) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) +aci: (targetattr = "krbPrincipalName || krbUPEnabled || krbMKey || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData || krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCount")(version 3.0; acl "Only the KDC System Account has access to kerberos material"; allow (read, search, compare) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) +aci: (targetfilter = "(|(objectClass=person)(objectClass=krbPrincipalAux)(objectClass=posixAccount)(objectClass=groupOfNames)(objectClass=posixGroup))")(targetattr != "aci || userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Account Admins can manage Users and Groups"; allow (add, delete, read, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) +aci: (targetfilter = "(objectClass=krbPwdPolicy)")(targetattr = "krbMaxPwdLife || krbMinPwdLife || krbPwdMinDiffChars || krbPwdMinLength || krbPwdHistoryLength")(version 3.0;acl "Admins can write password policies"; allow (read, search, compare, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) +aci: (targetattr = "givenName || sn || cn || displayName || title || initials || loginShell || gecos || homePhone || mobile || pager || facsimileTelephoneNumber || telephoneNumber || street || roomNumber || l || st || postalCode || manager || secretary || description || carLicense || labeledURI || inetUserHTTPURL || seeAlso || employeeType || businessCategory || ou")(version 3.0;acl "Self service";allow (write) userdn = "ldap:///self";) + +dn: cn=ipaConfig,cn=etc,$SUFFIX +changetype: modify +add: aci +aci: (targetfilter = "(objectClass=ipaGuiConfig)")(targetattr != "aci")(version 3.0;acl "Admins can change GUI config"; allow (read, search, compare, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) + +dn: cn=accounts,$SUFFIX +changetype: modify +add: aci +aci: (targetattr = "krbMaxPwdLife || krbMinPwdLife || krbPwdMinDiffChars || krbPwdMinLength || krbPwdHistoryLength")(version 3.0;acl "Admins can write password policy"; allow (write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) +aci: (targetattr = "aci")(version 3.0;acl "Admins can manage delegations"; allow (write, delete) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) + +dn: cn=radius,$SUFFIX +changetype: modify +add: aci +aci: (targetattr = "*")(version 3.0; acl "Only radius and admin can access radius service data"; deny (all) userdn!="ldap:///uid=admin,cn=users,cn=accounts,$SUFFIX || ldap:///krbprincipalname=radius/$FQDN@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";) +aci: (targetfilter = "(objectClass=radiusprofile)")(targetattr != "aci || userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Account Admins can manage Users and Groups"; allow (add, delete, read, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) + +dn: cn=services,cn=accounts,$SUFFIX +changetype: modify +add: aci +aci: (targetattr="krbPrincipalName || krbUPEnabled || krbPrincipalKey || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData")(version 3.0; acl "KDC System Account"; allow (read, search, compare, write) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) diff --git a/install/share/default-keytypes.ldif b/install/share/default-keytypes.ldif new file mode 100644 index 00000000..1d54a059 --- /dev/null +++ b/install/share/default-keytypes.ldif @@ -0,0 +1,25 @@ +#kerberos keytypes +dn: cn=$REALM,cn=kerberos,$SUFFIX +changetype: modify +add: krbSupportedEncSaltTypes +krbSupportedEncSaltTypes: aes256-cts:normal +krbSupportedEncSaltTypes: aes128-cts:normal +krbSupportedEncSaltTypes: des3-hmac-sha1:normal +krbSupportedEncSaltTypes: arcfour-hmac:normal +krbSupportedEncSaltTypes: des-hmac-sha1:normal +krbSupportedEncSaltTypes: des-cbc-md5:normal +krbSupportedEncSaltTypes: des-cbc-crc:normal +krbSupportedEncSaltTypes: des-cbc-crc:v4 +krbSupportedEncSaltTypes: des-cbc-crc:afs3 + +#kerberos keytypes +dn: cn=$REALM,cn=kerberos,$SUFFIX +changetype: modify +add: krbDefaultEncSaltTypes +krbDefaultEncSaltTypes: aes256-cts:normal +krbDefaultEncSaltTypes: aes128-cts:normal +krbDefaultEncSaltTypes: des3-hmac-sha1:normal +krbDefaultEncSaltTypes: arcfour-hmac:normal +krbDefaultEncSaltTypes: des-hmac-sha1:normal +krbDefaultEncSaltTypes: des-cbc-md5:normal + diff --git a/install/share/dna-posix.ldif b/install/share/dna-posix.ldif new file mode 100644 index 00000000..a8848545 --- /dev/null +++ b/install/share/dna-posix.ldif @@ -0,0 +1,39 @@ +# add container for posix configuration + +dn: cn=Posix,cn=ipa-dna,cn=plugins,cn=config +changetype: add +objectclass: top +objectclass: nsContainer +objectclass: extensibleObject +cn: Posix + +# add plugin configuration for posix users + +dn: cn=Accounts,cn=Posix,cn=ipa-dna,cn=plugins,cn=config +changetype: add +objectclass: top +objectclass: extensibleObject +cn: Accounts +dnaType: uidNumber +dnaNextValue: 1100 +dnaInterval: 1 +dnaMaxValue: 1000000000 +dnaMagicRegen: 999 +dnaFilter: (objectclass=posixAccount) +dnaScope: $SUFFIX + +# add plugin configuration for posix groups + +dn: cn=Groups,cn=Posix,cn=ipa-dna,cn=plugins,cn=config +changetype: add +objectclass: top +objectclass: extensibleObject +cn: Groups +dnaType: gidNumber +dnaNextValue: 1100 +dnaInterval: 1 +dnaMaxValue: 1000000000 +dnaMagicRegen: 999 +dnaFilter: (objectclass=posixGroup) +dnaScope: $SUFFIX + diff --git a/install/share/encrypted_attribute.ldif b/install/share/encrypted_attribute.ldif new file mode 100644 index 00000000..3f5e1b43 --- /dev/null +++ b/install/share/encrypted_attribute.ldif @@ -0,0 +1,6 @@ +dn: cn=$ENCRYPTED_ATTRIBUTE, cn=encrypted attributes, cn=userRoot, cn=ldbm database, cn=plugins, cn=config +changetype: add +objectClass: top +objectClass: nsAttributeEncryption +cn: $ENCRYPTED_ATTRIBUTE +nsEncryptionAlgorithm: AES diff --git a/install/share/fedora-ds.init.patch b/install/share/fedora-ds.init.patch new file mode 100644 index 00000000..865611d9 --- /dev/null +++ b/install/share/fedora-ds.init.patch @@ -0,0 +1,12 @@ +--- /etc/init.d/dirsrv.orig 2007-07-06 18:21:30.000000000 -0400 ++++ /etc/init.d/dirsrv 2007-05-18 19:36:24.000000000 -0400 +@@ -10,6 +10,9 @@ + # datadir: /var/lib/dirsrv/slapd-<instance name> + # + ++# Get config. ++[ -r /etc/sysconfig/dirsrv ] && . /etc/sysconfig/dirsrv ++ + # Source function library. + if [ -f /etc/rc.d/init.d/functions ] ; then + . /etc/rc.d/init.d/functions diff --git a/install/share/indices.ldif b/install/share/indices.ldif new file mode 100644 index 00000000..05c27655 --- /dev/null +++ b/install/share/indices.ldif @@ -0,0 +1,93 @@ +dn: cn=krbPrincipalName,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +objectClass:top +objectClass:nsIndex +cn:krbPrincipalName +nsSystemIndex:false +nsIndexType:eq +nsIndexType:sub + +dn: cn=ou,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +objectClass:top +objectClass:nsIndex +cn:ou +nsSystemIndex:false +nsIndexType:eq +nsIndexType:sub + +dn: cn=carLicense,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +objectClass:top +objectClass:nsIndex +cn:carLicense +nsSystemIndex:false +nsIndexType:eq +nsIndexType:sub + +dn: cn=title,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +objectClass:top +objectClass:nsIndex +cn:title +nsSystemIndex:false +nsIndexType:eq +nsIndexType:sub + +dn: cn=manager,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +objectClass:top +objectClass:nsIndex +cn:manager +nsSystemIndex:false +nsIndexType:eq + +dn: cn=secretary,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +objectClass:top +objectClass:nsIndex +cn:secretary +nsSystemIndex:false +nsIndexType:eq + +dn: cn=displayname,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +objectClass:top +objectClass:nsIndex +cn:displayname +nsSystemIndex:false +nsIndexType:eq +nsIndexType:sub + +dn: cn=uid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: modify +add: nsIndexType +nsIndexType:sub + +dn: cn=uidnumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +objectClass:top +objectClass:nsIndex +cn:uidnumber +nsSystemIndex:false +nsIndexType:eq +nsMatchingRule: integerOrderingMatch + +dn: cn=gidnumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: add +objectClass:top +objectClass:nsIndex +cn:gidnumber +nsSystemIndex:false +nsIndexType:eq +nsMatchingRule: integerOrderingMatch + +dn: cn=ntUniqueId,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: modify +replace: nsIndexType +nsIndexType: eq,pres + +dn: cn=ntUserDomainId,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +changetype: modify +replace: nsIndexType +nsIndexType: eq,pres diff --git a/install/share/kdc.conf.template b/install/share/kdc.conf.template new file mode 100644 index 00000000..0a574783 --- /dev/null +++ b/install/share/kdc.conf.template @@ -0,0 +1,15 @@ +[kdcdefaults] + kdc_ports = 88 + kdc_tcp_ports = 88 + +[realms] + $REALM = { + master_key_type = des3-hmac-sha1 + supported_enctypes = aes256-cts:normal aes128-cts:normal 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 + max_life = 7d + max_renewable_life = 14d + acl_file = /var/kerberos/krb5kdc/kadm5.acl + dict_file = /usr/share/dict/words + default_principal_flags = +preauth +; admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab + } diff --git a/install/share/kerberos.ldif b/install/share/kerberos.ldif new file mode 100644 index 00000000..f1b651d5 --- /dev/null +++ b/install/share/kerberos.ldif @@ -0,0 +1,16 @@ +#kerberos user +dn: uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX +changetype: add +objectclass: account +objectclass: simplesecurityobject +uid: kdc +userPassword: $PASSWORD + +#kerberos base object +dn: cn=kerberos,$SUFFIX +changetype: add +objectClass: krbContainer +objectClass: top +cn: kerberos +aci: (targetattr="*")(version 3.0; acl "KDC System Account"; allow (all) userdn= "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) + diff --git a/install/share/krb.con.template b/install/share/krb.con.template new file mode 100644 index 00000000..d75a8f60 --- /dev/null +++ b/install/share/krb.con.template @@ -0,0 +1,2 @@ +$REALM $DOMAIN +$REALM $DOMAIN admin server diff --git a/install/share/krb5.conf.template b/install/share/krb5.conf.template new file mode 100644 index 00000000..b81cedfe --- /dev/null +++ b/install/share/krb5.conf.template @@ -0,0 +1,42 @@ +[logging] + default = FILE:/var/log/krb5libs.log + kdc = FILE:/var/log/krb5kdc.log + admin_server = FILE:/var/log/kadmind.log + +[libdefaults] + default_realm = $REALM + dns_lookup_realm = true + dns_lookup_kdc = true + ticket_lifetime = 24h + forwardable = yes + +[realms] + $REALM = { + kdc = $FQDN:88 + admin_server = $FQDN:749 + default_domain = $DOMAIN +} + +[domain_realm] + .$DOMAIN = $REALM + $DOMAIN = $REALM + +[appdefaults] + pam = { + debug = false + ticket_lifetime = 36000 + renew_lifetime = 36000 + forwardable = true + krb4_convert = false + } + +[dbmodules] + $REALM = { + db_library = kldap + ldap_servers = ldap://127.0.0.1/ + ldap_kerberos_container_dn = cn=kerberos,$SUFFIX + ldap_kdc_dn = uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX + ldap_kadmind_dn = uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX + ldap_service_password_file = /var/kerberos/krb5kdc/ldappwd + } + diff --git a/install/share/krb5.ini.template b/install/share/krb5.ini.template new file mode 100644 index 00000000..89f4a370 --- /dev/null +++ b/install/share/krb5.ini.template @@ -0,0 +1,19 @@ +[libdefaults] + default_realm = $REALM + krb4_config = /usr/kerberos/lib/krb.conf + krb4_realms = /usr/kerberos/lib/krb.realms + dns_lookup_kdc = true + +[realms] + $REALM = { + admin_server = $FQDN + kdc = $FQDN + default_domain = $REALM + } + +[domain_realm] + .$DOMAIN = $REALM + $DOMAIN = $REALM + +[logging] +# kdc = CONSOLE diff --git a/install/share/krbrealm.con.template b/install/share/krbrealm.con.template new file mode 100644 index 00000000..c6781386 --- /dev/null +++ b/install/share/krbrealm.con.template @@ -0,0 +1,3 @@ +.$REALM $REALM +.$REALM. $REALM +$REALM $REALM diff --git a/install/share/master-entry.ldif b/install/share/master-entry.ldif new file mode 100644 index 00000000..09c1d44f --- /dev/null +++ b/install/share/master-entry.ldif @@ -0,0 +1,7 @@ +dn: cn=$FQHN,cn=masters,cn=ipa,cn=etc,$SUFFIX +changetype: add +objectclass: top +objectclass: extensibleObject +cn: $FQHN +dnabase: 1100 +dnainterval: 4 diff --git a/install/share/memberof-task.ldif b/install/share/memberof-task.ldif new file mode 100644 index 00000000..827949e3 --- /dev/null +++ b/install/share/memberof-task.ldif @@ -0,0 +1,8 @@ +dn: cn=IPA install $TIME, cn=memberof task, cn=tasks, cn=config +changetype: add +objectClass: top +objectClass: extensibleObject +cn: IPA install +basedn: $SUFFIX +filter: (objectclass=*) +ttl: 10 diff --git a/install/share/ntp.conf.server.template b/install/share/ntp.conf.server.template new file mode 100644 index 00000000..09149dfc --- /dev/null +++ b/install/share/ntp.conf.server.template @@ -0,0 +1,50 @@ +# Permit time synchronization with our time source, but do not +# permit the source to query or modify the service on this system. +restrict default kod nomodify notrap +restrict -6 default kod nomodify notrap + +# Permit all access over the loopback interface. This could +# be tightened as well, but to do so would effect some of +# the administrative functions. +restrict 127.0.0.1 +restrict -6 ::1 + +# Hosts on local network are less restricted. +#restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap + +# Use public servers from the pool.ntp.org project. +# Please consider joining the pool (http://www.pool.ntp.org/join.html). +server $SERVERA +server $SERVERB +server $SERVERC + +#broadcast 192.168.1.255 key 42 # broadcast server +#broadcastclient # broadcast client +#broadcast 224.0.1.1 key 42 # multicast server +#multicastclient 224.0.1.1 # multicast client +#manycastserver 239.255.254.254 # manycast server +#manycastclient 239.255.254.254 key 42 # manycast client + +# Undisciplined Local Clock. This is a fake driver intended for backup +# and when no outside source of synchronized time is available. +server 127.127.1.0 # local clock +#fudge 127.127.1.0 stratum 10 + +# Drift file. Put this in a directory which the daemon can write to. +# No symbolic links allowed, either, since the daemon updates the file +# by creating a temporary in the same directory and then rename()'ing +# it to the file. +driftfile /var/lib/ntp/drift + +# Key file containing the keys and key identifiers used when operating +# with symmetric key cryptography. +keys /etc/ntp/keys + +# Specify the key identifiers which are trusted. +#trustedkey 4 8 42 + +# Specify the key identifier to use with the ntpdc utility. +#requestkey 8 + +# Specify the key identifier to use with the ntpq utility. +#controlkey 8 diff --git a/install/share/ntpd.sysconfig.template b/install/share/ntpd.sysconfig.template new file mode 100644 index 00000000..3412a0e8 --- /dev/null +++ b/install/share/ntpd.sysconfig.template @@ -0,0 +1,8 @@ +# Drop root to id 'ntp:ntp' by default. +OPTIONS="-x -u ntp:ntp -p /var/run/ntpd.pid" + +# Set to 'yes' to sync hw clock after successful ntpdate +SYNC_HWCLOCK=yes + +# Additional options for ntpdate +NTPDATE_OPTIONS="" diff --git a/install/share/preferences.html.template b/install/share/preferences.html.template new file mode 100644 index 00000000..2d3684dc --- /dev/null +++ b/install/share/preferences.html.template @@ -0,0 +1,33 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> + <title>Automatically set browser preferences + + +
+ +
+ + + + + diff --git a/install/share/referint-conf.ldif b/install/share/referint-conf.ldif new file mode 100644 index 00000000..533b97de --- /dev/null +++ b/install/share/referint-conf.ldif @@ -0,0 +1,11 @@ +dn: cn=referential integrity postoperation,cn=plugins,cn=config +changetype: modify +replace: nsslapd-pluginenabled +nsslapd-pluginenabled: on +- +add: nsslapd-pluginArg7 +nsslapd-pluginArg7: manager +- +add: nsslapd-pluginArg8 +nsslapd-pluginArg8: secretary + diff --git a/install/share/schema_compat.uldif b/install/share/schema_compat.uldif new file mode 100644 index 00000000..71732c99 --- /dev/null +++ b/install/share/schema_compat.uldif @@ -0,0 +1,50 @@ +# +# Enable the Schema Compatibility plugin provided by slapi-nis. +# +# http://slapi-nis.fedorahosted.org/ +# +dn: cn=Schema Compatibility, cn=plugins, cn=config +default:objectclass: top +default:objectclass: nsSlapdPlugin +default:objectclass: extensibleObject +default:cn: Schema Compatibility +default:nsslapd-pluginpath: /usr/lib$LIBARCH/dirsrv/plugins/schemacompat-plugin.so +default:nsslapd-plugininitfunc: schema_compat_plugin_init +default:nsslapd-plugintype: object +default:nsslapd-pluginenabled: on +default:nsslapd-pluginid: schema-compat-plugin +default:nsslapd-pluginversion: 0.8 +default:nsslapd-pluginvendor: redhat.com +default:nsslapd-plugindescription: Schema Compatibility Plugin + +dn: cn=users, cn=Schema Compatibility, cn=plugins, cn=config +default:objectClass: top +default:objectClass: extensibleObject +default:cn: users +default:schema-compat-container-group: cn=compat, $SUFFIX +default:schema-compat-container-rdn: cn=users +default:schema-compat-search-base: cn=users, cn=accounts, $SUFFIX +default:schema-compat-search-filter: objectclass=posixAccount +default:schema-compat-entry-rdn: uid=%{uid} +default:schema-compat-entry-attribute: objectclass=posixAccount +default:schema-compat-entry-attribute: gecos=%{cn} +default:schema-compat-entry-attribute: cn=%{cn} +default:schema-compat-entry-attribute: uidNumber=%{uidNumber} +default:schema-compat-entry-attribute: gidNumber=%{gidNumber} +default:schema-compat-entry-attribute: loginShell=%{loginShell} +default:schema-compat-entry-attribute: homeDirectory=%{homeDirectory} + +dn: cn=groups, cn=Schema Compatibility, cn=plugins, cn=config +default:objectClass: top +default:objectClass: extensibleObject +default:cn: groups +default:schema-compat-container-group: cn=compat, $SUFFIX +default:schema-compat-container-rdn: cn=groups +default:schema-compat-search-base: cn=groups, cn=accounts, $SUFFIX +default:schema-compat-search-filter: objectclass=posixGroup +default:schema-compat-entry-rdn: cn=%{cn} +default:schema-compat-entry-attribute: objectclass=posixGroup +default:schema-compat-entry-attribute: gidNumber=%{gidNumber} +default:schema-compat-entry-attribute: memberUid=%{memberUid} +default:schema-compat-entry-attribute: memberUid=%deref("member","uid") +default:schema-compat-entry-attribute: memberUid=%referred("cn=users","memberOf","uid") diff --git a/install/share/unique-attributes.ldif b/install/share/unique-attributes.ldif new file mode 100644 index 00000000..82ec52d1 --- /dev/null +++ b/install/share/unique-attributes.ldif @@ -0,0 +1,35 @@ +dn: cn=krbPrincipalName uniqueness,cn=plugins,cn=config +changetype: add +objectClass: top +objectClass: nsSlapdPlugin +objectClass: extensibleObject +cn: krbPrincipalName uniqueness +nsslapd-pluginPath: libattr-unique-plugin +nsslapd-pluginInitfunc: NSUniqueAttr_Init +nsslapd-pluginType: preoperation +nsslapd-pluginEnabled: on +nsslapd-pluginarg0: krbPrincipalName +nsslapd-pluginarg1: $SUFFIX +nsslapd-plugin-depends-on-type: database +nsslapd-pluginId: NSUniqueAttr +nsslapd-pluginVersion: 1.1.0 +nsslapd-pluginVendor: Fedora Project +nsslapd-pluginDescription: Enforce unique attribute values + +#dn: cn=uid uniqueness,cn=plugins,cn=config +#objectClass: top +#objectClass: nsSlapdPlugin +#objectClass: extensibleObject +#cn: uid uniqueness +#nsslapd-pluginPath: libattr-unique-plugin +#nsslapd-pluginInitfunc: NSUniqueAttr_Init +#nsslapd-pluginType: preoperation +#nsslapd-pluginEnabled: on +#nsslapd-pluginarg0: uid +#nsslapd-pluginarg1: cn=accounts,$SUFFIX +#nsslapd-plugin-depends-on-type: database +#nsslapd-pluginId: NSUniqueAttr +#nsslapd-pluginVersion: 1.1.0 +#nsslapd-pluginVendor: Fedora Project +#nsslapd-pluginDescription: Enforce unique attribute values +# diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am new file mode 100644 index 00000000..3f566175 --- /dev/null +++ b/install/tools/Makefile.am @@ -0,0 +1,24 @@ +NULL = + +SUBDIRS = \ + share \ + updates \ + $(NULL) + +sbin_SCRIPTS = \ + ipa-server-install \ + ipa-replica-install \ + ipa-replica-prepare \ + ipa-replica-manage \ + ipa-server-certinstall \ + ipactl \ + $(NULL) + +EXTRA_DIST = \ + README \ + $(sbin_SCRIPTS) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/install/tools/README b/install/tools/README new file mode 100644 index 00000000..a52cede0 --- /dev/null +++ b/install/tools/README @@ -0,0 +1,67 @@ + +Required packages: + +krb5-server +fedora-ds-base +fedora-ds-base-devel +openldap-clients +openldap-devel +krb5-server-ldap +cyrus-sasl-gssapi +httpd +mod_auth_kerb +ntp +openssl-devel +nspr-devel +nss-devel +mozldap-devel +mod_python +gcc +python-ldap +TurboGears +python-kerberos +python-krbV +python-tgexpandingformwidget +python-pyasn1 + +Installation example: + +TEMPORARY: until bug https://bugzilla.redhat.com/show_bug.cgi?id=248169 is + fixed. + +Please apply the fedora-ds.init.patch in freeipa/ipa-server/ipa-install/share/ +to patch your init scripts before running ipa-server-install. This tells +FDS where to find its kerberos keytab. + +Things done as root are denoted by #. Things done as a unix user are denoted +by %. + +# cd freeipa +# patch -p0 < ipa-server/ipa-install/share/fedora-ds.init.patch + +Now to do the installation. + +# cd freeipa +# make install + +To start an interactive installation use: +# /usr/sbin/ipa-server-install + +For more verbose output add the -d flag run the command with -h to see all options + +You have a basic working system with one super administrator (named admin). + +To create another administrative user: + +% kinit admin@FREEIPA.ORG +% /usr/sbin/ipa-adduser -f Test -l User test +% ldappasswd -Y GSSAPI -h localhost -s password uid=test,cn=users,cn=accounts,dc=freeipa,dc=org +% /usr/sbin/ipa-groupmod -a test admins + +An admin user is just a regular user in the group admin. + +Now you can destroy the old ticket and log in as test: + +% kdestroy +% kinit test@FREEIPA.ORG +% /usr/sbin/ipa-finduser test diff --git a/install/tools/ipa-compat-manage b/install/tools/ipa-compat-manage new file mode 100755 index 00000000..648e2c3a --- /dev/null +++ b/install/tools/ipa-compat-manage @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# Authors: Rob Crittenden +# Authors: Simo Sorce +# +# Copyright (C) 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 +# + +import sys +try: + from optparse import OptionParser + from ipaserver import ipaldap + from ipa import entity, ipaerror, ipautil, config + from ipaserver import installutils + from ipaserver.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR + import ldap + import logging + import re + import krbV + import platform + import shlex + import time + import random +except ImportError: + print >> sys.stderr, """\ +There was a problem importing one of the required Python modules. The +error was: + + %s +""" % sys.exc_value + sys.exit(1) + +def parse_options(): + usage = "%prog [options] \n" + usage += "%prog [options]\n" + parser = OptionParser(usage=usage, formatter=config.IPAFormatter()) + + parser.add_option("-d", "--debug", action="store_true", dest="debug", + help="Display debugging information about the update(s)") + parser.add_option("-y", dest="password", + help="File containing the Directory Manager password") + + config.add_standard_options(parser) + options, args = parser.parse_args() + + config.init_config(options) + + return options, args + +def get_dirman_password(): + """Prompt the user for the Directory Manager password and verify its + correctness. + """ + password = installutils.read_password("Directory Manager", confirm=False, validate=False) + + return password + +def main(): + retval = 0 + loglevel = logging.NOTSET + files=['/usr/share/ipa/schema_compat.uldif'] + + options, args = parse_options() + if options.debug: + loglevel = logging.DEBUG + + if len(args) != 1: + print "You must specify one action, either enable or disable" + sys.exit(1) + elif args[0] != "enable" and args[0] != "disable": + print "Unrecognized action [" + args[0] + "]" + sys.exit(1) + + logging.basicConfig(level=loglevel, + format='%(levelname)s %(message)s') + + dirman_password = "" + if options.password: + pw = ipautil.template_file(options.password, []) + dirman_password = pw.strip() + else: + dirman_password = get_dirman_password() + + try: + try: + conn = ipaldap.IPAdmin(installutils.get_fqdn()) + conn.do_simple_bind(bindpw=dirman_password) + except ldap.LDAPError, e: + print "An error occurred while connecting to the server." + print "%s" % e[0]['desc'] + return 1 + + if args[0] == "enable": + try: + conn.getEntry("cn=Schema Compatibility,cn=plugins,cn=config", + ldap.SCOPE_BASE, "(objectclass=*)") + print "Plugin already Enabled" + retval = 2 + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + print "Enabling plugin" + except ldap.LDAPError, e: + print "An error occurred while talking to the server." + print "%s" % e[0]['desc'] + retval = 1 + + if retval == 0: + ld = LDAPUpdate(dm_password=dirman_password, sub_dict={}) + retval = ld.update(files) + if retval == 0: + print "This setting will not take effect until you restart Directory Server." + + elif args[0] == "disable": + # Make a quick hack foir now, directly delete the entries by name, + # In future we should add delete capabilites to LDAPUpdate + try: + conn.getEntry("cn=Schema Compatibility,cn=plugins,cn=config", + ldap.SCOPE_BASE, "(objectclass=*)") + conn.deleteEntry("cn=groups,cn=Schema Compatibility,cn=plugins,cn=config") + conn.deleteEntry("cn=users,cn=Schema Compatibility,cn=plugins,cn=config") + conn.deleteEntry("cn=Schema Compatibility,cn=plugins,cn=config") + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + print "Plugin is already disabled" + retval = 2 + except ldap.LDAPError, e: + print "An error occurred while talking to the server." + print "%s" % e[0]['desc'] + retval = 1 + + else: + retval = 1 + + finally: + if conn: + conn.unbind() + + return retval + +try: + if __name__ == "__main__": + sys.exit(main()) +except BadSyntax, e: + print "There is a syntax error in this update file:" + print " %s" % e + sys.exit(1) +except RuntimeError, e: + print "%s" % e + sys.exit(1) +except SystemExit, e: + sys.exit(e) +except KeyboardInterrupt, e: + sys.exit(1) +except config.IPAConfigError, e: + print "An IPA server to update cannot be found. Has one been configured yet?" + print "The error was: %s" % e + sys.exit(1) +except ipaerror, e: + print "An error occurred while performing operations: %s" % e + sys.exit(1) diff --git a/install/tools/ipa-fix-CVE-2008-3274 b/install/tools/ipa-fix-CVE-2008-3274 new file mode 100644 index 00000000..41d3abc9 --- /dev/null +++ b/install/tools/ipa-fix-CVE-2008-3274 @@ -0,0 +1,524 @@ +#!/usr/bin/python +# +# Upgrade configuration files to a newer template. + +etckrb5conf = "/etc/krb5.conf" +krb5dir = "/var/kerberos/krb5kdc" +cachedir = "/var/cache/ipa" +libdir = "/var/lib/ipa" +basedir = libdir+"/mkey" +ourkrb5conf = basedir+"/krb5.conf" +ldappwdfile = basedir+"/ldappwd" + +import sys +try: + from optparse import OptionParser + + import os + import random + import time + import shutil + import getpass + + import ipa + import ipa.config + import ipa.ipautil + + import krbV + import ldap + + from ldap import LDAPError + from ldap import ldapobject + + from ipaclient import ipachangeconf + from ipaserver import ipaldap + + from pyasn1.type import univ, namedtype + import pyasn1.codec.ber.encoder + import pyasn1.codec.ber.decoder + import struct + import base64 + +except ImportError: + print >> sys.stderr, """\ +There was a problem importing one of the required Python modules. The +error was: + + %s +""" % sys.exc_value + sys.exit(1) + +def parse_options(): + parser = OptionParser("%prog [--check] [--fix] [--fix-replica]") + parser.add_option("--check", dest="check", action="store_true", + help="Just check for the vulnerability and report (default action)") + parser.add_option("--fix", dest="fix", action="store_true", + help="Run checks and start procedure to fix the problem") + parser.add_option("--fix-replica", dest="fix_replica", action="store_true", + help="Fix a replica after the tool has been tun with --fix on another master") + + ipa.config.add_standard_options(parser) + options, args = parser.parse_args() + + ipa.config.verify_args(parser, args) + if not options.fix and not options.fix_replica and not options.check: + parser.error("please specify at least one option") + + ipa.config.init_config(options) + + return options, args + +def check_vuln(realm, suffix): + + try: + conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") + conn.simple_bind() + msgid = conn.search("cn="+realm+",cn=kerberos,"+suffix, + ldap.SCOPE_BASE, + "(objectclass=krbRealmContainer)", + ("krbmkey", "cn")) + res = conn.result(msgid) + conn.unbind() + + if len(res) != 2: + err = 'Realm Container not found, unable to proceed' + print err + raise Exception, err + + if 'krbmkey' in res[1][0][1]: + print 'System vulnerable' + return 1 + else: + print 'System *not* vulnerable' + return 0 + except Exception, e: + print "Could not connect to the LDAP server, unable to check server" + print "("+type(e)+")("+dir(e)+")" + raise e + +# We support only des3 encoded stash files for now +def generate_new_stash_file(file): + + odd_parity_bytes_pool = ['\x01', '\x02', '\x04', '\x07', '\x08', '\x0b', + '\r', '\x0e', '\x10', '\x13', '\x15', '\x16', '\x19', '\x1a', '\x1c', + '\x1f', ' ', '#', '%', '&', ')', '*', ',', '/', '1', '2', '4', '7', '8', + ';', '=', '>', '@', 'C', 'E', 'F', 'I', 'J', 'L', 'O', 'Q', 'R', 'T', + 'W', 'X', '[', ']', '^', 'a', 'b', 'd', 'g', 'h', 'k', 'm', 'n', 'p', + 's', 'u', 'v', 'y', 'z', '|', '\x7f', '\x80', '\x83', '\x85', '\x86', + '\x89', '\x8a', '\x8c', '\x8f', '\x91', '\x92', '\x94', '\x97', '\x98', + '\x9b', '\x9d', '\x9e', '\xa1', '\xa2', '\xa4', '\xa7', '\xa8', '\xab', + '\xad', '\xae', '\xb0', '\xb3', '\xb5', '\xb6', '\xb9', '\xba', '\xbc', + '\xbf', '\xc1', '\xc2', '\xc4', '\xc7', '\xc8', '\xcb', '\xcd', '\xce', + '\xd0', '\xd3', '\xd5', '\xd6', '\xd9', '\xda', '\xdc', '\xdf', '\xe0', + '\xe3', '\xe5', '\xe6', '\xe9', '\xea', '\xec', '\xef', '\xf1', '\xf2', + '\xf4', '\xf7', '\xf8', '\xfb', '\xfd', '\xfe'] + + pool_len = len(odd_parity_bytes_pool) + keytype = 16 # des3 + keydata = "" + + r = random.SystemRandom() + for k in range(24): + keydata += r.choice(odd_parity_bytes_pool) + + format = '=hi%ss' % len(keydata) + s = struct.pack(format, keytype, len(keydata), keydata) + try: + fd = open(file, "w") + fd.write(s) + except os.error, e: + logging.critical("failed to write stash file") + raise e + +# clean up procedures +def change_mkey_cleanup(password): + try: + os.stat(basedir) + except: + return None + try: + # always remove ldappwdfile as it contains the Directory Manager password + os.remove(ldappwdfile) + except: + pass + + # tar and encrypt the working dir so that we do not leave sensitive data + # around unproteceted + curtime = time.strftime("%Y%m%d%H%M%S",time.gmtime()) + tarfile = libdir+"/ipa-change-mkey-"+curtime+".tar" + gpgfile = tarfile+".gpg" + args = ['/bin/tar', '-C', libdir, '-cf', tarfile, 'mkey'] + ipa.ipautil.run(args) + ipa.ipautil.encrypt_file(tarfile, gpgfile, password, cachedir) + os.remove(tarfile) + shutil.rmtree(basedir, ignore_errors=True) + + return "The temporary working directory with backup dump files has been securely archived and gpg-encrypted as "+gpgfile+" using the Directory Manager password." + +def change_mkey(password = None, quiet = False): + + krbctx = krbV.default_context() + + realm = krbctx.default_realm + suffix = ipa.ipautil.realm_to_suffix(realm) + + backupfile = basedir+"/backup.dump" + convertfile = basedir+"/convert.dump" + oldstashfile = krb5dir+"/.k5."+realm + newstashfile = basedir+"/.new.mkey" + bkpstashfile = basedir+"/.k5."+realm + + if os.getuid() != 0: + print "ERROR: This command must be run as root" + sys.exit(1) + + print "DANGER: This is a dangerous operation, make sure you backup all your IPA data before running the tool" + print "This command will restart your Directory and KDC Servers." + + #TODO: ask for confirmation + if not ipa.ipautil.user_input("Do you want to proceed and change the Kerberos Master key?", False): + print "" + print "Aborting..." + return 1 + + if not password: + password = getpass.getpass("Directory Manager password: ") + + # get a connection to the DS + try: + conn = ipaldap.IPAdmin(ipa.config.config.default_server[0]) + conn.do_simple_bind(bindpw=password) + except Exception, e: + print "ERROR: Could not connect to the Directory Server on "+ipa.config.config.default_server[0]+" ("+str(e)+")" + return 1 + + # Wipe basedir and recreate it + shutil.rmtree(basedir, ignore_errors=True) + os.mkdir(basedir, 0700) + + generate_new_stash_file(newstashfile) + + # Generate conf files + try: + shutil.copyfile(etckrb5conf, ourkrb5conf) + + krbconf = ipachangeconf.IPAChangeConf("IPA Installer") + krbconf.setOptionAssignment(" = ") + krbconf.setSectionNameDelimiters(("[","]")) + krbconf.setSubSectionDelimiters(("{","}")) + krbconf.setIndent((""," "," ")) + + #OPTS + opts = [{'name':'ldap_kadmind_dn', 'type':'option', 'action':'set', 'value':'cn=Directory Manager'}, + {'name':'ldap_service_password_file', 'type':'option', 'action':'set', 'value':ldappwdfile}] + + #REALM + realmopts = [{'name':realm, 'type':'subsection', 'action':'set', 'value':opts}] + + #DBMODULES + dbopts = [{'name':'dbmodules', 'type':'section', 'action':'set', 'value':realmopts}] + + krbconf.changeConf(ourkrb5conf, dbopts); + + hexpwd = "" + for x in password: + hexpwd += (hex(ord(x))[2:]) + pwd_fd = open(ldappwdfile, "w") + pwd_fd.write("cn=Directory Manager#{HEX}"+hexpwd+"\n") + pwd_fd.close() + os.chmod(ldappwdfile, 0600) + + except Exception, e: + print "Failed to create custom configuration files ("+str(e)+") aborting..." + return 1 + + #Set environment vars so that the modified krb5.conf is used + os.environ['KRB5_CONFIG'] = ourkrb5conf + + #Backup the kerberos key material for recovery if needed + args = ["/usr/kerberos/sbin/kdb5_util", "dump", "-verbose", backupfile] + print "Performing safety backup of the key material" + try: + output = ipa.ipautil.run(args) + except ipa.ipautil.CalledProcessError, e: + print "Failed to backup key material ("+str(e)+"), aborting ..." + return 1 + + if not quiet: + princlist = output[1].split('\n') + print "Principals stored into the backup file "+backupfile+":" + for p in princlist: + print p + print "" + + #Convert the kerberos keys to the new master key + args = ["/usr/kerberos/sbin/kdb5_util", "dump", "-verbose", "-new_mkey_file", newstashfile, convertfile] + print "Converting key material to new master key" + try: + output = ipa.ipautil.run(args) + except ipa.ipautil.CalledProcessError, e: + print "Failed to convert key material, aborting ..." + return 1 + + savedprinclist = output[1].split('\n') + + if not quiet: + princlist = output[1].split('\n') + print "Principals dumped for conversion:" + for p in princlist: + print p + print "" + + #Stop the KDC + args = ["/etc/init.d/krb5kdc", "stop"] + try: + output = ipa.ipautil.run(args) + if output[0]: + print output[0] + if output[1]: + print output[1] + except ipa.ipautil.CalledProcessError, e: + print "WARNING: Failed to restart the KDC ("+str(e)+")" + print "You will have to manually restart the KDC when the operation is completed" + + #Change the mkey into ldap + try: + stash = open(newstashfile, "r") + keytype = struct.unpack('h', stash.read(2))[0] + keylen = struct.unpack('i', stash.read(4))[0] + keydata = stash.read(keylen) + + #encode it in the asn.1 attribute + MasterKey = univ.Sequence() + MasterKey.setComponentByPosition(0, univ.Integer(keytype)) + MasterKey.setComponentByPosition(1, univ.OctetString(keydata)) + krbMKey = univ.Sequence() + krbMKey.setComponentByPosition(0, univ.Integer(0)) #we have no kvno + krbMKey.setComponentByPosition(1, MasterKey) + asn1key = pyasn1.codec.ber.encoder.encode(krbMKey) + + dn = "cn="+realm+",cn=kerberos,"+suffix + mod = [(ldap.MOD_REPLACE, 'krbMKey', str(asn1key))] + conn.modify_s(dn, mod) + except Exception, e: + print "ERROR: Failed to upload the Master Key from the Stash file: "+newstashfile+" ("+str(e)+")" + return 1 + + #Backup old stash file and substitute with new + try: + shutil.move(oldstashfile, bkpstashfile) + shutil.copyfile(newstashfile, oldstashfile) + except Exception, e: + print "ERROR: An error occurred while installing the new stash file("+str(e)+")" + print "The KDC may fail to start if the correct stash file is not in place" + print "Verify that "+newstashfile+" has been correctly installed into "+oldstashfile + print "A backup copy of the old stash file should be saved in "+bkpstashfile + + #Finally upload the converted principals + args = ["/usr/kerberos/sbin/kdb5_util", "load", "-verbose", "-update", convertfile] + print "Uploading converted key material" + try: + output = ipa.ipautil.run(args) + except ipa.ipautil.CalledProcessError, e: + print "Failed to upload key material ("+e+"), aborting ..." + return 1 + + if not quiet: + princlist = output[1].split('\n') + print "Principals converted and uploaded:" + for p in princlist: + print p + print "" + + uploadedprinclist = output[1].split('\n') + + #Check for differences and report + d = [] + for p in savedprinclist: + if uploadedprinclist.count(p) == 0: + d.append(p) + if len(d) != 0: + print "WARNING: Not all dumped principals have been updated" + print "Principals not Updated:" + for p in d: + print p + + #Remove custom environ + del os.environ['KRB5_CONFIG'] + + #Restart Directory Server (the pwd plugin need to read the new mkey) + args = ["/etc/init.d/dirsrv", "restart"] + try: + output = ipa.ipautil.run(args) + if output[0]: + print output[0] + if output[1]: + print output[1] + except ipa.ipautil.CalledProcessError, e: + print "WARNING: Failed to restart the Directory Server ("+str(e)+")" + print "Please manually restart the DS with 'service dirsrv restart'" + + #Restart the KDC + args = ["/etc/init.d/krb5kdc", "start"] + try: + output = ipa.ipautil.run(args) + if output[0]: + print output[0] + if output[1]: + print output[1] + except ipa.ipautil.CalledProcessError, e: + print "WARNING: Failed to restart the KDC ("+str(e)+")" + print "Please manually restart the kdc with 'service krb5kdc start'" + + print "Master Password successfully changed" + #print "You MUST now copy the stash file "+oldstashfile+" to all the replicas and restart them!" + print "" + + return 0 + +def fix_replica(password, realm, suffix): + + try: + conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") + conn.simple_bind("cn=Directory Manager", password) + msgid = conn.search("cn="+realm+",cn=kerberos,"+suffix, + ldap.SCOPE_BASE, + "(objectclass=krbRealmContainer)", + ("krbmkey", "cn")) + res = conn.result(msgid) + conn.unbind() + krbmkey = res[1][0][1]['krbmkey'][0] + except Exception, e: + print "Could not connect to the LDAP server, unable to fix server" + print "("+type(e)+")("+dir(e)+")" + raise e + + krbMKey = pyasn1.codec.ber.decoder.decode(krbmkey) + keytype = int(krbMKey[0][1][0]) + keydata = str(krbMKey[0][1][1]) + + format = '=hi%ss' % len(keydata) + s = struct.pack(format, keytype, len(keydata), keydata) + try: + fd = open("/var/kerberos/krb5kdc/.k5."+realm, "w") + fd.write(s) + fd.close() + except os.error, e: + print "failed to write stash file" + raise e + + #restart KDC so that it can reload the new Master Key + os.system("/etc/init.d/krb5kdc restart") + +KRBMKEY_DENY_ACI = """ +(targetattr = "krbMKey")(version 3.0; acl "No external access"; deny (all) userdn != "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) +""" + +def fix_main(password, realm, suffix): + + #Run the change master key tool + print "Changing Kerberos master key" + try: + ret = change_mkey(password, True) + except SystemExit: + ret = 1 + pass + except Exception, e: + ret = 1 + print "%s" % str(e) + + try: + msg = change_mkey_cleanup(password) + if msg: + print msg + except Exception, e: + print "Failed to clean up the temporary location for the dump files and generate and encrypted archive with error:" + print e + print "Please securely archive/encrypt "+basedir + + if ret is not 0: + sys.exit(ret) + + #Finally upload new master key + + #get the Master Key from the stash file + try: + stash = open("/var/kerberos/krb5kdc/.k5."+realm, "r") + keytype = struct.unpack('h', stash.read(2))[0] + keylen = struct.unpack('i', stash.read(4))[0] + keydata = stash.read(keylen) + except os.error: + print "Failed to retrieve Master Key from Stash file: %s" + raise e + #encode it in the asn.1 attribute + MasterKey = univ.Sequence() + MasterKey.setComponentByPosition(0, univ.Integer(keytype)) + MasterKey.setComponentByPosition(1, univ.OctetString(keydata)) + krbMKey = univ.Sequence() + krbMKey.setComponentByPosition(0, univ.Integer(0)) #we have no kvno + krbMKey.setComponentByPosition(1, MasterKey) + asn1key = pyasn1.codec.ber.encoder.encode(krbMKey) + + dn = "cn=%s,cn=kerberos,%s" % (realm, suffix) + sub_dict = dict(REALM=realm, SUFFIX=suffix) + #protect the master key by adding an appropriate deny rule along with the key + mod = [(ldap.MOD_ADD, 'aci', ipa.ipautil.template_str(KRBMKEY_DENY_ACI, sub_dict)), + (ldap.MOD_REPLACE, 'krbMKey', str(asn1key))] + + conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") + conn.simple_bind("cn=Directory Manager", password) + conn.modify_s(dn, mod) + conn.unbind() + + print "\n" + print "This server is now correctly configured and the master-key has been changed and secured." + print "Please now run this tool with the --fix-replica option on all your other replicas." + print "Until you fix the replicas their KDCs will not work." + +def main(): + + options, args = parse_options() + + if options.fix or options.fix_replica: + password = getpass.getpass("Directory Manager password: ") + + krbctx = krbV.default_context() + realm = krbctx.default_realm + suffix = ipa.ipautil.realm_to_suffix(realm) + + try: + ret = check_vuln(realm, suffix) + except: + sys.exit(1) + + if options.fix_replica: + if ret is 1: + print "Your system is still vulnerable" + print "If you have already run this tool with --fix on a master then make sure your replication is working correctly, before runnig --fix-replica" + sys.exit(1) + try: + fix_replica(password, realm, suffix) + except Exception, e: + print "Unexpected error ("+str(e)+")" + sys.exit(1) + sys.exit(0) + + if options.check: + sys.exit(0) + + if options.fix: + if ret is 1: + try: + ret = fix_main(password, realm, suffix) + except Exception, e: + print "Unexpected error ("+str(e)+")" + sys.exit(1) + sys.exit(ret) + +try: + if __name__ == "__main__": + sys.exit(main()) +except SystemExit, e: + sys.exit(e) +except KeyboardInterrupt, e: + sys.exit(1) diff --git a/install/tools/ipa-ldap-updater b/install/tools/ipa-ldap-updater new file mode 100755 index 00000000..28fb1a17 --- /dev/null +++ b/install/tools/ipa-ldap-updater @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# Authors: Rob Crittenden +# +# Copyright (C) 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 +# + +# Documentation can be found at http://freeipa.org/page/LdapUpdate + +# TODO +# save undo files? + +import sys +try: + from optparse import OptionParser + from ipaserver import ipaldap + from ipa import entity, ipaerror, ipautil, config + from ipaserver import installutils + from ipaserver.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR + import ldap + import logging + import re + import krbV + import platform + import shlex + import time + import random +except ImportError: + print >> sys.stderr, """\ +There was a problem importing one of the required Python modules. The +error was: + + %s +""" % sys.exc_value + sys.exit(1) + +def parse_options(): + usage = "%prog [options] input_file(s)\n" + usage += "%prog [options]\n" + parser = OptionParser(usage=usage, formatter=config.IPAFormatter()) + + parser.add_option("-d", "--debug", action="store_true", dest="debug", + help="Display debugging information about the update(s)") + parser.add_option("-t", "--test", action="store_true", dest="test", + help="Run through the update without changing anything") + parser.add_option("-y", dest="password", + help="File containing the Directory Manager password") + + config.add_standard_options(parser) + options, args = parser.parse_args() + + config.init_config(options) + + return options, args + +def get_dirman_password(): + """Prompt the user for the Directory Manager password and verify its + correctness. + """ + password = installutils.read_password("Directory Manager", confirm=False, validate=False) + + return password + +def main(): + loglevel = logging.INFO + + options, args = parse_options() + if options.debug: + loglevel = logging.DEBUG + + logging.basicConfig(level=loglevel, + format='%(levelname)s %(message)s') + + dirman_password = "" + if options.password: + pw = ipautil.template_file(options.password, []) + dirman_password = pw.strip() + else: + dirman_password = get_dirman_password() + + ld = LDAPUpdate(dm_password=dirman_password, sub_dict={}, live_run=not options.test) + + files=[] + if len(args) < 1: + files = ld.get_all_files(UPDATES_DIR) + else: + files = args + + modified = ld.update(files) + + if modified and options.test: + return 2 + else: + return 0 + +try: + if __name__ == "__main__": + sys.exit(main()) +except BadSyntax, e: + print "There is a syntax error in this update file:" + print " %s" % e + sys.exit(1) +except RuntimeError, e: + print "%s" % e + sys.exit(1) +except SystemExit, e: + sys.exit(e) +except KeyboardInterrupt, e: + sys.exit(1) +except config.IPAConfigError, e: + print "An IPA server to update cannot be found. Has one been configured yet?" + print "The error was: %s" % e + sys.exit(1) diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install new file mode 100644 index 00000000..c2704be0 --- /dev/null +++ b/install/tools/ipa-replica-install @@ -0,0 +1,312 @@ +#! /usr/bin/python -E +# Authors: Karl MacMillan +# +# Copyright (C) 2007 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 +# + +import sys + +import tempfile, os, pwd, traceback, logging, shutil +from ConfigParser import SafeConfigParser +import ldap + +from ipa import ipautil + +from ipaserver import dsinstance, replication, installutils, krbinstance, service +from ipaserver import httpinstance, ntpinstance, certs, ipaldap +from ipa import version + +CACERT="/usr/share/ipa/html/ca.crt" + +class ReplicaConfig: + def __init__(self): + self.realm_name = "" + self.domain_name = "" + self.master_host_name = "" + self.dirman_password = "" + self.ds_user = "" + self.host_name = "" + self.repl_password = "" + self.dir = "" + +def parse_options(): + from optparse import OptionParser + parser = OptionParser(version=version.VERSION) + parser.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false", + help="do not configure ntp", default=True) + parser.add_option("-d", "--debug", dest="debug", action="store_true", + default=False, help="gather extra debugging information") + parser.add_option("-p", "--password", dest="password", + help="Directory Manager (existing master) password") + + options, args = parser.parse_args() + + if len(args) != 1: + parser.error("you must provide a file generated by ipa-replica-prepare") + + return options, args[0] + +def get_dirman_password(): + return installutils.read_password("Directory Manager (existing master)", confirm=False, validate=False) + +def expand_info(filename, password): + top_dir = tempfile.mkdtemp("ipa") + tarfile = top_dir+"/files.tar" + dir = top_dir + "/realm_info" + ipautil.decrypt_file(filename, tarfile, password, top_dir) + ipautil.run(["tar", "xf", tarfile, "-C", top_dir]) + os.remove(tarfile) + + return top_dir, dir + +def read_info(dir, rconfig): + filename = dir + "/realm_info" + fd = open(filename) + config = SafeConfigParser() + config.readfp(fd) + + rconfig.realm_name = config.get("realm", "realm_name") + rconfig.master_host_name = config.get("realm", "master_host_name") + rconfig.ds_user = config.get("realm", "ds_user") + rconfig.domain_name = config.get("realm", "domain_name") + rconfig.host_name = config.get("realm", "destination_host") + +def get_host_name(): + hostname = installutils.get_fqdn() + try: + installutils.verify_fqdn(hostname) + except RuntimeError, e: + logging.error(str(e)) + sys.exit(1) + + return hostname + +def set_owner(config, dir): + pw = pwd.getpwnam(config.ds_user) + os.chown(dir, pw.pw_uid, pw.pw_gid) + +def install_ds(config): + dsinstance.check_existing_installation() + dsinstance.check_ports() + + # if we have a pkcs12 file, create the cert db from + # that. Otherwise the ds setup will create the CA + # cert + pkcs12_info = None + if ipautil.file_exists(config.dir + "/dscert.p12"): + pkcs12_info = (config.dir + "/dscert.p12", + config.dir + "/dirsrv_pin.txt") + + ds = dsinstance.DsInstance() + ds.create_instance(config.ds_user, config.realm_name, config.host_name, config.domain_name, config.dirman_password, pkcs12_info) + + return ds + +def install_krb(config): + krb = krbinstance.KrbInstance() + ldappwd_filename = config.dir + "/ldappwd" + kpasswd_filename = config.dir + "/kpasswd.keytab" + krb.create_replica(config.ds_user, config.realm_name, config.host_name, + config.domain_name, config.dirman_password, + ldappwd_filename, kpasswd_filename) + +def install_ca_cert(config): + if ipautil.file_exists(config.dir + "/ca.crt"): + try: + shutil.copy(config.dir + "/ca.crt", CACERT) + os.chmod(CACERT, 0444) + except Exception, e: + print "error copying files: " + str(e) + sys.exit(1) + +def install_http(config): + # if we have a pkcs12 file, create the cert db from + # that. Otherwise the ds setup will create the CA + # cert + pkcs12_info = None + if ipautil.file_exists(config.dir + "/httpcert.p12"): + pkcs12_info = (config.dir + "/httpcert.p12", + config.dir + "/http_pin.txt") + + http = httpinstance.HTTPInstance() + http.create_instance(config.realm_name, config.host_name, config.domain_name, False, pkcs12_info) + + # Now copy the autoconfiguration files + if ipautil.file_exists(config.dir + "/preferences.html"): + try: + shutil.copy(config.dir + "/preferences.html", "/usr/share/ipa/html/preferences.html") + shutil.copy(config.dir + "/configure.jar", "/usr/share/ipa/html/configure.jar") + except Exception, e: + print "error copying files: " + str(e) + sys.exit(1) + +def check_dirsrv(): + serverids = dsinstance.check_existing_installation() + if serverids: + print "" + print "An existing Directory Server has been detected." + if not ipautil.user_input("Do you wish to remove it and create a new one?", False): + print "" + print "Only a single Directory Server instance is allowed on an IPA" + print "server, the one used by IPA itself." + sys.exit(1) + + try: + service.stop("dirsrv") + except: + pass + + for serverid in serverids: + dsinstance.erase_ds_instance_data(serverid) + + (ds_unsecure, ds_secure) = dsinstance.check_ports() + if not ds_unsecure or not ds_secure: + print "IPA requires ports 389 and 636 for the Directory Server." + print "These are currently in use:" + if not ds_unsecure: + print "\t389" + if not ds_secure: + print "\t636" + sys.exit(1) + +def main(): + options, filename = parse_options() + installutils.standard_logging_setup("/var/log/ipareplica-install.log", options.debug) + + if not ipautil.file_exists(filename): + sys.exit("Replica file %s does not exist" % filename) + + check_dirsrv() + + # get the directory manager password + dirman_password = options.password + if not dirman_password: + try: + dirman_password = get_dirman_password() + except KeyboardInterrupt: + sys.exit(0) + + try: + top_dir, dir = expand_info(filename, dirman_password) + except Exception, e: + print "ERROR: Failed to decrypt or open the replica file." + print "Verify you entered the correct Directory Manager password." + sys.exit(1) + + config = ReplicaConfig() + read_info(dir, config) + config.dirman_password = dirman_password + host = get_host_name() + if config.host_name != host: + try: + print "This replica was created for '%s' but this machine is named '%s'" % (config.host_name, host) + if not ipautil.user_input("This may cause problems. Continue?", True): + sys.exit(0) + config.host_name = host + print "" + except KeyboardInterrupt: + sys.exit(0) + config.repl_password = ipautil.ipa_generate_password() + config.dir = dir + + # Try out the password + try: + conn = ipaldap.IPAdmin(config.master_host_name) + conn.do_simple_bind(bindpw=config.dirman_password) + conn.unbind() + except ldap.CONNECT_ERROR, e: + sys.exit("\nUnable to connect to LDAP server %s" % config.master_host_name) + except ldap.SERVER_DOWN, e: + sys.exit("\nUnable to connect to LDAP server %s" % config.master_host_name) + except ldap.INVALID_CREDENTIALS, e : + sys.exit("\nThe password provided is incorrect for LDAP server %s" % config.master_host_name) + + # Configure ntpd + if options.conf_ntp: + ntp = ntpinstance.NTPInstance() + ntp.create_instance() + + # Configure dirsrv + ds = install_ds(config) + + # Install CA cert so that we can do SSL connections with ldap + install_ca_cert(config) + + try: + repl = replication.ReplicationManager(config.host_name, config.dirman_password) + ret = repl.setup_replication(config.master_host_name, config.realm_name) + except Exception, e: + logging.debug("Connection error: %s" % e) + raise RuntimeError("Unable to connect to LDAP server %s." % config.host_name) + if ret != 0: + raise RuntimeError("Failed to start replication") + + install_krb(config) + install_http(config) + + # Create the config file + fd = open("/etc/ipa/ipa.conf", "w") + fd.write("[defaults]\n") + fd.write("server=" + config.host_name + "\n") + fd.write("realm=" + config.realm_name + "\n") + fd.write("domain=" + config.domain_name + "\n") + fd.close() + + # Create a Web Gui instance + webgui = httpinstance.WebGuiInstance() + webgui.create_instance() + + # Apply any LDAP updates. Needs to be done after the replica is synced-up + service.print_msg("Applying LDAP updates") + ds.apply_updates() + + service.restart("dirsrv") + service.restart("krb5kdc") + + # Call client install script + try: + ipautil.run(["/usr/sbin/ipa-client-install", "--on-master", "--unattended", "--domain", config.domain_name, "--server", config.host_name, "--realm", config.realm_name]) + except Exception, e: + print "Configuration of client side components failed!" + print "ipa-client-install returned: " + str(e) + raise RuntimeError("Failed to configure the client") + + ds.init_memberof() + +try: + if not os.geteuid()==0: + sys.exit("\nYou must be root to run this script.\n") + + main() + sys.exit(0) +except SystemExit, e: + sys.exit(e) +except Exception, e: + print "creation of replica failed: %s" % str(e) + message = str(e) + for str in traceback.format_tb(sys.exc_info()[2]): + message = message + "\n" + str + logging.debug(message) +except KeyboardInterrupt: + print "Installation cancelled." + +print "" +print "Your system may be partly configured." +print "Run /usr/sbin/ipa-server-install --uninstall to clean up." + +# the only way to get here is on error or ^C +sys.exit(1) diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage new file mode 100755 index 00000000..db8c32d5 --- /dev/null +++ b/install/tools/ipa-replica-manage @@ -0,0 +1,218 @@ +#! /usr/bin/python -E +# Authors: Karl MacMillan +# +# Copyright (C) 2007 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 +# +import sys + +import getpass, ldap, re, krbV +import traceback, logging + +from ipa import ipautil +from ipaserver import replication, ipaldap, dsinstance, installutils +from ipa import version + +def parse_options(): + from optparse import OptionParser + + parser = OptionParser(version=version.VERSION) + parser.add_option("-H", "--host", dest="host", help="starting host") + parser.add_option("-p", "--password", dest="dirman_passwd", help="Directory Manager password") + parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, + help="provide additional information") + parser.add_option("--port", type="int", dest="port", + help="port number of other server") + parser.add_option("--binddn", dest="binddn", + help="Bind DN to use with remote server") + parser.add_option("--bindpw", dest="bindpw", + help="Password for Bind DN to use with remote server") + parser.add_option("--winsync", dest="winsync", action="store_true", default=False, + help="This is a Windows Sync Agreement") + parser.add_option("--cacert", dest="cacert", + help="Full path and filename of CA certificate to use with TLS/SSL to the remote server") + parser.add_option("--win-subtree", dest="win_subtree", + help="DN of Windows subtree containing the users you want to sync (default cn=Users, logging.INFO: + mylogger.setLevel(logging.INFO) + # else user has already configured logging externally lower + return options, args + +def get_realm_name(): + c = krbV.default_context() + return c.default_realm + +def get_suffix(): + suffix = ipaldap.IPAdmin.normalizeDN(dsinstance.realm_to_suffix(get_realm_name())) + return suffix + +def get_host_name(): + hostname = installutils.get_fqdn() + try: + installutils.verify_fqdn(hostname) + except RuntimeError, e: + logging.error(str(e)) + sys.exit(1) + + return hostname + +def list_masters(replman, verbose): + dns = replman.find_replication_dns(replman.conn) + + for dn in dns: + entry = replman.conn.search_s(dn, ldap.SCOPE_SUBTREE)[0] + print entry.getValue('nsds5replicahost') + + if verbose: + print " last init status: %s" % entry.nsds5replicalastinitstatus + print " last init ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastinitend)) + print " last update status: %s" % entry.nsds5replicalastupdatestatus + print " last update ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastupdateend)) + +def del_master(replman, hostname): + try: + t = replman.get_agreement_type(hostname) + except ldap.NO_SUCH_OBJECT: + print "No replication agreement found for %s" % hostname + + if t == replication.IPA_REPLICA: + dirman_passwd = getpass.getpass("Directory Manager password (%s): " % hostname) + other_replman = replication.ReplicationManager(hostname, dirman_passwd) + other_replman.suffix = get_suffix() + other_replman.delete_agreement(replman.conn.host) + + replman.delete_agreement(hostname) + +def add_master(replman, hostname, options): + other_args = {} + if options.port: + other_args['port'] = options.port + if options.binddn: + other_args['binddn'] = options.binddn + if options.bindpw: + other_args['bindpw'] = options.bindpw + if options.cacert: + other_args['cacert'] = options.cacert + if options.win_subtree: + other_args['win_subtree'] = options.win_subtree + if options.passsync: + other_args['passsync'] = options.passsync + if options.winsync: + other_args['winsync'] = True + if not options.binddn or not options.bindpw or not options.cacert or not options.passsync: + logging.error("The arguments --binddn, --bindpw, --passsync and --cacert are required to create a winsync agreement") + sys.exit(1) + if options.cacert: + # have to install the given CA cert before doing anything else + ds = dsinstance.DsInstance(realm_name = get_realm_name(), + dm_password = replman.dirman_passwd) + if not ds.add_ca_cert(options.cacert): + logging.error("Could not load the required CA certificate file [%s]" % + options.cacert) + sys.exit(1) + else: + logging.info("Added CA certificate %s to certificate database for %s" % + (options.cacert, replman.hostname)) + # have to reconnect replman connection since the directory server was restarted + replman = replication.ReplicationManager(replman.hostname, replman.dirman_passwd) + logging.info("Restarted directory server " + replman.hostname) + replman.setup_replication(hostname, get_realm_name(), **other_args) + logging.info("Added agreement for other host " + hostname) + +def init_master(replman, dirman_passwd, hostname): + filter = "(&(nsDS5ReplicaHost=%s)(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement)))" % hostname + entry = replman.conn.search_s("cn=config", ldap.SCOPE_SUBTREE, filter) + if len(entry) == 0: + logging.error("Unable to find replication agreement for %s" % hostname) + sys.exit(1) + if len(entry) > 1: + logging.error("Found multiple agreements for %s. Only initializing the first one returned: %s" % (hostname, entry[0].dn)) + replman.initialize_replication(entry[0].dn, replman.conn) + ds = dsinstance.DsInstance(realm_name = get_realm_name(), dm_password = dirman_passwd) + ds.init_memberof() + +def synch_master(replman, hostname): + filter = "(&(nsDS5ReplicaHost=%s)(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement)))" % hostname + entry = replman.conn.search_s("cn=config", ldap.SCOPE_SUBTREE, filter) + if len(entry) == 0: + logging.error("Unable to find replication agreement for %s" % hostname) + sys.exit(1) + if len(entry) > 1: + logging.error("Found multiple agreements for %s. Only initializing the first one returned: %s" % (hostname, entry[0].dn)) + replman.force_synch(entry[0].dn, entry[0].nsds5replicaupdateschedule, replman.conn) + +def main(): + options, args = parse_options() + + if options.dirman_passwd: + dirman_passwd = options.dirman_passwd + else: + dirman_passwd = getpass.getpass("Directory Manager password: ") + + if options.host: + host = options.host + else: + host = get_host_name() + + r = replication.ReplicationManager(host, dirman_passwd) + r.suffix = get_suffix() + + if args[0] == "list": + list_masters(r, options.verbose) + elif args[0] == "del": + if len(args) != 2: + print "must provide hostname of master to delete" + sys.exit(1) + del_master(r, args[1]) + elif args[0] == "add": + if len(args) != 2: + print "must provide hostname of master to add" + sys.exit(1) + add_master(r, args[1], options) + elif args[0] == "init": + if len(args) != 2: + print "hostname of master to initialize is required." + sys.exit(1) + init_master(r, dirman_passwd, args[1]) + elif args[0] == "synch": + if len(args) != 2: + print "must provide hostname of supplier to synchronize with" + sys.exit(1) + synch_master(r, args[1]) + +try: + main() +except KeyboardInterrupt: + sys.exit(1) +except SystemExit, e: + sys.exit(e) +except ldap.INVALID_CREDENTIALS: + print "Invalid password" + sys.exit(1) +except Exception, e: + print "unexpected error: %s" % str(e) diff --git a/install/tools/ipa-replica-prepare b/install/tools/ipa-replica-prepare new file mode 100644 index 00000000..eb962b4c --- /dev/null +++ b/install/tools/ipa-replica-prepare @@ -0,0 +1,294 @@ +#! /usr/bin/python -E +# Authors: Karl MacMillan +# +# Copyright (C) 2007 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 +# + +import sys + +import logging, tempfile, shutil, os, pwd +import traceback +from ConfigParser import SafeConfigParser +import krbV +from optparse import OptionParser + +import ipa.config +from ipa import ipautil +from ipaserver import dsinstance, installutils, certs, ipaldap +from ipa import version +import ldap + +def parse_options(): + usage = "%prog [options] FQDN (e.g. replica.example.com)" + parser = OptionParser(usage=usage, version=version.VERSION) + + parser.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12", + help="install certificate for the directory server") + parser.add_option("--http_pkcs12", dest="http_pkcs12", + help="install certificate for the http server") + parser.add_option("--dirsrv_pin", dest="dirsrv_pin", + help="PIN for the Directory Server PKCS#12 file") + parser.add_option("--http_pin", dest="http_pin", + help="PIN for the Apache Server PKCS#12 file") + parser.add_option("-p", "--password", dest="password", + help="Directory Manager (existing master) password") + + ipa.config.add_standard_options(parser) + options, args = parser.parse_args() + + # If any of the PKCS#12 options are selected, all are required. Create a + # list of the options and count it to enforce that all are required without + # having a huge set of it blocks. + pkcs12 = [options.dirsrv_pkcs12, options.http_pkcs12, options.dirsrv_pin, options.http_pin] + cnt = pkcs12.count(None) + if cnt > 0 and cnt < 4: + parser.error("error: All PKCS#12 options are required if any are used.") + + if len(args) != 1: + parser.error("must provide the fully-qualified name of the replica") + + ipa.config.init_config(options) + + return options, args + +def get_host_name(): + hostname = installutils.get_fqdn() + try: + installutils.verify_fqdn(hostname) + except RuntimeError, e: + logging.error(str(e)) + sys.exit(1) + + return hostname + +def get_realm_name(): + try: + c = krbV.default_context() + return c.default_realm + except Exception, e: + return None + +def get_domain_name(): + try: + ipa.config.init_config() + domain_name = ipa.config.config.get_domain() + except Exception, e: + return None + + return domain_name + +def check_ipa_configuration(realm_name): + config_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name)) + if not ipautil.dir_exists(config_dir): + logging.error("could not find directory instance: %s" % config_dir) + sys.exit(1) + +def export_certdb(realm_name, ds_dir, dir, passwd_fname, fname, subject): + """realm is the kerberos realm for the IPA server. + ds_dir is the location of the master DS we are creating a replica for. + dir is the location of the files for the replica we are creating. + passwd_fname is the file containing the PKCS#12 password + fname is the filename of the PKCS#12 file for this cert (minus the .p12). + subject is the subject of the certificate we are creating + """ + try: + ds_ca = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name))) + ca = certs.CertDB(dir) + ca.create_from_cacert(ds_ca.cacert_fname) + ca.create_server_cert("Server-Cert", subject, ds_ca) + except Exception, e: + raise e + + pkcs12_fname = dir + "/" + fname + ".p12" + + try: + ca.export_pkcs12(pkcs12_fname, passwd_fname, "Server-Cert") + except ipautil.CalledProcessError, e: + print "error exporting CA certificate: " + str(e) + try: + os.unlink(pkcs12_fname) + os.unlink(passwd_fname) + except: + pass + + os.unlink(dir + "/cert8.db") + os.unlink(dir + "/key3.db") + os.unlink(dir + "/secmod.db") + os.unlink(dir + "/noise.txt") + if ipautil.file_exists(passwd_fname + ".orig"): + os.unlink(passwd_fname + ".orig") + +def get_ds_user(ds_dir): + uid = os.stat(ds_dir).st_uid + user = pwd.getpwuid(uid)[0] + + return user + +def save_config(dir, realm_name, host_name, ds_user, domain_name, dest_host): + config = SafeConfigParser() + config.add_section("realm") + config.set("realm", "realm_name", realm_name) + config.set("realm", "master_host_name", host_name) + config.set("realm", "ds_user", ds_user) + config.set("realm", "domain_name", domain_name) + config.set("realm", "destination_host", dest_host) + fd = open(dir + "/realm_info", "w") + config.write(fd) + +def copy_files(realm_name, dir): + config_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name)) + + try: + shutil.copy("/var/kerberos/krb5kdc/ldappwd", dir + "/ldappwd") + shutil.copy("/var/kerberos/krb5kdc/kpasswd.keytab", dir + "/kpasswd.keytab") + shutil.copy("/usr/share/ipa/html/ca.crt", dir + "/ca.crt") + if ipautil.file_exists("/usr/share/ipa/html/preferences.html"): + shutil.copy("/usr/share/ipa/html/preferences.html", dir + "/preferences.html") + shutil.copy("/usr/share/ipa/html/configure.jar", dir + "/configure.jar") + except Exception, e: + print "error copying files: " + str(e) + sys.exit(1) + +def get_dirman_password(): + return installutils.read_password("Directory Manager (existing master)", confirm=False, validate=False) + +def main(): + options, args = parse_options() + + replica_fqdn = args[0] + + if not ipautil.file_exists(certs.CA_SERIALNO) and not options.dirsrv_pin: + sys.exit("The replica must be created on the primary IPA server.\nIf you installed IPA with your own certificates using PKCS#12 files you must provide PKCS#12 files for any replicas you create as well.") + + print "Determining current realm name" + realm_name = get_realm_name() + if realm_name is None: + print "Unable to determine default realm" + sys.exit(1) + + check_ipa_configuration(realm_name) + + print "Getting domain name from LDAP" + domain_name = get_domain_name() + if domain_name is None: + print "Unable to determine LDAP default domain" + sys.exit(1) + + host_name = get_host_name() + if host_name == replica_fqdn: + print "You can't create a replica on itself" + sys.exit(1) + ds_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name)) + ds_user = get_ds_user(ds_dir) + + # get the directory manager password + dirman_password = options.password + if not options.password: + try: + dirman_password = get_dirman_password() + except KeyboardInterrupt: + sys.exit(0) + + # Try out the password + try: + conn = ipaldap.IPAdmin(host_name) + conn.do_simple_bind(bindpw=dirman_password) + conn.unbind() + except ldap.CONNECT_ERROR, e: + sys.exit("\nUnable to connect to LDAP server %s" % host_name) + except ldap.SERVER_DOWN, e: + sys.exit("\nUnable to connect to LDAP server %s" % host_name) + except ldap.INVALID_CREDENTIALS, e : + sys.exit("\nThe password provided is incorrect for LDAP server %s" % host_name) + + print "Preparing replica for %s from %s" % (replica_fqdn, host_name) + + top_dir = tempfile.mkdtemp("ipa") + dir = top_dir + "/realm_info" + os.mkdir(dir, 0700) + + if options.dirsrv_pin: + passwd = options.dirsrv_pin + else: + passwd = "" + + passwd_fname = dir + "/dirsrv_pin.txt" + fd = open(passwd_fname, "w") + fd.write("%s\n" % passwd) + fd.close() + + if options.dirsrv_pkcs12: + print "Copying SSL certificate for the Directory Server from %s" % options.dirsrv_pkcs12 + try: + shutil.copy(options.dirsrv_pkcs12, dir + "/dscert.p12") + except IOError, e: + print "Copy failed %s" % e + sys.exit(1) + else: + print "Creating SSL certificate for the Directory Server" + export_certdb(realm_name, ds_dir, dir, passwd_fname, "dscert", "cn=%s,ou=Fedora Directory Server" % replica_fqdn) + + if options.http_pin: + passwd = options.http_pin + else: + passwd = "" + + passwd_fname = dir + "/http_pin.txt" + fd = open(passwd_fname, "w") + fd.write("%s\n" % passwd) + fd.close() + + if options.http_pkcs12: + print "Copying SSL certificate for the Web Server from %s" % options.http_pkcs12 + try: + shutil.copy(options.http_pkcs12, dir + "/httpcert.p12") + except IOError, e: + print "Copy failed %s" % e + sys.exit(1) + else: + print "Creating SSL certificate for the Web Server" + export_certdb(realm_name, ds_dir, dir, passwd_fname, "httpcert", "cn=%s,ou=Apache Web Server" % replica_fqdn) + print "Copying additional files" + copy_files(realm_name, dir) + print "Finalizing configuration" + save_config(dir, realm_name, host_name, ds_user, domain_name, replica_fqdn) + + replicafile = "/var/lib/ipa/replica-info-" + replica_fqdn + encfile = replicafile+".gpg" + + print "Packaging replica information into %s" % encfile + ipautil.run(["/bin/tar", "cf", replicafile, "-C", top_dir, "realm_info"]) + ipautil.encrypt_file(replicafile, encfile, dirman_password, top_dir); + + os.remove(replicafile) + shutil.rmtree(dir) + +try: + if not os.geteuid()==0: + sys.exit("\nYou must be root to run this script.\n") + + main() +except SystemExit, e: + sys.exit(e) +except Exception, e: + print "preparation of replica failed: %s" % str(e) + message = str(e) + for str in traceback.format_tb(sys.exc_info()[2]): + message = message + "\n" + str + logging.debug(message) + print message + sys.exit(1) diff --git a/install/tools/ipa-server-certinstall b/install/tools/ipa-server-certinstall new file mode 100644 index 00000000..a0d11856 --- /dev/null +++ b/install/tools/ipa-server-certinstall @@ -0,0 +1,157 @@ +#! /usr/bin/python -E +# Authors: Karl MacMillan +# +# Copyright (C) 2007 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 +# + +import sys +import os +import pwd +import tempfile + +import traceback + +import krbV, ldap, getpass + +from ipa.ipautil import user_input +from ipaserver import certs, dsinstance, httpinstance, ipaldap, installutils + +def get_realm_name(): + c = krbV.default_context() + return c.default_realm + +def parse_options(): + from optparse import OptionParser + parser = OptionParser() + + parser.add_option("-d", "--dirsrv", dest="dirsrv", action="store_true", + default=False, help="install certificate for the directory server") + parser.add_option("-w", "--http", dest="http", action="store_true", + default=False, help="install certificate for the http server") + parser.add_option("--dirsrv_pin", dest="dirsrv_pin", + help="The password of the Directory Server PKCS#12 file") + parser.add_option("--http_pin", dest="http_pin", + help="The password of the Apache Server PKCS#12 file") + + options, args = parser.parse_args() + + if not options.dirsrv and not options.http: + parser.error("you must specify dirsrv and/or http") + if ((options.dirsrv and not options.dirsrv_pin) or + (options.http and not options.http_pin)): + parser.error("you must provide the password for the PKCS#12 file") + + if len(args) != 1: + parser.error("you must provide a pkcs12 filename") + + return options, args[0] + +def set_ds_cert_name(cert_name, dm_password): + conn = ipaldap.IPAdmin("127.0.0.1") + conn.simple_bind_s("cn=directory manager", dm_password) + + mod = [(ldap.MOD_REPLACE, "nsSSLPersonalitySSL", cert_name)] + + conn.modify_s("cn=RSA,cn=encryption,cn=config", mod) + + conn.unbind() + +def choose_server_cert(server_certs): + print "Please select the certificate to use:" + num = 1 + for cert in server_certs: + print "%d. %s" % (num, cert[0]) + num += 1 + + while 1: + num = user_input("Certificate number", 1) + print "" + if num < 1 or num > len(server_certs): + print "number out of range" + else: + break + + return server_certs[num - 1] + +def import_cert(dirname, pkcs12_fname, pkcs12_passwd, db_password): + cdb = certs.CertDB(dirname) + cdb.create_passwd_file(db_password) + cdb.create_certdbs() + [pw_fd, pw_name] = tempfile.mkstemp() + os.write(pw_fd, pkcs12_passwd) + os.close(pw_fd) + + try: + try: + cdb.import_pkcs12(pkcs12_fname, pw_name) + except RuntimeError, e: + print str(e) + sys.exit(1) + finally: + os.remove(pw_name) + + server_certs = cdb.find_server_certs() + if len(server_certs) == 0: + print "could not find a suitable server cert in import" + sys.exit(1) + elif len(server_certs) == 1: + server_cert = server_certs[0] + else: + server_cert = choose_server_cert(server_certs) + + cdb.trust_root_cert(server_cert[0]) + + return server_cert + +def main(): + options, pkcs12_fname = parse_options() + + try: + if options.dirsrv: + dm_password = getpass.getpass("Directory Manager password: ") + realm = get_realm_name() + dirname = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm)) + fd = open(dirname + "/pwdfile.txt") + passwd = fd.read() + fd.close() + + server_cert = import_cert(dirname, pkcs12_fname, options.dirsrv_pin, passwd) + set_ds_cert_name(server_cert[0], dm_password) + + if options.http: + dirname = httpinstance.NSS_DIR + server_cert = import_cert(dirname, pkcs12_fname, options.http_pin, "") + installutils.set_directive(httpinstance.NSS_CONF, 'NSSNickname', server_cert[0]) + + # Fix the database permissions + os.chmod(dirname + "/cert8.db", 0640) + os.chmod(dirname + "/key3.db", 0640) + os.chmod(dirname + "/secmod.db", 0640) + + pent = pwd.getpwnam("apache") + os.chown(dirname + "/cert8.db", 0, pent.pw_gid ) + os.chown(dirname + "/key3.db", 0, pent.pw_gid ) + os.chown(dirname + "/secmod.db", 0, pent.pw_gid ) + + except Exception, e: + print "an unexpected error occurred: %s" % str(e) + traceback.print_exc() + return 1 + + return 0 + +sys.exit(main()) diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install new file mode 100644 index 00000000..c9d5c5bf --- /dev/null +++ b/install/tools/ipa-server-install @@ -0,0 +1,622 @@ +#! /usr/bin/python -E +# Authors: Karl MacMillan +# +# Copyright (C) 2007 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 +# + + +# requires the following packages: +# fedora-ds-base +# openldap-clients +# nss-tools + +import sys +import os +import socket +import errno +import logging +import pwd +import subprocess +import signal +import shutil +import glob +import traceback +from optparse import OptionParser + +import ipaserver.dsinstance +import ipaserver.krbinstance +import ipaserver.bindinstance +import ipaserver.httpinstance +import ipaserver.ntpinstance + +from ipaserver import service +from ipa import version +from ipaserver.installutils import * + +from ipa import sysrestore +from ipa.ipautil import * + +pw_name = None + +def parse_options(): + parser = OptionParser(version=version.VERSION) + parser.add_option("-u", "--user", dest="ds_user", + help="ds user") + parser.add_option("-r", "--realm", dest="realm_name", + help="realm name") + parser.add_option("-n", "--domain", dest="domain_name", + help="domain name") + parser.add_option("-p", "--ds-password", dest="dm_password", + help="admin password") + parser.add_option("-P", "--master-password", dest="master_password", + help="kerberos master password (normally autogenerated)") + parser.add_option("-a", "--admin-password", dest="admin_password", + help="admin user kerberos password") + parser.add_option("-d", "--debug", dest="debug", action="store_true", + default=False, help="print debugging information") + parser.add_option("--hostname", dest="host_name", help="fully qualified name of server") + parser.add_option("--ip-address", dest="ip_address", help="Master Server IP Address") + parser.add_option("--setup-bind", dest="setup_bind", action="store_true", + default=False, help="configure bind with our zone file") + parser.add_option("-U", "--unattended", dest="unattended", action="store_true", + default=False, help="unattended installation never prompts the user") + parser.add_option("", "--uninstall", dest="uninstall", action="store_true", + default=False, help="uninstall an existing installation") + parser.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false", + help="do not configure ntp", default=True) + parser.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12", + help="PKCS#12 file containing the Directory Server SSL certificate") + parser.add_option("--http_pkcs12", dest="http_pkcs12", + help="PKCS#12 file containing the Apache Server SSL certificate") + parser.add_option("--dirsrv_pin", dest="dirsrv_pin", + help="The password of the Directory Server PKCS#12 file") + parser.add_option("--http_pin", dest="http_pin", + help="The password of the Apache Server PKCS#12 file") + parser.add_option("--no-host-dns", dest="no_host_dns", action="store_true", + default=False, + help="Do not use DNS for hostname lookup during installation") + + options, args = parser.parse_args() + + if options.uninstall: + if (options.ds_user or options.realm_name or + options.dm_password or options.admin_password or + options.master_password): + parser.error("error: In uninstall mode, -u, r, -p and -P options are not allowed") + elif options.unattended: + if (not options.ds_user or not options.realm_name or + not options.dm_password or not options.admin_password): + parser.error("error: In unattended mode you need to provide at least -u, -r, -p and -a options") + + # If any of the PKCS#12 options are selected, all are required. Create a + # list of the options and count it to enforce that all are required without + # having a huge set of it blocks. + pkcs12 = [options.dirsrv_pkcs12, options.http_pkcs12, options.dirsrv_pin, options.http_pin] + cnt = pkcs12.count(None) + if cnt > 0 and cnt < 4: + parser.error("error: All PKCS#12 options are required if any are used.") + + return options + +def signal_handler(signum, frame): + global ds + print "\nCleaning up..." + if ds: + print "Removing configuration for %s instance" % ds.serverid + ds.stop() + if ds.serverid: + ipaserver.dsinstance.erase_ds_instance_data (ds.serverid) + sys.exit(1) + +def read_host_name(host_default,no_host_dns=False): + host_name = "" + + print "Enter the fully qualified domain name of the computer" + print "on which you're setting up server software. Using the form" + print "." + print "Example: master.example.com." + print "" + print "" + if host_default == "": + host_default = "master.example.com" + while True: + host_name = user_input("Server host name", host_default, allow_empty = False) + print "" + try: + verify_fqdn(host_name,no_host_dns) + except Exception, e: + raise e + else: + break + return host_name + +def resolve_host(host_name): + ip = "" + try: + ip = socket.gethostbyname(host_name) + + if ip == "127.0.0.1" or ip == "::1": + print "The hostname resolves to the localhost address (127.0.0.1/::1)" + print "Please change your /etc/hosts file so that the hostname" + print "resolves to the ip address of your network interface." + print "The KDC service does not listen on localhost" + print "" + print "Please fix your /etc/hosts file and restart the setup program" + return None + + except: + print "Unable to lookup the IP address of the provided host" + return ip + +def verify_ip_address(ip): + is_ok = True + try: + socket.inet_pton(socket.AF_INET, ip) + except: + try: + socket.inet_pton(socket.AF_INET6, ip) + except: + print "Unable to verify IP address" + is_ok = False + return is_ok + +def read_ip_address(host_name): + while True: + ip = user_input("Please provide the IP address to be used for this host name", allow_empty = False) + + if ip == "127.0.0.1" or ip == "::1": + print "The IPA Server can't use localhost as a valid IP" + continue + + if not verify_ip_address(ip): + continue + + print "Adding ["+ip+" "+host_name+"] to your /etc/hosts file" + fstore.backup_file("/etc/hosts") + hosts_fd = open('/etc/hosts', 'r+') + hosts_fd.seek(0, 2) + hosts_fd.write(ip+'\t'+host_name+' '+host_name.split('.')[0]+'\n') + hosts_fd.close() + + return ip + +def read_ds_user(): + print "The server must run as a specific user in a specific group." + print "It is strongly recommended that this user should have no privileges" + print "on the computer (i.e. a non-root user). The setup procedure" + print "will give this user/group some permissions in specific paths/files" + print "to perform server-specific operations." + print "" + + ds_user = "" + try: + pwd.getpwnam('dirsrv') + + print "A user account named 'dirsrv' already exists. This is the user id" + print "that the Directory Server will run as." + print "" + if user_input("Do you want to use the existing 'dirsrv' account?", True): + ds_user = "dirsrv" + else: + print "" + ds_user = user_input_plain("Which account name do you want to use for the DS instance?", allow_empty = False, allow_spaces = False) + print "" + except KeyError: + ds_user = "dirsrv" + + return ds_user + +def read_domain_name(domain_name, unattended): + print "The domain name has been calculated based on the host name." + print "" + if not unattended: + domain_name = user_input("Please confirm the domain name", domain_name) + print "" + return domain_name + +def read_realm_name(domain_name, unattended): + print "The kerberos protocol requires a Realm name to be defined." + print "This is typically the domain name converted to uppercase." + print "" + + if unattended: + return domain_name.upper() + realm_name = user_input("Please provide a realm name", domain_name.upper()) + upper_dom = realm_name.upper() + if upper_dom != realm_name: + print "An upper-case realm name is required." + if not user_input("Do you want to use " + upper_dom + " as realm name?", True): + print "" + print "An upper-case realm name is required. Unable to continue." + sys.exit(1) + else: + realm_name = upper_dom + print "" + return realm_name + + +def read_dm_password(): + print "Certain directory server operations require an administrative user." + print "This user is referred to as the Directory Manager and has full access" + print "to the Directory for system management tasks and will be added to the" + print "instance of directory server created for IPA." + print "The password must be at least 8 characters long." + print "" + #TODO: provide the option of generating a random password + dm_password = read_password("Directory Manager") + return dm_password + +def read_admin_password(): + print "The IPA server requires an administrative user, named 'admin'." + print "This user is a regular system account used for IPA server administration." + print "" + #TODO: provide the option of generating a random password + admin_password = read_password("IPA admin") + return admin_password + +def check_dirsrv(unattended): + serverids = ipaserver.dsinstance.check_existing_installation() + if serverids: + print "" + print "An existing Directory Server has been detected." + if unattended or not user_input("Do you wish to remove it and create a new one?", False): + print "" + print "Only a single Directory Server instance is allowed on an IPA" + print "server, the one used by IPA itself." + sys.exit(1) + + try: + service.stop("dirsrv") + except: + pass + + for serverid in serverids: + ipaserver.dsinstance.erase_ds_instance_data(serverid) + + (ds_unsecure, ds_secure) = ipaserver.dsinstance.check_ports() + if not ds_unsecure or not ds_secure: + print "IPA requires ports 389 and 636 for the Directory Server." + print "These are currently in use:" + if not ds_unsecure: + print "\t389" + if not ds_secure: + print "\t636" + sys.exit(1) + +def uninstall(): + try: + run(["/usr/sbin/ipa-client-install", "--on-master", "--unattended", "--uninstall"]) + except Exception, e: + print "Uninstall of client side components failed!" + print "ipa-client-install returned: " + str(e) + pass + + ipaserver.ntpinstance.NTPInstance(fstore).uninstall() + ipaserver.bindinstance.BindInstance(fstore).uninstall() + ipaserver.httpinstance.WebGuiInstance().uninstall() + ipaserver.httpinstance.HTTPInstance(fstore).uninstall() + ipaserver.krbinstance.KrbInstance(fstore).uninstall() + ipaserver.dsinstance.DsInstance().uninstall() + fstore.restore_all_files() + return 0 + +def main(): + global ds + global pw_name + ds = None + + options = parse_options() + + if os.getegid() != 0: + print "Must be root to setup server" + return 1 + + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + + if options.uninstall: + standard_logging_setup("/var/log/ipaserver-uninstall.log", options.debug) + else: + standard_logging_setup("/var/log/ipaserver-install.log", options.debug) + print "\nThe log file for this installation can be found in /var/log/ipaserver-install.log" + + global fstore + fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + + if options.uninstall: + if not options.unattended: + print "\nThis is a NON REVERSIBLE operation and will delete all data and configuration!\n" + if not user_input("Are you sure you want to continue with the uninstall procedure?", False): + print "" + print "Aborting uninstall operation." + sys.exit(1) + + return uninstall() + + print "==============================================================================" + print "This program will setup the FreeIPA Server." + print "" + print "This includes:" + if options.conf_ntp: + print " * Configure the Network Time Daemon (ntpd)" + print " * Create and configure an instance of Directory Server" + print " * Create and configure a Kerberos Key Distribution Center (KDC)" + print " * Configure Apache (httpd)" + print " * Configure TurboGears" + if options.setup_bind: + print " * Configure DNS (bind)" + if not options.conf_ntp: + print "" + print "Excluded by options:" + print " * Configure the Network Time Daemon (ntpd)" + print "" + print "To accept the default shown in brackets, press the Enter key." + print "" + + check_dirsrv(options.unattended) + + ds_user = "" + realm_name = "" + host_name = "" + domain_name = "" + ip_address = "" + master_password = "" + dm_password = "" + admin_password = "" + + # check bind packages are installed + if options.setup_bind: + if not ipaserver.bindinstance.check_inst(): + print "--setup-bind was specified but bind is not installed on the system" + print "Please install bind and restart the setup program" + return 1 + + # check the hostname is correctly configured, it must be as the kldap + # utilities just use the hostname as returned by gethostbyname to set + # up some of the standard entries + + host_default = "" + if options.host_name: + host_default = options.host_name + else: + host_default = get_fqdn() + + if options.unattended: + try: + verify_fqdn(host_default,options.no_host_dns) + except RuntimeError, e: + logging.error(str(e) + "\n") + return 1 + + host_name = host_default + else: + host_name = read_host_name(host_default,options.no_host_dns) + + host_name = host_name.lower() + + if not options.domain_name: + domain_name = read_domain_name(host_name[host_name.find(".")+1:], options.unattended) + else: + domain_name = options.domain_name + + domain_name = domain_name.lower() + + # Check we have a public IP that is associated with the hostname + ip = resolve_host(host_name) + if ip is None: + if options.ip_address: + ip = options.ip_address + if ip is None and options.unattended: + print "Unable to resolve IP address for host name" + return 1 + + if not verify_ip_address(ip): + ip = "" + if options.unattended: + return 1 + + if options.ip_address and options.ip_address != ip: + if options.setup_bind: + ip = options.ip_address + else: + print "Error: the hostname resolves to an IP address that is different" + print "from the one provided on the command line. Please fix your DNS" + print "or /etc/hosts file and restart the installation." + return 1 + + if options.unattended: + if not ip: + print "Unable to resolve IP address" + return 1 + + if not ip: + ip = read_ip_address(host_name) + ip_address = ip + + print "The IPA Master Server will be configured with" + print "Hostname: " + host_name + print "IP address: " + ip_address + print "Domain name: " + domain_name + print "" + + if not options.ds_user: + ds_user = read_ds_user() + if ds_user == "": + return 1 + else: + ds_user = options.ds_user + + if not options.realm_name: + realm_name = read_realm_name(domain_name, options.unattended) + else: + realm_name = options.realm_name.upper() + + if not options.dm_password: + dm_password = read_dm_password() + else: + dm_password = options.dm_password + + if not options.master_password: + master_password = ipa_generate_password() + else: + master_password = options.master_password + + if not options.admin_password: + admin_password = read_admin_password() + else: + admin_password = options.admin_password + + if not options.unattended: + print "" + print "The following operations may take some minutes to complete." + print "Please wait until the prompt is returned." + + # Configure ntpd + if options.conf_ntp: + ntp = ipaserver.ntpinstance.NTPInstance(fstore) + ntp.create_instance() + + if options.dirsrv_pin: + [pw_fd, pw_name] = tempfile.mkstemp() + os.write(pw_fd, options.dirsrv_pin) + os.close(pw_fd) + + # Create a directory server instance + ds = ipaserver.dsinstance.DsInstance() + if options.dirsrv_pkcs12: + pkcs12_info = (options.dirsrv_pkcs12, pw_name) + ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, pkcs12_info) + os.remove(pw_name) + else: + ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password) + + # Create a kerberos instance + krb = ipaserver.krbinstance.KrbInstance(fstore) + krb.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, master_password) + + # Create a HTTP instance + + if options.http_pin: + [pw_fd, pw_name] = tempfile.mkstemp() + os.write(pw_fd, options.http_pin) + os.close(pw_fd) + + http = ipaserver.httpinstance.HTTPInstance(fstore) + if options.http_pkcs12: + pkcs12_info = (options.http_pkcs12, pw_name) + http.create_instance(realm_name, host_name, domain_name, autoconfig=False, pkcs12_info=pkcs12_info) + os.remove(pw_name) + else: + http.create_instance(realm_name, host_name, domain_name, autoconfig=True) + + # Create the config file + fstore.backup_file("/etc/ipa/ipa.conf") + fd = open("/etc/ipa/ipa.conf", "w") + fd.write("[defaults]\n") + fd.write("server=" + host_name + "\n") + fd.write("realm=" + realm_name + "\n") + fd.write("domain=" + domain_name + "\n") + fd.close() + + # Create a Web Gui instance + webgui = ipaserver.httpinstance.WebGuiInstance() + webgui.create_instance() + + bind = ipaserver.bindinstance.BindInstance(fstore) + bind.setup(host_name, ip_address, realm_name, domain_name) + if options.setup_bind: + bind.create_instance() + else: + bind.create_sample_bind_zone() + + # Apply any LDAP updates. Needs to be done after the configuration file + # is created + service.print_msg("Applying LDAP updates") + ds.apply_updates() + + # Restart ds and krb after configurations have been changed + service.print_msg("restarting the directory server") + ds.restart() + + service.print_msg("restarting the KDC") + krb.restart() + + # Set the admin user kerberos password + ds.change_admin_password(admin_password) + + # Call client install script + try: + run(["/usr/sbin/ipa-client-install", "--on-master", "--unattended", "--domain", domain_name, "--server", host_name, "--realm", realm_name]) + except Exception, e: + print "Configuration of client side components failed!" + print "ipa-client-install returned: " + str(e) + return 1 + + print "==============================================================================" + print "Setup complete" + print "" + print "Next steps:" + print "\t1. You must make sure these network ports are open:" + print "\t\tTCP Ports:" + print "\t\t * 80, 443: HTTP/HTTPS" + print "\t\t * 389, 636: LDAP/LDAPS" + print "\t\t * 88, 464: kerberos" + if options.setup_bind: + print "\t\t * 53: bind" + print "\t\tUDP Ports:" + print "\t\t * 88, 464: kerberos" + if options.setup_bind: + print "\t\t * 53: bind" + if options.conf_ntp: + print "\t\t * 123: ntp" + print "" + print "\t2. You can now obtain a kerberos ticket using the command: 'kinit admin'" + print "\t This ticket will allow you to use the IPA tools (e.g., ipa-adduser)" + print "\t and the web user interface." + + if not service.is_running("ntpd"): + print "\t3. Kerberos requires time synchronization between clients" + print "\t and servers for correct operation. You should consider enabling ntpd." + + print "" + if not options.dirsrv_pkcs12: + print "Be sure to back up the CA certificate stored in " + ipaserver.dsinstance.config_dirname(ds.serverid) + "cacert.p12" + print "The password for this file is in " + ipaserver.dsinstance.config_dirname(ds.serverid) + "pwdfile.txt" + else: + print "In order for Firefox autoconfiguration to work you will need to" + print "use a SSL signing certificate. See the IPA documentation for more details." + print "You also need to install a PEM copy of the HTTP issuing CA into" + print "/usr/share/ipa/html/ca.crt" + + return 0 + +try: + try: + sys.exit(main()) + except SystemExit, e: + sys.exit(e) + except Exception, e: + message = "Unexpected error - see ipaserver-install.log for details:\n %s" % str(e) + print message + message = str(e) + for str in traceback.format_tb(sys.exc_info()[2]): + message = message + "\n" + str + logging.debug(message) + sys.exit(1) +finally: + if pw_name and ipautil.file_exists(pw_name): + os.remove(pw_name) diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig new file mode 100644 index 00000000..48c4117d --- /dev/null +++ b/install/tools/ipa-upgradeconfig @@ -0,0 +1,130 @@ +#!/usr/bin/python +# +# Upgrade configuration files to a newer template. + +import sys +try: + from ipa import ipautil + import krbV + import re + import os + import shutil + import fileinput +except ImportError: + print >> sys.stderr, """\ +There was a problem importing one of the required Python modules. The +error was: + + %s +""" % sys.exc_value + sys.exit(1) + +def backup_file(filename, ext): + """Make a backup of filename using ext as the extension. Do not overwrite + previous backups.""" + if not os.path.isabs(filename): + raise ValueError("Absolute path required") + + backupfile = filename + ".bak" + (reldir, file) = os.path.split(filename) + + while os.path.exists(backupfile): + backupfile = backupfile + "." + str(ext) + + shutil.copy2(filename, backupfile) + +def update_conf(sub_dict, filename, template_filename): + template = ipautil.template_file(template_filename, sub_dict) + fd = open(filename, "w") + fd.write(template) + fd.close() + +def find_hostname(): + """Find the hostname currently configured in ipa-rewrite.conf""" + filename="/etc/httpd/conf.d/ipa-rewrite.conf" + pattern = "^[\s#]*.*https:\/\/([A-Za-z0-9\.\-]*)\/.*" + p = re.compile(pattern) + for line in fileinput.input(filename): + if p.search(line): + fileinput.close() + return p.search(line).group(1) + fileinput.close() + + return None + +def find_version(filename): + """Find the version of a configuration file""" + if os.path.exists(filename): + pattern = "^[\s#]*VERSION\s+([0-9]+)\s+.*" + p = re.compile(pattern) + for line in fileinput.input(filename): + if p.search(line): + fileinput.close() + return p.search(line).group(1) + fileinput.close() + + # no VERSION found + return 0 + else: + return -1 + +def upgrade(sub_dict, filename, template): + old = int(find_version(filename)) + new = int(find_version(template)) + + if old < 0: + print "%s not found." % filename + sys.exit(1) + + if new < 0: + print "%s not found." % template + + if old < new: + backup_file(filename, new) + update_conf(sub_dict, filename, template) + print "Upgraded %s to version %d" % (filename, new) + +def check_certs(realm_name): + """Check ca.crt is in the right place, and try to fix if not""" + if not os.path.exists("/usr/share/ipa/html/ca.crt"): + ca_file = "/etc/dirsrv/slapd-" + ("-".join(realm_name.split("."))) + "/cacert.asc" + if os.path.exists(ca_file): + shutil.copyfile(ca_file, "/usr/share/ipa/html/ca.crt") + else: + print "Missing Certification Authority file." + print "You should place a copy of the CA certificate in /usr/share/ipa/html/ca.crt" + +def main(): + try: + krbctx = krbV.default_context() + except krbV.Krb5Error, e: + print "Unable to get default kerberos realm: %s" % e[1] + sys.exit(1) + + try: + check_certs(krbctx.default_realm) + except Error, e: + print "Failed to check CA certificate: %s" % e + + try: + fqdn = find_hostname() + except IOError: + # ipa-rewrite.conf doesn't exist, nothing to do + sys.exit(0) + + if fqdn is None: + print "Unable to determine hostname from ipa-rewrite.conf" + sys.exit(1) + + sub_dict = { "REALM" : krbctx.default_realm, "FQDN": fqdn } + + upgrade(sub_dict, "/etc/httpd/conf.d/ipa.conf", ipautil.SHARE_DIR + "ipa.conf") + upgrade(sub_dict, "/etc/httpd/conf.d/ipa-rewrite.conf", ipautil.SHARE_DIR + "ipa-rewrite.conf") + +try: + if __name__ == "__main__": + sys.exit(main()) +except SystemExit, e: + sys.exit(e) +except KeyboardInterrupt, e: + sys.exit(1) diff --git a/install/tools/ipactl b/install/tools/ipactl new file mode 100644 index 00000000..11038394 --- /dev/null +++ b/install/tools/ipactl @@ -0,0 +1,57 @@ +#!/bin/sh +# +# Copyright (C) 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 +# +# +# IPA control to start/stop the various services required for IPA in the +# proper order +# + +function start() { + /sbin/service dirsrv start + /sbin/service ntpd start + /sbin/service krb5kdc start + /sbin/service ipa_kpasswd start + /sbin/service ipa_webgui start + /sbin/service httpd start +} + +function stop() { + /sbin/service ipa_webgui stop + /sbin/service ipa_kpasswd stop + /sbin/service httpd stop + /sbin/service krb5kdc stop + /sbin/service dirsrv stop + /sbin/service ntpd stop +} + +case "$1" in +restart) + stop + start + ;; +start) + start + ;; +stop) + stop + ;; +*) + echo "Usage: ipactl {start|stop|restart}" + exit 1 + ;; +esac diff --git a/install/tools/man/Makefile.am b/install/tools/man/Makefile.am new file mode 100644 index 00000000..244b06b8 --- /dev/null +++ b/install/tools/man/Makefile.am @@ -0,0 +1,27 @@ +# This file will be processed with automake-1.7 to create Makefile.in + +AUTOMAKE_OPTIONS = 1.7 + +NULL= + +man1_MANS = \ + ipa-replica-install.1 \ + ipa-replica-manage.1 \ + ipa-replica-prepare.1 \ + ipa-server-certinstall.1 \ + ipa-server-install.1 \ + ipa-ldap-updater.1 \ + ipa-compat-manage.1 + +man8_MANS = \ + ipactl.8 \ + ipa_kpasswd.8 \ + ipa_webgui.8 + +install-data-hook: + @for i in $(man1_MANS) ; do gzip -f $(DESTDIR)$(man1dir)/$$i ; done + @for i in $(man8_MANS) ; do gzip -f $(DESTDIR)$(man8dir)/$$i ; done + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/install/tools/man/ipa-compat-manage.1 b/install/tools/man/ipa-compat-manage.1 new file mode 100644 index 00000000..767384a4 --- /dev/null +++ b/install/tools/man/ipa-compat-manage.1 @@ -0,0 +1,45 @@ +.\" A man page for ipa-ldap-updater +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Simo Sorce +.\" +.TH "ipa-compat-manage" "1" "Dec 2 2008" "freeipa" "" +.SH "NAME" +ipa\-compat\-manage \- Enables or disables the schema compatibility plugin +.SH "SYNOPSIS" +ipa\-compat\-manage [options] +.SH "DESCRIPTION" +Run the command with the \fBenable\fR option to enable the compat plugin. + +Run the command with the \fBdisable\fR option to disable the compat plugin. + +In both cases the user will be prompted to provide the Directory Manager's password unless option \fB\-y\fR is used. + +Directory Server will need to be restarted after the schema compatibility plugin has been enabled. + +.SH "OPTIONS" +.TP +\fB\-d\fR, \fB\-\-debug\fR +Enable debug logging when more verbose output is needed +.TP +\fB\-y\fR \fIfile\fR +File containing the Directory Manager password +.SH "EXIT STATUS" +0 if the command was successful + +1 if an error occurred + +2 if the plugin is already in the required status (enabled or disabled) diff --git a/install/tools/man/ipa-ldap-updater.1 b/install/tools/man/ipa-ldap-updater.1 new file mode 100644 index 00000000..453ac758 --- /dev/null +++ b/install/tools/man/ipa-ldap-updater.1 @@ -0,0 +1,78 @@ +.\" A man page for ipa-ldap-updater +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden +.\" +.TH "ipa-ldap-updater" "1" "Sep 12 2008" "freeipa" "" +.SH "NAME" +ipa\-ldap\-updater \- Update the IPA LDAP configuration +.SH "SYNOPSIS" +ipa\-ldap\-updater [options] input_file(s) +ipa\-ldap\-updater [options] +.SH "DESCRIPTION" +Run with no file arguments, ipa\-ldap\-updater will process all files with the extension .update in /usr/share/ipa/updates. + +An update file describes an LDAP entry and a set of operations to be performed on that entry. It can be used to add new entries or modify existing entries. It cannot remove entries, just specific values in a given attribute. + +Blank lines and lines beginning with # are ignored. + +There are 4 keywords: + + * default: the starting value + * add: add a value (or values) to an attribute + * remove: remove a value (or values) from an attribute + * only: set an attribute to this + +Values is a comma\-separated field so multi\-values may be added at one time. Double or single quotes may be put around individual values that contain embedded commas. + +The difference between the default and add keywords is if the DN of the entry exists then default is ignored. So for updating something like schema, which will be under cn=schema, you must always use add (because cn=schema is guaranteed to exist). It will not re\-add the same information again and again. + +It alsos provide some things that can be templated such as architecture (for plugin paths), realm and domain name. + +The available template variables are: + + * $REALM \- the kerberos realm (EXAMPLE.COM) + * $FQDN \- the fully\-qualified domain name of the IPA server being updated (ipa.example.com) + * $DOMAIN \- the domain name (example.com) + * $SUFFIX \- the IPA LDAP suffix (dc=example,dc=com) + * $LIBARCH \- set to 64 on x86_64 systems to be used for plugin paths + * $TIME \- an integer representation of current time + +A few rules: + + 1. Only one rule per line + 2. Each line stands alone (e.g. an only followed by an only results in the last only being used) + 3. adding a value that exists is ok. The request is ignored, duplicate values are not added + 4. removing a value that doesn't exist is ok. It is simply ignored. + 5. If a DN doesn't exist it is created from the 'default' entry and all updates are applied + 6. If a DN does exist the default values are skipped + 7. Only the first rule on a line is respected +.SH "OPTIONS" +.TP +\fB\-d\fR, \fB\-\-debug +Enable debug logging when more verbose output is needed +.TP +\fB\-t\fR, \fB\-\-test\fR +Run through the update without changing anything. If changes are available then the command returns 2. If no updates are available it returns 0. +.TP +\fB\-y\fR +File containing the Directory Manager password +.SH "EXIT STATUS" +0 if the command was successful + +1 if an error occurred + +2 if run with in test mode (\-t) and updates are available diff --git a/install/tools/man/ipa-replica-install.1 b/install/tools/man/ipa-replica-install.1 new file mode 100644 index 00000000..674afd12 --- /dev/null +++ b/install/tools/man/ipa-replica-install.1 @@ -0,0 +1,41 @@ +.\" A man page for ipa-replica-install +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden +.\" +.TH "ipa-replica-install" "1" "Mar 14 2008" "freeipa" "" +.SH "NAME" +ipa\-replica\-install \- Create an IPA replica +.SH "SYNOPSIS" +ipa\-replica\-install [\fIOPTION\fR]... replica_file +.SH "DESCRIPTION" +Configures a new IPA server that is a replica of the server that generated it. Once it has been created it is an exact copy of the original IPA server and is an equal master. Changes made to any master are automatically replicated to other masters. + +The replica_file is created using the ipa\-replica\-prepare utility. +.SH "OPTIONS" +.TP +\fB\-d\fR, \fB\-\-debug +Enable debug logging when more verbose output is needed +.TP +\fB\-n\fR, \fB\-\-no\-ntp\fR +Do not configure NTP +.TP +\fB\-p\fR, \fB\-\-password\fR=\fIDM_PASSWORD\fR +Directory Manager (existing master) password +.SH "EXIT STATUS" +0 if the command was successful + +1 if an error occurred diff --git a/install/tools/man/ipa-replica-manage.1 b/install/tools/man/ipa-replica-manage.1 new file mode 100644 index 00000000..810cf1de --- /dev/null +++ b/install/tools/man/ipa-replica-manage.1 @@ -0,0 +1,70 @@ +.\" A man page for ipa-replica-manage +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden +.\" +.TH "ipa-replica-manage" "1" "Mar 14 2008" "freeipa" "" +.SH "NAME" +ipa\-replica\-manage \- Manage an IPA replica +.SH "SYNOPSIS" +ipa\-replica\-manage [\fIOPTION\fR]... [add|del|list|init|synch] [SERVER] +.SH "DESCRIPTION" +Manages the replication agreements of an IPA server. +.TP +add \- Adds a new replication agreement between two existing IPA servers +.TP +del \- Removes a replication agreement +.TP +list \- Lists the hostnames that HOST IPA server has agreements with +.TP +init \- Forces a full initialization of the IPA server on SERVER from HOST +.TP +synch \- Immediately flush any data to be replicated to SERVER +.SH "OPTIONS" +.TP +\fB\-H HOST\fR, \fB\-\-host\fR=\fIHOST\fR +The IPA server to manage +.TP +\fB\-p DM_PASSWORD\fR, \fB\-\-password\fR=\fIDM_PASSWORD\fR +The Directory Manager password to use for authentication +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Provide additional information +.TP +\fB\-\-winsync\fR +Specifies to create/use a Windows Sync Agreement +.TP +\fB\-\-port\fR=\fISERVER_PORT\fR +Port number of other server (default is 636, the LDAPS port) +.TP +\fB\-\-binddn\fR=\fIADMIN_DN\fR +Bind DN to use with remote server (default is cn=Directory Manager) - Be careful to quote this value on the command line +.TP +\fB--bindpw\fR=\fIADMIN_PWD\fR +Password for Bind DN to use with remote server (default is the DM_PASSWORD above) +.TP +\fB\-\-cacert\fR=\fI/path/to/cacertfile\fR +Full path and filename of CA certificate to use with TLS/SSL to the remote server - this CA certificate will be installed in the directory server's certificate database +.TP +\fB\-\-win-subtree\fR=\fIcn=Users,dc=example,dc=com\fR +DN of Windows subtree containing the users you want to sync (default cn=Users, - this is typically what Windows AD uses as the default value) - Be careful to quote this value on the command line +.TP +\fB\-\-passsync\fR=\fIPASSSYNC_PWD\fR +Password for the Windows PassSync user. +.SH "EXIT STATUS" +0 if the command was successful + +1 if an error occurred diff --git a/install/tools/man/ipa-replica-prepare.1 b/install/tools/man/ipa-replica-prepare.1 new file mode 100644 index 00000000..8eb49444 --- /dev/null +++ b/install/tools/man/ipa-replica-prepare.1 @@ -0,0 +1,48 @@ +.\" A man page for ipa-replica-prepare +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden +.\" +.TH "ipa-replica-prepare" "1" "Mar 14 2008" "freeipa" "" +.SH "NAME" +ipa\-replica\-prepare \- Create an IPA replica file +.SH "SYNOPSIS" +ipa\-replica\-prepare [\fIOPTION\fR]... hostname +.SH "DESCRIPTION" +Generates a replica file that may be used with ipa\-replica\-install to create a replica of an IPA server. + +A replica can only be created on an IPA server installed with ipa\-server\-install (the first server). + +You must provide the fully\-qualified hostname of the machine you want to install the replica on and a host\-specific replica_file will be created. It is host\-specific because SSL server certificates are generated as part of the process and they are specific to a particular hostname. + +Once the file has been created it will be named replica\-hostname. This file can then be moved across the network to the target machine and a new IPA replica setup by running ipa\-replica\-install replica\-hostname. +.SH "OPTIONS" +.TP +\fB\-\-dirsrv_pkcs12\fR=\fIFILE\fR +PKCS#12 file containing the Directory Server SSL Certificate +.TP +\fB\-\-http_pkcs12\fR=\fIFILE\fR +PKCS#12 file containing the Apache Server SSL Certificate +.TP +\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR +The password of the Directory Server PKCS#12 file +.TP +\fB\-\-http_pin\fR=\fIHTTP_PIN\fR +The password of the Apache Server PKCS#12 file +.SH "EXIT STATUS" +0 if the command was successful + +1 if an error occurred diff --git a/install/tools/man/ipa-server-certinstall.1 b/install/tools/man/ipa-server-certinstall.1 new file mode 100644 index 00000000..946ab9f8 --- /dev/null +++ b/install/tools/man/ipa-server-certinstall.1 @@ -0,0 +1,48 @@ +.\" A man page for ipa-server-certinstall +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden +.\" +.TH "ipa-server-certinstall" "1" "Mar 14 2008" "freeipa" "" +.SH "NAME" +ipa\-server\-certinstall \- Install new SSL server certificates +.SH "SYNOPSIS" +ipa\-server\-certinstall [\fIOPTION\fR]... PKCS12_FILE +.SH "DESCRIPTION" +Replace the current SSL Directory and/or Apache server certificate(s) with the certificate in the PKCS#12 file. + +PKCS#12 is a file format used to safely transport SSL certificates and public/private keypairs. + +They may be generated and managed using the NSS pk12util command or the OpenSSL pkcs12 command. + +The service(s) are not automatically restarted. In order to use the newly installed certificate(s) you will need to manually restart the Directory and/or Apache servers. +.SH "OPTIONS" +.TP +\fB\-d\fR, \fB\-\-dirsrv\fR +Install the certificate on the Directory Server +.TP +\fB\-w\fR, \fB\-\-http\fR +Install the certificate in the Apache Web Server +.TP +\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR +The password of the Directory Server PKCS#12 file +.TP +\fB\-\-http_pin\fR=\fIHTTP_PIN\fR +The password of the Apache Server PKCS#12 file +.SH "EXIT STATUS" +0 if the installation was successful + +1 if an error occurred diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1 new file mode 100644 index 00000000..8854f4e5 --- /dev/null +++ b/install/tools/man/ipa-server-install.1 @@ -0,0 +1,81 @@ +.\" A man page for ipa-server-install +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden +.\" +.TH "ipa-server-install" "1" "Mar 14 2008" "freeipa" "" +.SH "NAME" +ipa\-server\-install \- Configure an IPA server +.SH "SYNOPSIS" +ipa\-server\-install [\fIOPTION\fR]... +.SH "DESCRIPTION" +Configures the services needed by an IPA server. This includes setting up a Kerberos Key Distribution Center (KDC) with an LDAP back\-end, configuring Apache, configuring NTP and starting some IPA\-provided services: ipa_kpasswd and ipa_webgui. +.SH "OPTIONS" +.TP +\fB\-u\fR, \fB\-\-user\fR=\fIDS_USER\fR +The user that the Directory Server will run as +.TP +\fB\-r\fR, \fB\-\-realm\fR=\fIREALM_NAME\fR +The Kerberos realm name for the IPA server +.TP +\fB\-n\fR, \fB\-\-domain\fR=\fIDOMAIN_NAME\fR +Your DNS domain name +.TP +\fB\-p\fR, \fB\-\-ds\-password\fR=\fIDM_PASSWORD\fR +The password to be used by the Directory Server for the Directory Manager user +.TP +\fB\-P\fR, \fB\-\-master\-password\fR=\fIMASTER_PASSWORD\fR +The kerberos master password (normally autogenerated) +.TP +\fB\-a\fR, \fB\-\-admin\-password\fR=\fIADMIN_PASSWORD\fR +The password for the IPA admin user +.TP +\fB\-d\fR, \fB\-\-debug\fR +Enable debug logging when more verbose output is needed +.TP +\fB\-\-hostname\fR=\fIHOST_NAME\fR +The fully\-qualified DNS name of this server +.TP +\fB\-\-ip\-address\fR=\fIIP_ADDRESS\fR +The IP address of this server +.TP +\fB\-U\fR, \fB\-\-unattended\fR +An unattended installation that will never prompt for user input +.TP +\fB\-\-setup\-bind\fR +Generate a DNS zone file that contains auto\-discovery records for this IPA server +.TP +\fB\-n\fR, \fB\-\-no\-ntp\fR +Do not configure NTP +\fB\-U\fR, \fB\-\-uninstall\fR +Uninstall an existing IPA installation +.TP +\fB\-\-dirsrv_pkcs12\fR=\fIFILE\fR +PKCS#12 file containing the Directory Server SSL Certificate +.TP +\fB\-\-http_pkcs12\fR=\fIFILE\fR +PKCS#12 file containing the Apache Server SSL Certificate +.TP +\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR +The password of the Directory Server PKCS#12 file +.TP +\fB\-\-http_pin\fR=\fIHTTP_PIN\fR +The password of the Apache Server PKCS#12 file +.PP +.SH "EXIT STATUS" +0 if the installation was successful + +1 if an error occurred diff --git a/install/tools/man/ipa_kpasswd.8 b/install/tools/man/ipa_kpasswd.8 new file mode 100644 index 00000000..f2ba3dd9 --- /dev/null +++ b/install/tools/man/ipa_kpasswd.8 @@ -0,0 +1,36 @@ +.\" A man page for ipa_kpasswd +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden +.\" +.TH "ipa_kpasswd" "8" "Mar 14 2008" "freeipa" "" +.SH "NAME" +ipa_kpasswd \- Proxy Kerberos password change requests +.SH "SYNOPSIS" +ipa_kpasswd +.SH "DESCRIPTION" +Implementation of the kpasswd protocol (RFC 3244). + +It is used to proxy password change operations to Directory Server. +.SH "ENVIRONMENT VARIABLES" +.TP +KRB5_KTNAME +Location of the keytab to be used by ipa_kpasswd +.TP +IPA_KPASSWD_DEBUG +Enable additional syslog output from ipa_kpasswd. Setting greater than 0 gets basic output. Setting higher than 100 gets more. +.SH "EXIT STATUS" +\-1 if an error occurred diff --git a/install/tools/man/ipa_webgui.8 b/install/tools/man/ipa_webgui.8 new file mode 100644 index 00000000..20545363 --- /dev/null +++ b/install/tools/man/ipa_webgui.8 @@ -0,0 +1,37 @@ +.\" A man page for ipa_webgui +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden +.\" +.TH "ipa_webgui" "8" "Mar 14 2008" "freeipa" "" +.SH "NAME" +ipa_webgui \- Start the IPA Web User Interface +.SH "SYNOPSIS" +ipa_webgui [\fIOPTION\fR]... + +.SH "DESCRIPTION" +Used to start the TurboGears web user interface for IPA +.SH "OPTIONS" +.TP +\fB\-f\fR, \fB\-\-foreground\fR +Remain in the foreground instead of becoming a daemon. +.TP +\fB\-d\fR, \fB\-\-debug\fR +.TP +Increase the amount of logging and print it to stdout instead of logging to /var/log/ipa_error.log + +.SH "EXIT STATUS" +1 if an error occurred diff --git a/install/tools/man/ipactl.8 b/install/tools/man/ipactl.8 new file mode 100644 index 00000000..a4797f96 --- /dev/null +++ b/install/tools/man/ipactl.8 @@ -0,0 +1,37 @@ +.\" A man page for ipactl +.\" Copyright (C) 2008 Red Hat, Inc. +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU Library 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 Library General Public +.\" License along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" Author: Rob Crittenden +.\" +.TH "ipactl" "8" "Mar 14 2008" "freeipa" "" +.SH "NAME" +ipactl \- IPA Server Control Interface +.SH "SYNOPSIS" +ipactl \fIcommand\fR +.SH "DESCRIPTION" +A tool to help an administer control an IPA environment. + +IPA glues several discrete services together to work in concert and the order that these services are started and stopped is important. ipactl ensures that they are started and stopped in the correct order. +.SH "OPTIONS" +.TP +start +Start all of the services that make up IPA +.TP +stop +Stop all of the services that make up IPA +.TP +restart +Stop then start all of the services that make up IPA diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am new file mode 100644 index 00000000..11d20ddd --- /dev/null +++ b/install/updates/Makefile.am @@ -0,0 +1,19 @@ +NULL = + +appdir = $(IPA_DATA_DIR)/updates +app_DATA = \ + RFC4876.update \ + RFC2307bis.update \ + nss_ldap.update \ + winsync_index.update \ + replication.update \ + indices.update \ + $(NULL) + +EXTRA_DIST = \ + $(app_DATA) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/install/updates/RFC2307bis.update b/install/updates/RFC2307bis.update new file mode 100644 index 00000000..1ddebc1a --- /dev/null +++ b/install/updates/RFC2307bis.update @@ -0,0 +1,65 @@ +# +# Schema derived from RFC 2307bis: +# "An Approach for Using LDAP as a Network Information Service" +# +dn: cn=schema +add: attributeTypes: + ( 1.3.6.1.1.1.1.28 NAME 'nisPublickey' + DESC 'nisPublickey' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC2307bis' ) +add:attributeTypes: + ( 1.3.6.1.1.1.1.29 NAME 'nisSecretkey' + DESC 'nisSecretkey' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC2307bis' ) +add:attributeTypes: + ( 1.3.6.1.4.1.1.1.1.12 NAME 'nisDomain' + DESC 'NIS domain' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC2307bis' ) +add:attributeTypes: + ( 2.16.840.1.113730.3.1.30 NAME 'mgrpRFC822MailMember' + DESC 'mgrpRFC822MailMember' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC2307bis' ) +add:attributeTypes: + ( 1.3.6.1.4.1.42.2.27.1.1.12 NAME 'nisNetIdUser' + DESC 'nisNetIdUser' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC2307bis' ) +add:attributeTypes: + ( 1.3.6.1.4.1.42.2.27.1.1.13 NAME 'nisNetIdGroup' + DESC 'nisNetIdGroup' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC2307bis' ) +add:attributeTypes: + ( 1.3.6.1.4.1.42.2.27.1.1.14 NAME 'nisNetIdHost' + DESC 'nisNetIdHost' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC2307bis' ) +add:objectClasses: + ( 1.3.6.1.1.1.2.14 NAME 'nisKeyObject' + DESC 'nisKeyObject' SUP top + MUST ( cn $ nisPublickey $ nisSecretkey ) + MAY ( uidNumber $ description ) ) +add:objectClasses: + ( 1.3.1.6.1.1.1.2.15 NAME 'nisDomainObject' + DESC 'nisDomainObject' SUP top AUXILIARY + MUST ( nisDomain ) ) +add:objectClasses: + ( 2.16.840.1.113730.3.2.4 NAME 'mailGroup' + DESC 'mailGroup' SUP top + MUST ( mail ) + MAY ( cn $ mgrpRFC822MailMember ) ) +add:objectClasses: + ( 1.3.6.1.4.1.42.2.27.1.2.6 NAME 'nisNetId' + DESC 'nisNetId' SUP top + MUST ( cn ) + MAY ( nisNetIdUser $ nisNetIdGroup $ nisNetIdHost ) ) diff --git a/install/updates/RFC4876.update b/install/updates/RFC4876.update new file mode 100644 index 00000000..5a372c20 --- /dev/null +++ b/install/updates/RFC4876.update @@ -0,0 +1,146 @@ +# +# Schema more or less verbatim from RFC 4876: +# "A Configuration Profile Schema for Lightweight Directory Access +# Protocol (LDAP)-Based Agents" +# +dn: cn=schema +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.0 NAME 'defaultServerList' + DESC 'List of default servers' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.1 NAME 'defaultSearchBase' + DESC 'Default base for searches' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.2 NAME 'preferredServerList' + DESC 'List of preferred servers' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.3 NAME 'searchTimeLimit' + DESC 'Maximum time an agent or service allows for a + search to complete' + EQUALITY integerMatch + ORDERING integerOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.4 NAME 'bindTimeLimit' + DESC 'Maximum time an agent or service allows for a + bind operation to complete' + EQUALITY integerMatch + ORDERING integerOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.5 NAME 'followReferrals' + DESC 'An agent or service does or should follow referrals' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.6 NAME 'authenticationMethod' + DESC 'Identifies the types of authentication methods either + used, required, or provided by a service or peer' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.7 NAME 'profileTTL' + DESC 'Time to live, in seconds, before a profile is + considered stale' + EQUALITY integerMatch + ORDERING integerOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.9 NAME 'attributeMap' + DESC 'Attribute mappings used, required, or supported by an + agent or service' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.10 NAME 'credentialLevel' + DESC 'Identifies type of credentials either used, required, + or supported by an agent or service' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.11 NAME 'objectclassMap' + DESC 'Object class mappings used, required, or supported by + an agent or service' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.12 NAME 'defaultSearchScope' + DESC 'Default scope used when performing a search' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.13 NAME 'serviceCredentialLevel' + DESC 'Specifies the type of credentials either used, required, + or supported by a specific service' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.14 NAME 'serviceSearchDescriptor' + DESC 'Specifies search descriptors required, used, or + supported by a particular service or agent' + EQUALITY caseExactMatch + SUBSTR caseExactSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.15 NAME 'serviceAuthenticationMethod' + DESC 'Specifies types authentication methods either + used, required, or supported by a particular service' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC4876' ) +add:attributeTypes: + ( 1.3.6.1.4.1.11.1.3.1.1.16 NAME 'dereferenceAliases' + DESC 'Specifies if a service or agent either requires, + supports, or uses dereferencing of aliases.' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + SINGLE-VALUE + X-ORIGIN 'RFC4876' ) +add:objectClasses: + ( 1.3.6.1.4.1.11.1.3.1.2.5 NAME 'DUAConfigProfile' + SUP top STRUCTURAL + DESC 'Abstraction of a base configuration for a DUA' + MUST ( cn ) + MAY ( defaultServerList $ preferredServerList $ + defaultSearchBase $ defaultSearchScope $ + searchTimeLimit $ bindTimeLimit $ + credentialLevel $ authenticationMethod $ + followReferrals $ dereferenceAliases $ + serviceSearchDescriptor $ serviceCredentialLevel $ + serviceAuthenticationMethod $ objectclassMap $ + attributeMap $ profileTTL ) + X-ORIGIN 'RFC4876' ) diff --git a/install/updates/indices.update b/install/updates/indices.update new file mode 100644 index 00000000..3d0e42af --- /dev/null +++ b/install/updates/indices.update @@ -0,0 +1,18 @@ +# +# Some nss_ldap implementations will always ask for memberuid so we must +# have an index for it. +# +dn: cn=memberuid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +default:cn: memberuid +default:ObjectClass: top +default:ObjectClass: nsIndex +default:nsSystemIndex: false +default:nsIndexType: eq,pres + +dn: cn=memberof,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +default:cn: memberof +default:ObjectClass: top +default:ObjectClass: nsIndex +default:nsSystemIndex: false +default:nsIndexType: eq + diff --git a/install/updates/nss_ldap.update b/install/updates/nss_ldap.update new file mode 100644 index 00000000..e8c1e00f --- /dev/null +++ b/install/updates/nss_ldap.update @@ -0,0 +1,33 @@ +# +# Add profile for RFC 4876 agents (Solaris and HP/ux) +# + +# Update the top-level entry +dn: $SUFFIX +add:objectClass: domain +add:objectClass: domainRelatedObject +add:objectClass: nisDomainObject +add:associatedDomain: $DOMAIN +add:nisDomain: $DOMAIN + +# Add a place to store the nss_ldap default profile +dn: ou=profile,$SUFFIX +add: objectClass: top +add: objectClass: organizationalUnit +add: ou: profiles + +# The DUA profile. On Solaris one can run: +# ldap_client init ipa.example.com +dn: cn=default,ou=profile,$SUFFIX +default:ObjectClass: top +default:ObjectClass: DUAConfigProfile +default:defaultServerList: $FQDN +default:defaultSearchBase: $SUFFIX +default:authenticationMethod: none +default:searchTimeLimit: 15 +default:cn: default +default:serviceSearchDescriptor: passwd:cn=users,cn=accounts,$SUFFIX +default:serviceSearchDescriptor: group:cn=groups,cn=compat,$SUFFIX +default:bindTimeLimit: 5 +default:objectClassMap: shadow:shadowAccount=posixAccount +default:followReferrals:TRUE diff --git a/install/updates/replication.update b/install/updates/replication.update new file mode 100644 index 00000000..29823a6f --- /dev/null +++ b/install/updates/replication.update @@ -0,0 +1,9 @@ +# +# Counter used to store the next replica id +# +# Start at 3 to avoid conflicts with v1.0 replica ids. The value itself +# isn't important but each replica needs a unique id. +dn: cn=replication,cn=etc,$SUFFIX +add: objectclass: nsDS5Replica +add: nsDS5ReplicaId: 3 +add: nsDS5ReplicaRoot: '$SUFFIX' diff --git a/install/updates/winsync_index.update b/install/updates/winsync_index.update new file mode 100644 index 00000000..f24bdf8b --- /dev/null +++ b/install/updates/winsync_index.update @@ -0,0 +1,10 @@ +# +# Make sure winsync attributes have the correct indexing +# + +dn: cn=ntUniqueId,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +only: nsIndexType: eq,pres + +dn: cn=ntUserDomainId,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config +only: nsIndexType: eq,pres + diff --git a/ipa-server/autogen.sh b/ipa-server/autogen.sh deleted file mode 100755 index c95b6dbc..00000000 --- a/ipa-server/autogen.sh +++ /dev/null @@ -1,196 +0,0 @@ -#!/bin/sh -# Run this to generate all the initial makefiles, etc. -set -e - -PACKAGE=freeipa-server - -LIBTOOLIZE=${LIBTOOLIZE-libtoolize} -LIBTOOLIZE_FLAGS="--copy --force" -AUTOHEADER=${AUTOHEADER-autoheader} -AUTOMAKE_FLAGS="--add-missing --gnu" -AUTOCONF=${AUTOCONF-autoconf} - -# automake 1.8 requires autoconf 2.58 -# automake 1.7 requires autoconf 2.54 -automake_min_vers=1.7 -aclocal_min_vers=$automake_min_vers -autoconf_min_vers=2.54 -libtoolize_min_vers=1.4 - -# The awk-based string->number conversion we use needs a C locale to work -# as expected. Setting LC_ALL overrides whether the user set LC_ALL, -# LC_NUMERIC, or LANG. -LC_ALL=C - -ARGV0=$0 - -# Allow invocation from a separate build directory; in that case, we change -# to the source directory to run the auto*, then change back before running configure -srcdir=`dirname $ARGV0` -test -z "$srcdir" && srcdir=. - -ORIGDIR=`pwd` - -cd $srcdir - -# Usage: -# compare_versions MIN_VERSION ACTUAL_VERSION -# returns true if ACTUAL_VERSION >= MIN_VERSION -compare_versions() { - ch_min_version=$1 - ch_actual_version=$2 - ch_status=0 - IFS="${IFS= }"; ch_save_IFS="$IFS"; IFS="." - set $ch_actual_version - for ch_min in $ch_min_version; do - ch_cur=`echo $1 | sed 's/[^0-9].*$//'`; shift # remove letter suffixes - if [ -z "$ch_min" ]; then break; fi - if [ -z "$ch_cur" ]; then ch_status=1; break; fi - if [ $ch_cur -gt $ch_min ]; then break; fi - if [ $ch_cur -lt $ch_min ]; then ch_status=1; break; fi - done - IFS="$ch_save_IFS" - return $ch_status -} - -if ($AUTOCONF --version) < /dev/null > /dev/null 2>&1 ; then - if ($AUTOCONF --version | head -n 1 | awk 'NR==1 { if( $(NF) >= '$autoconf_min_vers') \ - exit 1; exit 0; }'); - then - echo "$ARGV0: ERROR: \`$AUTOCONF' is too old." - $AUTOCONF --version - echo " (version $autoconf_min_vers or newer is required)" - DIE="yes" - fi -else - echo $AUTOCONF: command not found - echo - echo "$ARGV0: ERROR: You must have \`autoconf' installed to compile $PACKAGE." - echo " (version $autoconf_min_vers or newer is required)" - DIE="yes" -fi - -# -# Hunt for an appropriate version of automake and aclocal; we can't -# assume that 'automake' is necessarily the most recent installed version -# -# We check automake first to allow it to be a newer version than we know about. -# -if test x"$AUTOMAKE" = x || test x"$ACLOCAL" = x ; then - am_ver="" - for ver in "" "-1.9" "-1.8" "-1.7" ; do - am="automake$ver" - if ($am --version) < /dev/null > /dev/null 2>&1 ; then - if ($am --version | head -n 1 | awk 'NR==1 { if( $(NF) >= '$automake_min_vers') \ - exit 1; exit 0; }'); then : ; else - am_ver=$ver - break; - fi - fi - done - - AUTOMAKE=${AUTOMAKE-automake$am_ver} - ACLOCAL=${ACLOCAL-aclocal$am_ver} -fi - -# -# Now repeat the tests with the copies we decided upon and error out if they -# aren't sufficiently new. -# -if ($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 ; then - automake_actual_version=`$AUTOMAKE --version | head -n 1 | \ - sed 's/^.*[ ]\([0-9.]*[a-z]*\).*$/\1/'` - if ! compare_versions $automake_min_vers $automake_actual_version; then - echo "$ARGV0: ERROR: \`$AUTOMAKE' is too old." - $AUTOMAKE --version - echo " (version $automake_min_vers or newer is required)" - DIE="yes" - fi - if ($ACLOCAL --version) < /dev/null > /dev/null 2>&1; then - aclocal_actual_version=`$ACLOCAL --version | head -n 1 | \ - sed 's/^.*[ ]\([0-9.]*[a-z]*\).*$/\1/'` - - if ! compare_versions $aclocal_min_vers $aclocal_actual_version; then - echo "$ARGV0: ERROR: \`$ACLOCAL' is too old." - $ACLOCAL --version - echo " (version $aclocal_min_vers or newer is required)" - DIE="yes" - fi - else - echo $ACLOCAL: command not found - echo - echo "$ARGV0: ERROR: Missing \`$ACLOCAL'" - echo " The version of $AUTOMAKE installed doesn't appear recent enough." - DIE="yes" - fi -else - echo $AUTOMAKE: command not found - echo - echo "$ARGV0: ERROR: You must have \`automake' installed to compile $PACKAGE." - echo " (version $automake_min_vers or newer is required)" - DIE="yes" -fi - -if ($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 ; then - if ($LIBTOOLIZE --version | awk 'NR==1 { if( $4 >= '$libtoolize_min_vers') \ - exit 1; exit 0; }'); - then - echo "$ARGV0: ERROR: \`$LIBTOOLIZE' is too old." - echo " (version $libtoolize_min_vers or newer is required)" - DIE="yes" - fi -else - echo $LIBTOOLIZE: command not found - echo - echo "$ARGV0: ERROR: You must have \`libtoolize' installed to compile $PACKAGE." - echo " (version $libtoolize_min_vers or newer is required)" - DIE="yes" -fi - -if test -z "$ACLOCAL_FLAGS"; then - acdir=`$ACLOCAL --print-ac-dir` - if [ ! -f $acdir/pkg.m4 ]; then - echo "$ARGV0: Error: Could not find pkg-config macros." - echo " (Looked in $acdir/pkg.m4)" - echo " If pkg.m4 is available in /another/directory, please set" - echo " ACLOCAL_FLAGS=\"-I /another/directory\"" - echo " Otherwise, please install pkg-config." - echo "" - echo "pkg-config is available from:" - echo "http://www.freedesktop.org/software/pkgconfig/" - DIE=yes - fi -fi - -if test "X$DIE" != X; then - exit 1 -fi - - -if test -z "$*"; then - echo "$ARGV0: Note: \`./configure' will be run with no arguments." - echo " If you wish to pass any to it, please specify them on the" - echo " \`$0' command line." - echo -fi - -do_cmd() { - echo "$ARGV0: running \`$@'" - $@ -} - -do_cmd $LIBTOOLIZE $LIBTOOLIZE_FLAGS - -do_cmd $ACLOCAL $ACLOCAL_FLAGS - -do_cmd $AUTOHEADER - -do_cmd $AUTOMAKE $AUTOMAKE_FLAGS - -do_cmd $AUTOCONF - -cd $ORIGDIR || exit 1 - -rm -f config.cache - -do_cmd $srcdir/configure --cache-file=config.cache --disable-static --enable-maintainer-mode --enable-gtk-doc ${1+"$@"} && echo "Now type \`make' to compile" || exit 1 diff --git a/ipa-server/ipa-compat-manage b/ipa-server/ipa-compat-manage deleted file mode 100755 index 648e2c3a..00000000 --- a/ipa-server/ipa-compat-manage +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python -# Authors: Rob Crittenden -# Authors: Simo Sorce -# -# Copyright (C) 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 -# - -import sys -try: - from optparse import OptionParser - from ipaserver import ipaldap - from ipa import entity, ipaerror, ipautil, config - from ipaserver import installutils - from ipaserver.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR - import ldap - import logging - import re - import krbV - import platform - import shlex - import time - import random -except ImportError: - print >> sys.stderr, """\ -There was a problem importing one of the required Python modules. The -error was: - - %s -""" % sys.exc_value - sys.exit(1) - -def parse_options(): - usage = "%prog [options] \n" - usage += "%prog [options]\n" - parser = OptionParser(usage=usage, formatter=config.IPAFormatter()) - - parser.add_option("-d", "--debug", action="store_true", dest="debug", - help="Display debugging information about the update(s)") - parser.add_option("-y", dest="password", - help="File containing the Directory Manager password") - - config.add_standard_options(parser) - options, args = parser.parse_args() - - config.init_config(options) - - return options, args - -def get_dirman_password(): - """Prompt the user for the Directory Manager password and verify its - correctness. - """ - password = installutils.read_password("Directory Manager", confirm=False, validate=False) - - return password - -def main(): - retval = 0 - loglevel = logging.NOTSET - files=['/usr/share/ipa/schema_compat.uldif'] - - options, args = parse_options() - if options.debug: - loglevel = logging.DEBUG - - if len(args) != 1: - print "You must specify one action, either enable or disable" - sys.exit(1) - elif args[0] != "enable" and args[0] != "disable": - print "Unrecognized action [" + args[0] + "]" - sys.exit(1) - - logging.basicConfig(level=loglevel, - format='%(levelname)s %(message)s') - - dirman_password = "" - if options.password: - pw = ipautil.template_file(options.password, []) - dirman_password = pw.strip() - else: - dirman_password = get_dirman_password() - - try: - try: - conn = ipaldap.IPAdmin(installutils.get_fqdn()) - conn.do_simple_bind(bindpw=dirman_password) - except ldap.LDAPError, e: - print "An error occurred while connecting to the server." - print "%s" % e[0]['desc'] - return 1 - - if args[0] == "enable": - try: - conn.getEntry("cn=Schema Compatibility,cn=plugins,cn=config", - ldap.SCOPE_BASE, "(objectclass=*)") - print "Plugin already Enabled" - retval = 2 - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - print "Enabling plugin" - except ldap.LDAPError, e: - print "An error occurred while talking to the server." - print "%s" % e[0]['desc'] - retval = 1 - - if retval == 0: - ld = LDAPUpdate(dm_password=dirman_password, sub_dict={}) - retval = ld.update(files) - if retval == 0: - print "This setting will not take effect until you restart Directory Server." - - elif args[0] == "disable": - # Make a quick hack foir now, directly delete the entries by name, - # In future we should add delete capabilites to LDAPUpdate - try: - conn.getEntry("cn=Schema Compatibility,cn=plugins,cn=config", - ldap.SCOPE_BASE, "(objectclass=*)") - conn.deleteEntry("cn=groups,cn=Schema Compatibility,cn=plugins,cn=config") - conn.deleteEntry("cn=users,cn=Schema Compatibility,cn=plugins,cn=config") - conn.deleteEntry("cn=Schema Compatibility,cn=plugins,cn=config") - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - print "Plugin is already disabled" - retval = 2 - except ldap.LDAPError, e: - print "An error occurred while talking to the server." - print "%s" % e[0]['desc'] - retval = 1 - - else: - retval = 1 - - finally: - if conn: - conn.unbind() - - return retval - -try: - if __name__ == "__main__": - sys.exit(main()) -except BadSyntax, e: - print "There is a syntax error in this update file:" - print " %s" % e - sys.exit(1) -except RuntimeError, e: - print "%s" % e - sys.exit(1) -except SystemExit, e: - sys.exit(e) -except KeyboardInterrupt, e: - sys.exit(1) -except config.IPAConfigError, e: - print "An IPA server to update cannot be found. Has one been configured yet?" - print "The error was: %s" % e - sys.exit(1) -except ipaerror, e: - print "An error occurred while performing operations: %s" % e - sys.exit(1) diff --git a/ipa-server/ipa-fix-CVE-2008-3274 b/ipa-server/ipa-fix-CVE-2008-3274 deleted file mode 100644 index 41d3abc9..00000000 --- a/ipa-server/ipa-fix-CVE-2008-3274 +++ /dev/null @@ -1,524 +0,0 @@ -#!/usr/bin/python -# -# Upgrade configuration files to a newer template. - -etckrb5conf = "/etc/krb5.conf" -krb5dir = "/var/kerberos/krb5kdc" -cachedir = "/var/cache/ipa" -libdir = "/var/lib/ipa" -basedir = libdir+"/mkey" -ourkrb5conf = basedir+"/krb5.conf" -ldappwdfile = basedir+"/ldappwd" - -import sys -try: - from optparse import OptionParser - - import os - import random - import time - import shutil - import getpass - - import ipa - import ipa.config - import ipa.ipautil - - import krbV - import ldap - - from ldap import LDAPError - from ldap import ldapobject - - from ipaclient import ipachangeconf - from ipaserver import ipaldap - - from pyasn1.type import univ, namedtype - import pyasn1.codec.ber.encoder - import pyasn1.codec.ber.decoder - import struct - import base64 - -except ImportError: - print >> sys.stderr, """\ -There was a problem importing one of the required Python modules. The -error was: - - %s -""" % sys.exc_value - sys.exit(1) - -def parse_options(): - parser = OptionParser("%prog [--check] [--fix] [--fix-replica]") - parser.add_option("--check", dest="check", action="store_true", - help="Just check for the vulnerability and report (default action)") - parser.add_option("--fix", dest="fix", action="store_true", - help="Run checks and start procedure to fix the problem") - parser.add_option("--fix-replica", dest="fix_replica", action="store_true", - help="Fix a replica after the tool has been tun with --fix on another master") - - ipa.config.add_standard_options(parser) - options, args = parser.parse_args() - - ipa.config.verify_args(parser, args) - if not options.fix and not options.fix_replica and not options.check: - parser.error("please specify at least one option") - - ipa.config.init_config(options) - - return options, args - -def check_vuln(realm, suffix): - - try: - conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") - conn.simple_bind() - msgid = conn.search("cn="+realm+",cn=kerberos,"+suffix, - ldap.SCOPE_BASE, - "(objectclass=krbRealmContainer)", - ("krbmkey", "cn")) - res = conn.result(msgid) - conn.unbind() - - if len(res) != 2: - err = 'Realm Container not found, unable to proceed' - print err - raise Exception, err - - if 'krbmkey' in res[1][0][1]: - print 'System vulnerable' - return 1 - else: - print 'System *not* vulnerable' - return 0 - except Exception, e: - print "Could not connect to the LDAP server, unable to check server" - print "("+type(e)+")("+dir(e)+")" - raise e - -# We support only des3 encoded stash files for now -def generate_new_stash_file(file): - - odd_parity_bytes_pool = ['\x01', '\x02', '\x04', '\x07', '\x08', '\x0b', - '\r', '\x0e', '\x10', '\x13', '\x15', '\x16', '\x19', '\x1a', '\x1c', - '\x1f', ' ', '#', '%', '&', ')', '*', ',', '/', '1', '2', '4', '7', '8', - ';', '=', '>', '@', 'C', 'E', 'F', 'I', 'J', 'L', 'O', 'Q', 'R', 'T', - 'W', 'X', '[', ']', '^', 'a', 'b', 'd', 'g', 'h', 'k', 'm', 'n', 'p', - 's', 'u', 'v', 'y', 'z', '|', '\x7f', '\x80', '\x83', '\x85', '\x86', - '\x89', '\x8a', '\x8c', '\x8f', '\x91', '\x92', '\x94', '\x97', '\x98', - '\x9b', '\x9d', '\x9e', '\xa1', '\xa2', '\xa4', '\xa7', '\xa8', '\xab', - '\xad', '\xae', '\xb0', '\xb3', '\xb5', '\xb6', '\xb9', '\xba', '\xbc', - '\xbf', '\xc1', '\xc2', '\xc4', '\xc7', '\xc8', '\xcb', '\xcd', '\xce', - '\xd0', '\xd3', '\xd5', '\xd6', '\xd9', '\xda', '\xdc', '\xdf', '\xe0', - '\xe3', '\xe5', '\xe6', '\xe9', '\xea', '\xec', '\xef', '\xf1', '\xf2', - '\xf4', '\xf7', '\xf8', '\xfb', '\xfd', '\xfe'] - - pool_len = len(odd_parity_bytes_pool) - keytype = 16 # des3 - keydata = "" - - r = random.SystemRandom() - for k in range(24): - keydata += r.choice(odd_parity_bytes_pool) - - format = '=hi%ss' % len(keydata) - s = struct.pack(format, keytype, len(keydata), keydata) - try: - fd = open(file, "w") - fd.write(s) - except os.error, e: - logging.critical("failed to write stash file") - raise e - -# clean up procedures -def change_mkey_cleanup(password): - try: - os.stat(basedir) - except: - return None - try: - # always remove ldappwdfile as it contains the Directory Manager password - os.remove(ldappwdfile) - except: - pass - - # tar and encrypt the working dir so that we do not leave sensitive data - # around unproteceted - curtime = time.strftime("%Y%m%d%H%M%S",time.gmtime()) - tarfile = libdir+"/ipa-change-mkey-"+curtime+".tar" - gpgfile = tarfile+".gpg" - args = ['/bin/tar', '-C', libdir, '-cf', tarfile, 'mkey'] - ipa.ipautil.run(args) - ipa.ipautil.encrypt_file(tarfile, gpgfile, password, cachedir) - os.remove(tarfile) - shutil.rmtree(basedir, ignore_errors=True) - - return "The temporary working directory with backup dump files has been securely archived and gpg-encrypted as "+gpgfile+" using the Directory Manager password." - -def change_mkey(password = None, quiet = False): - - krbctx = krbV.default_context() - - realm = krbctx.default_realm - suffix = ipa.ipautil.realm_to_suffix(realm) - - backupfile = basedir+"/backup.dump" - convertfile = basedir+"/convert.dump" - oldstashfile = krb5dir+"/.k5."+realm - newstashfile = basedir+"/.new.mkey" - bkpstashfile = basedir+"/.k5."+realm - - if os.getuid() != 0: - print "ERROR: This command must be run as root" - sys.exit(1) - - print "DANGER: This is a dangerous operation, make sure you backup all your IPA data before running the tool" - print "This command will restart your Directory and KDC Servers." - - #TODO: ask for confirmation - if not ipa.ipautil.user_input("Do you want to proceed and change the Kerberos Master key?", False): - print "" - print "Aborting..." - return 1 - - if not password: - password = getpass.getpass("Directory Manager password: ") - - # get a connection to the DS - try: - conn = ipaldap.IPAdmin(ipa.config.config.default_server[0]) - conn.do_simple_bind(bindpw=password) - except Exception, e: - print "ERROR: Could not connect to the Directory Server on "+ipa.config.config.default_server[0]+" ("+str(e)+")" - return 1 - - # Wipe basedir and recreate it - shutil.rmtree(basedir, ignore_errors=True) - os.mkdir(basedir, 0700) - - generate_new_stash_file(newstashfile) - - # Generate conf files - try: - shutil.copyfile(etckrb5conf, ourkrb5conf) - - krbconf = ipachangeconf.IPAChangeConf("IPA Installer") - krbconf.setOptionAssignment(" = ") - krbconf.setSectionNameDelimiters(("[","]")) - krbconf.setSubSectionDelimiters(("{","}")) - krbconf.setIndent((""," "," ")) - - #OPTS - opts = [{'name':'ldap_kadmind_dn', 'type':'option', 'action':'set', 'value':'cn=Directory Manager'}, - {'name':'ldap_service_password_file', 'type':'option', 'action':'set', 'value':ldappwdfile}] - - #REALM - realmopts = [{'name':realm, 'type':'subsection', 'action':'set', 'value':opts}] - - #DBMODULES - dbopts = [{'name':'dbmodules', 'type':'section', 'action':'set', 'value':realmopts}] - - krbconf.changeConf(ourkrb5conf, dbopts); - - hexpwd = "" - for x in password: - hexpwd += (hex(ord(x))[2:]) - pwd_fd = open(ldappwdfile, "w") - pwd_fd.write("cn=Directory Manager#{HEX}"+hexpwd+"\n") - pwd_fd.close() - os.chmod(ldappwdfile, 0600) - - except Exception, e: - print "Failed to create custom configuration files ("+str(e)+") aborting..." - return 1 - - #Set environment vars so that the modified krb5.conf is used - os.environ['KRB5_CONFIG'] = ourkrb5conf - - #Backup the kerberos key material for recovery if needed - args = ["/usr/kerberos/sbin/kdb5_util", "dump", "-verbose", backupfile] - print "Performing safety backup of the key material" - try: - output = ipa.ipautil.run(args) - except ipa.ipautil.CalledProcessError, e: - print "Failed to backup key material ("+str(e)+"), aborting ..." - return 1 - - if not quiet: - princlist = output[1].split('\n') - print "Principals stored into the backup file "+backupfile+":" - for p in princlist: - print p - print "" - - #Convert the kerberos keys to the new master key - args = ["/usr/kerberos/sbin/kdb5_util", "dump", "-verbose", "-new_mkey_file", newstashfile, convertfile] - print "Converting key material to new master key" - try: - output = ipa.ipautil.run(args) - except ipa.ipautil.CalledProcessError, e: - print "Failed to convert key material, aborting ..." - return 1 - - savedprinclist = output[1].split('\n') - - if not quiet: - princlist = output[1].split('\n') - print "Principals dumped for conversion:" - for p in princlist: - print p - print "" - - #Stop the KDC - args = ["/etc/init.d/krb5kdc", "stop"] - try: - output = ipa.ipautil.run(args) - if output[0]: - print output[0] - if output[1]: - print output[1] - except ipa.ipautil.CalledProcessError, e: - print "WARNING: Failed to restart the KDC ("+str(e)+")" - print "You will have to manually restart the KDC when the operation is completed" - - #Change the mkey into ldap - try: - stash = open(newstashfile, "r") - keytype = struct.unpack('h', stash.read(2))[0] - keylen = struct.unpack('i', stash.read(4))[0] - keydata = stash.read(keylen) - - #encode it in the asn.1 attribute - MasterKey = univ.Sequence() - MasterKey.setComponentByPosition(0, univ.Integer(keytype)) - MasterKey.setComponentByPosition(1, univ.OctetString(keydata)) - krbMKey = univ.Sequence() - krbMKey.setComponentByPosition(0, univ.Integer(0)) #we have no kvno - krbMKey.setComponentByPosition(1, MasterKey) - asn1key = pyasn1.codec.ber.encoder.encode(krbMKey) - - dn = "cn="+realm+",cn=kerberos,"+suffix - mod = [(ldap.MOD_REPLACE, 'krbMKey', str(asn1key))] - conn.modify_s(dn, mod) - except Exception, e: - print "ERROR: Failed to upload the Master Key from the Stash file: "+newstashfile+" ("+str(e)+")" - return 1 - - #Backup old stash file and substitute with new - try: - shutil.move(oldstashfile, bkpstashfile) - shutil.copyfile(newstashfile, oldstashfile) - except Exception, e: - print "ERROR: An error occurred while installing the new stash file("+str(e)+")" - print "The KDC may fail to start if the correct stash file is not in place" - print "Verify that "+newstashfile+" has been correctly installed into "+oldstashfile - print "A backup copy of the old stash file should be saved in "+bkpstashfile - - #Finally upload the converted principals - args = ["/usr/kerberos/sbin/kdb5_util", "load", "-verbose", "-update", convertfile] - print "Uploading converted key material" - try: - output = ipa.ipautil.run(args) - except ipa.ipautil.CalledProcessError, e: - print "Failed to upload key material ("+e+"), aborting ..." - return 1 - - if not quiet: - princlist = output[1].split('\n') - print "Principals converted and uploaded:" - for p in princlist: - print p - print "" - - uploadedprinclist = output[1].split('\n') - - #Check for differences and report - d = [] - for p in savedprinclist: - if uploadedprinclist.count(p) == 0: - d.append(p) - if len(d) != 0: - print "WARNING: Not all dumped principals have been updated" - print "Principals not Updated:" - for p in d: - print p - - #Remove custom environ - del os.environ['KRB5_CONFIG'] - - #Restart Directory Server (the pwd plugin need to read the new mkey) - args = ["/etc/init.d/dirsrv", "restart"] - try: - output = ipa.ipautil.run(args) - if output[0]: - print output[0] - if output[1]: - print output[1] - except ipa.ipautil.CalledProcessError, e: - print "WARNING: Failed to restart the Directory Server ("+str(e)+")" - print "Please manually restart the DS with 'service dirsrv restart'" - - #Restart the KDC - args = ["/etc/init.d/krb5kdc", "start"] - try: - output = ipa.ipautil.run(args) - if output[0]: - print output[0] - if output[1]: - print output[1] - except ipa.ipautil.CalledProcessError, e: - print "WARNING: Failed to restart the KDC ("+str(e)+")" - print "Please manually restart the kdc with 'service krb5kdc start'" - - print "Master Password successfully changed" - #print "You MUST now copy the stash file "+oldstashfile+" to all the replicas and restart them!" - print "" - - return 0 - -def fix_replica(password, realm, suffix): - - try: - conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") - conn.simple_bind("cn=Directory Manager", password) - msgid = conn.search("cn="+realm+",cn=kerberos,"+suffix, - ldap.SCOPE_BASE, - "(objectclass=krbRealmContainer)", - ("krbmkey", "cn")) - res = conn.result(msgid) - conn.unbind() - krbmkey = res[1][0][1]['krbmkey'][0] - except Exception, e: - print "Could not connect to the LDAP server, unable to fix server" - print "("+type(e)+")("+dir(e)+")" - raise e - - krbMKey = pyasn1.codec.ber.decoder.decode(krbmkey) - keytype = int(krbMKey[0][1][0]) - keydata = str(krbMKey[0][1][1]) - - format = '=hi%ss' % len(keydata) - s = struct.pack(format, keytype, len(keydata), keydata) - try: - fd = open("/var/kerberos/krb5kdc/.k5."+realm, "w") - fd.write(s) - fd.close() - except os.error, e: - print "failed to write stash file" - raise e - - #restart KDC so that it can reload the new Master Key - os.system("/etc/init.d/krb5kdc restart") - -KRBMKEY_DENY_ACI = """ -(targetattr = "krbMKey")(version 3.0; acl "No external access"; deny (all) userdn != "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) -""" - -def fix_main(password, realm, suffix): - - #Run the change master key tool - print "Changing Kerberos master key" - try: - ret = change_mkey(password, True) - except SystemExit: - ret = 1 - pass - except Exception, e: - ret = 1 - print "%s" % str(e) - - try: - msg = change_mkey_cleanup(password) - if msg: - print msg - except Exception, e: - print "Failed to clean up the temporary location for the dump files and generate and encrypted archive with error:" - print e - print "Please securely archive/encrypt "+basedir - - if ret is not 0: - sys.exit(ret) - - #Finally upload new master key - - #get the Master Key from the stash file - try: - stash = open("/var/kerberos/krb5kdc/.k5."+realm, "r") - keytype = struct.unpack('h', stash.read(2))[0] - keylen = struct.unpack('i', stash.read(4))[0] - keydata = stash.read(keylen) - except os.error: - print "Failed to retrieve Master Key from Stash file: %s" - raise e - #encode it in the asn.1 attribute - MasterKey = univ.Sequence() - MasterKey.setComponentByPosition(0, univ.Integer(keytype)) - MasterKey.setComponentByPosition(1, univ.OctetString(keydata)) - krbMKey = univ.Sequence() - krbMKey.setComponentByPosition(0, univ.Integer(0)) #we have no kvno - krbMKey.setComponentByPosition(1, MasterKey) - asn1key = pyasn1.codec.ber.encoder.encode(krbMKey) - - dn = "cn=%s,cn=kerberos,%s" % (realm, suffix) - sub_dict = dict(REALM=realm, SUFFIX=suffix) - #protect the master key by adding an appropriate deny rule along with the key - mod = [(ldap.MOD_ADD, 'aci', ipa.ipautil.template_str(KRBMKEY_DENY_ACI, sub_dict)), - (ldap.MOD_REPLACE, 'krbMKey', str(asn1key))] - - conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") - conn.simple_bind("cn=Directory Manager", password) - conn.modify_s(dn, mod) - conn.unbind() - - print "\n" - print "This server is now correctly configured and the master-key has been changed and secured." - print "Please now run this tool with the --fix-replica option on all your other replicas." - print "Until you fix the replicas their KDCs will not work." - -def main(): - - options, args = parse_options() - - if options.fix or options.fix_replica: - password = getpass.getpass("Directory Manager password: ") - - krbctx = krbV.default_context() - realm = krbctx.default_realm - suffix = ipa.ipautil.realm_to_suffix(realm) - - try: - ret = check_vuln(realm, suffix) - except: - sys.exit(1) - - if options.fix_replica: - if ret is 1: - print "Your system is still vulnerable" - print "If you have already run this tool with --fix on a master then make sure your replication is working correctly, before runnig --fix-replica" - sys.exit(1) - try: - fix_replica(password, realm, suffix) - except Exception, e: - print "Unexpected error ("+str(e)+")" - sys.exit(1) - sys.exit(0) - - if options.check: - sys.exit(0) - - if options.fix: - if ret is 1: - try: - ret = fix_main(password, realm, suffix) - except Exception, e: - print "Unexpected error ("+str(e)+")" - sys.exit(1) - sys.exit(ret) - -try: - if __name__ == "__main__": - sys.exit(main()) -except SystemExit, e: - sys.exit(e) -except KeyboardInterrupt, e: - sys.exit(1) diff --git a/ipa-server/ipa-install/Makefile.am b/ipa-server/ipa-install/Makefile.am deleted file mode 100644 index 3f566175..00000000 --- a/ipa-server/ipa-install/Makefile.am +++ /dev/null @@ -1,24 +0,0 @@ -NULL = - -SUBDIRS = \ - share \ - updates \ - $(NULL) - -sbin_SCRIPTS = \ - ipa-server-install \ - ipa-replica-install \ - ipa-replica-prepare \ - ipa-replica-manage \ - ipa-server-certinstall \ - ipactl \ - $(NULL) - -EXTRA_DIST = \ - README \ - $(sbin_SCRIPTS) \ - $(NULL) - -MAINTAINERCLEANFILES = \ - *~ \ - Makefile.in diff --git a/ipa-server/ipa-install/README b/ipa-server/ipa-install/README deleted file mode 100644 index a52cede0..00000000 --- a/ipa-server/ipa-install/README +++ /dev/null @@ -1,67 +0,0 @@ - -Required packages: - -krb5-server -fedora-ds-base -fedora-ds-base-devel -openldap-clients -openldap-devel -krb5-server-ldap -cyrus-sasl-gssapi -httpd -mod_auth_kerb -ntp -openssl-devel -nspr-devel -nss-devel -mozldap-devel -mod_python -gcc -python-ldap -TurboGears -python-kerberos -python-krbV -python-tgexpandingformwidget -python-pyasn1 - -Installation example: - -TEMPORARY: until bug https://bugzilla.redhat.com/show_bug.cgi?id=248169 is - fixed. - -Please apply the fedora-ds.init.patch in freeipa/ipa-server/ipa-install/share/ -to patch your init scripts before running ipa-server-install. This tells -FDS where to find its kerberos keytab. - -Things done as root are denoted by #. Things done as a unix user are denoted -by %. - -# cd freeipa -# patch -p0 < ipa-server/ipa-install/share/fedora-ds.init.patch - -Now to do the installation. - -# cd freeipa -# make install - -To start an interactive installation use: -# /usr/sbin/ipa-server-install - -For more verbose output add the -d flag run the command with -h to see all options - -You have a basic working system with one super administrator (named admin). - -To create another administrative user: - -% kinit admin@FREEIPA.ORG -% /usr/sbin/ipa-adduser -f Test -l User test -% ldappasswd -Y GSSAPI -h localhost -s password uid=test,cn=users,cn=accounts,dc=freeipa,dc=org -% /usr/sbin/ipa-groupmod -a test admins - -An admin user is just a regular user in the group admin. - -Now you can destroy the old ticket and log in as test: - -% kdestroy -% kinit test@FREEIPA.ORG -% /usr/sbin/ipa-finduser test diff --git a/ipa-server/ipa-install/ipa-replica-install b/ipa-server/ipa-install/ipa-replica-install deleted file mode 100644 index c2704be0..00000000 --- a/ipa-server/ipa-install/ipa-replica-install +++ /dev/null @@ -1,312 +0,0 @@ -#! /usr/bin/python -E -# Authors: Karl MacMillan -# -# Copyright (C) 2007 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 -# - -import sys - -import tempfile, os, pwd, traceback, logging, shutil -from ConfigParser import SafeConfigParser -import ldap - -from ipa import ipautil - -from ipaserver import dsinstance, replication, installutils, krbinstance, service -from ipaserver import httpinstance, ntpinstance, certs, ipaldap -from ipa import version - -CACERT="/usr/share/ipa/html/ca.crt" - -class ReplicaConfig: - def __init__(self): - self.realm_name = "" - self.domain_name = "" - self.master_host_name = "" - self.dirman_password = "" - self.ds_user = "" - self.host_name = "" - self.repl_password = "" - self.dir = "" - -def parse_options(): - from optparse import OptionParser - parser = OptionParser(version=version.VERSION) - parser.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false", - help="do not configure ntp", default=True) - parser.add_option("-d", "--debug", dest="debug", action="store_true", - default=False, help="gather extra debugging information") - parser.add_option("-p", "--password", dest="password", - help="Directory Manager (existing master) password") - - options, args = parser.parse_args() - - if len(args) != 1: - parser.error("you must provide a file generated by ipa-replica-prepare") - - return options, args[0] - -def get_dirman_password(): - return installutils.read_password("Directory Manager (existing master)", confirm=False, validate=False) - -def expand_info(filename, password): - top_dir = tempfile.mkdtemp("ipa") - tarfile = top_dir+"/files.tar" - dir = top_dir + "/realm_info" - ipautil.decrypt_file(filename, tarfile, password, top_dir) - ipautil.run(["tar", "xf", tarfile, "-C", top_dir]) - os.remove(tarfile) - - return top_dir, dir - -def read_info(dir, rconfig): - filename = dir + "/realm_info" - fd = open(filename) - config = SafeConfigParser() - config.readfp(fd) - - rconfig.realm_name = config.get("realm", "realm_name") - rconfig.master_host_name = config.get("realm", "master_host_name") - rconfig.ds_user = config.get("realm", "ds_user") - rconfig.domain_name = config.get("realm", "domain_name") - rconfig.host_name = config.get("realm", "destination_host") - -def get_host_name(): - hostname = installutils.get_fqdn() - try: - installutils.verify_fqdn(hostname) - except RuntimeError, e: - logging.error(str(e)) - sys.exit(1) - - return hostname - -def set_owner(config, dir): - pw = pwd.getpwnam(config.ds_user) - os.chown(dir, pw.pw_uid, pw.pw_gid) - -def install_ds(config): - dsinstance.check_existing_installation() - dsinstance.check_ports() - - # if we have a pkcs12 file, create the cert db from - # that. Otherwise the ds setup will create the CA - # cert - pkcs12_info = None - if ipautil.file_exists(config.dir + "/dscert.p12"): - pkcs12_info = (config.dir + "/dscert.p12", - config.dir + "/dirsrv_pin.txt") - - ds = dsinstance.DsInstance() - ds.create_instance(config.ds_user, config.realm_name, config.host_name, config.domain_name, config.dirman_password, pkcs12_info) - - return ds - -def install_krb(config): - krb = krbinstance.KrbInstance() - ldappwd_filename = config.dir + "/ldappwd" - kpasswd_filename = config.dir + "/kpasswd.keytab" - krb.create_replica(config.ds_user, config.realm_name, config.host_name, - config.domain_name, config.dirman_password, - ldappwd_filename, kpasswd_filename) - -def install_ca_cert(config): - if ipautil.file_exists(config.dir + "/ca.crt"): - try: - shutil.copy(config.dir + "/ca.crt", CACERT) - os.chmod(CACERT, 0444) - except Exception, e: - print "error copying files: " + str(e) - sys.exit(1) - -def install_http(config): - # if we have a pkcs12 file, create the cert db from - # that. Otherwise the ds setup will create the CA - # cert - pkcs12_info = None - if ipautil.file_exists(config.dir + "/httpcert.p12"): - pkcs12_info = (config.dir + "/httpcert.p12", - config.dir + "/http_pin.txt") - - http = httpinstance.HTTPInstance() - http.create_instance(config.realm_name, config.host_name, config.domain_name, False, pkcs12_info) - - # Now copy the autoconfiguration files - if ipautil.file_exists(config.dir + "/preferences.html"): - try: - shutil.copy(config.dir + "/preferences.html", "/usr/share/ipa/html/preferences.html") - shutil.copy(config.dir + "/configure.jar", "/usr/share/ipa/html/configure.jar") - except Exception, e: - print "error copying files: " + str(e) - sys.exit(1) - -def check_dirsrv(): - serverids = dsinstance.check_existing_installation() - if serverids: - print "" - print "An existing Directory Server has been detected." - if not ipautil.user_input("Do you wish to remove it and create a new one?", False): - print "" - print "Only a single Directory Server instance is allowed on an IPA" - print "server, the one used by IPA itself." - sys.exit(1) - - try: - service.stop("dirsrv") - except: - pass - - for serverid in serverids: - dsinstance.erase_ds_instance_data(serverid) - - (ds_unsecure, ds_secure) = dsinstance.check_ports() - if not ds_unsecure or not ds_secure: - print "IPA requires ports 389 and 636 for the Directory Server." - print "These are currently in use:" - if not ds_unsecure: - print "\t389" - if not ds_secure: - print "\t636" - sys.exit(1) - -def main(): - options, filename = parse_options() - installutils.standard_logging_setup("/var/log/ipareplica-install.log", options.debug) - - if not ipautil.file_exists(filename): - sys.exit("Replica file %s does not exist" % filename) - - check_dirsrv() - - # get the directory manager password - dirman_password = options.password - if not dirman_password: - try: - dirman_password = get_dirman_password() - except KeyboardInterrupt: - sys.exit(0) - - try: - top_dir, dir = expand_info(filename, dirman_password) - except Exception, e: - print "ERROR: Failed to decrypt or open the replica file." - print "Verify you entered the correct Directory Manager password." - sys.exit(1) - - config = ReplicaConfig() - read_info(dir, config) - config.dirman_password = dirman_password - host = get_host_name() - if config.host_name != host: - try: - print "This replica was created for '%s' but this machine is named '%s'" % (config.host_name, host) - if not ipautil.user_input("This may cause problems. Continue?", True): - sys.exit(0) - config.host_name = host - print "" - except KeyboardInterrupt: - sys.exit(0) - config.repl_password = ipautil.ipa_generate_password() - config.dir = dir - - # Try out the password - try: - conn = ipaldap.IPAdmin(config.master_host_name) - conn.do_simple_bind(bindpw=config.dirman_password) - conn.unbind() - except ldap.CONNECT_ERROR, e: - sys.exit("\nUnable to connect to LDAP server %s" % config.master_host_name) - except ldap.SERVER_DOWN, e: - sys.exit("\nUnable to connect to LDAP server %s" % config.master_host_name) - except ldap.INVALID_CREDENTIALS, e : - sys.exit("\nThe password provided is incorrect for LDAP server %s" % config.master_host_name) - - # Configure ntpd - if options.conf_ntp: - ntp = ntpinstance.NTPInstance() - ntp.create_instance() - - # Configure dirsrv - ds = install_ds(config) - - # Install CA cert so that we can do SSL connections with ldap - install_ca_cert(config) - - try: - repl = replication.ReplicationManager(config.host_name, config.dirman_password) - ret = repl.setup_replication(config.master_host_name, config.realm_name) - except Exception, e: - logging.debug("Connection error: %s" % e) - raise RuntimeError("Unable to connect to LDAP server %s." % config.host_name) - if ret != 0: - raise RuntimeError("Failed to start replication") - - install_krb(config) - install_http(config) - - # Create the config file - fd = open("/etc/ipa/ipa.conf", "w") - fd.write("[defaults]\n") - fd.write("server=" + config.host_name + "\n") - fd.write("realm=" + config.realm_name + "\n") - fd.write("domain=" + config.domain_name + "\n") - fd.close() - - # Create a Web Gui instance - webgui = httpinstance.WebGuiInstance() - webgui.create_instance() - - # Apply any LDAP updates. Needs to be done after the replica is synced-up - service.print_msg("Applying LDAP updates") - ds.apply_updates() - - service.restart("dirsrv") - service.restart("krb5kdc") - - # Call client install script - try: - ipautil.run(["/usr/sbin/ipa-client-install", "--on-master", "--unattended", "--domain", config.domain_name, "--server", config.host_name, "--realm", config.realm_name]) - except Exception, e: - print "Configuration of client side components failed!" - print "ipa-client-install returned: " + str(e) - raise RuntimeError("Failed to configure the client") - - ds.init_memberof() - -try: - if not os.geteuid()==0: - sys.exit("\nYou must be root to run this script.\n") - - main() - sys.exit(0) -except SystemExit, e: - sys.exit(e) -except Exception, e: - print "creation of replica failed: %s" % str(e) - message = str(e) - for str in traceback.format_tb(sys.exc_info()[2]): - message = message + "\n" + str - logging.debug(message) -except KeyboardInterrupt: - print "Installation cancelled." - -print "" -print "Your system may be partly configured." -print "Run /usr/sbin/ipa-server-install --uninstall to clean up." - -# the only way to get here is on error or ^C -sys.exit(1) diff --git a/ipa-server/ipa-install/ipa-replica-manage b/ipa-server/ipa-install/ipa-replica-manage deleted file mode 100755 index db8c32d5..00000000 --- a/ipa-server/ipa-install/ipa-replica-manage +++ /dev/null @@ -1,218 +0,0 @@ -#! /usr/bin/python -E -# Authors: Karl MacMillan -# -# Copyright (C) 2007 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 -# -import sys - -import getpass, ldap, re, krbV -import traceback, logging - -from ipa import ipautil -from ipaserver import replication, ipaldap, dsinstance, installutils -from ipa import version - -def parse_options(): - from optparse import OptionParser - - parser = OptionParser(version=version.VERSION) - parser.add_option("-H", "--host", dest="host", help="starting host") - parser.add_option("-p", "--password", dest="dirman_passwd", help="Directory Manager password") - parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, - help="provide additional information") - parser.add_option("--port", type="int", dest="port", - help="port number of other server") - parser.add_option("--binddn", dest="binddn", - help="Bind DN to use with remote server") - parser.add_option("--bindpw", dest="bindpw", - help="Password for Bind DN to use with remote server") - parser.add_option("--winsync", dest="winsync", action="store_true", default=False, - help="This is a Windows Sync Agreement") - parser.add_option("--cacert", dest="cacert", - help="Full path and filename of CA certificate to use with TLS/SSL to the remote server") - parser.add_option("--win-subtree", dest="win_subtree", - help="DN of Windows subtree containing the users you want to sync (default cn=Users, logging.INFO: - mylogger.setLevel(logging.INFO) - # else user has already configured logging externally lower - return options, args - -def get_realm_name(): - c = krbV.default_context() - return c.default_realm - -def get_suffix(): - suffix = ipaldap.IPAdmin.normalizeDN(dsinstance.realm_to_suffix(get_realm_name())) - return suffix - -def get_host_name(): - hostname = installutils.get_fqdn() - try: - installutils.verify_fqdn(hostname) - except RuntimeError, e: - logging.error(str(e)) - sys.exit(1) - - return hostname - -def list_masters(replman, verbose): - dns = replman.find_replication_dns(replman.conn) - - for dn in dns: - entry = replman.conn.search_s(dn, ldap.SCOPE_SUBTREE)[0] - print entry.getValue('nsds5replicahost') - - if verbose: - print " last init status: %s" % entry.nsds5replicalastinitstatus - print " last init ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastinitend)) - print " last update status: %s" % entry.nsds5replicalastupdatestatus - print " last update ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastupdateend)) - -def del_master(replman, hostname): - try: - t = replman.get_agreement_type(hostname) - except ldap.NO_SUCH_OBJECT: - print "No replication agreement found for %s" % hostname - - if t == replication.IPA_REPLICA: - dirman_passwd = getpass.getpass("Directory Manager password (%s): " % hostname) - other_replman = replication.ReplicationManager(hostname, dirman_passwd) - other_replman.suffix = get_suffix() - other_replman.delete_agreement(replman.conn.host) - - replman.delete_agreement(hostname) - -def add_master(replman, hostname, options): - other_args = {} - if options.port: - other_args['port'] = options.port - if options.binddn: - other_args['binddn'] = options.binddn - if options.bindpw: - other_args['bindpw'] = options.bindpw - if options.cacert: - other_args['cacert'] = options.cacert - if options.win_subtree: - other_args['win_subtree'] = options.win_subtree - if options.passsync: - other_args['passsync'] = options.passsync - if options.winsync: - other_args['winsync'] = True - if not options.binddn or not options.bindpw or not options.cacert or not options.passsync: - logging.error("The arguments --binddn, --bindpw, --passsync and --cacert are required to create a winsync agreement") - sys.exit(1) - if options.cacert: - # have to install the given CA cert before doing anything else - ds = dsinstance.DsInstance(realm_name = get_realm_name(), - dm_password = replman.dirman_passwd) - if not ds.add_ca_cert(options.cacert): - logging.error("Could not load the required CA certificate file [%s]" % - options.cacert) - sys.exit(1) - else: - logging.info("Added CA certificate %s to certificate database for %s" % - (options.cacert, replman.hostname)) - # have to reconnect replman connection since the directory server was restarted - replman = replication.ReplicationManager(replman.hostname, replman.dirman_passwd) - logging.info("Restarted directory server " + replman.hostname) - replman.setup_replication(hostname, get_realm_name(), **other_args) - logging.info("Added agreement for other host " + hostname) - -def init_master(replman, dirman_passwd, hostname): - filter = "(&(nsDS5ReplicaHost=%s)(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement)))" % hostname - entry = replman.conn.search_s("cn=config", ldap.SCOPE_SUBTREE, filter) - if len(entry) == 0: - logging.error("Unable to find replication agreement for %s" % hostname) - sys.exit(1) - if len(entry) > 1: - logging.error("Found multiple agreements for %s. Only initializing the first one returned: %s" % (hostname, entry[0].dn)) - replman.initialize_replication(entry[0].dn, replman.conn) - ds = dsinstance.DsInstance(realm_name = get_realm_name(), dm_password = dirman_passwd) - ds.init_memberof() - -def synch_master(replman, hostname): - filter = "(&(nsDS5ReplicaHost=%s)(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement)))" % hostname - entry = replman.conn.search_s("cn=config", ldap.SCOPE_SUBTREE, filter) - if len(entry) == 0: - logging.error("Unable to find replication agreement for %s" % hostname) - sys.exit(1) - if len(entry) > 1: - logging.error("Found multiple agreements for %s. Only initializing the first one returned: %s" % (hostname, entry[0].dn)) - replman.force_synch(entry[0].dn, entry[0].nsds5replicaupdateschedule, replman.conn) - -def main(): - options, args = parse_options() - - if options.dirman_passwd: - dirman_passwd = options.dirman_passwd - else: - dirman_passwd = getpass.getpass("Directory Manager password: ") - - if options.host: - host = options.host - else: - host = get_host_name() - - r = replication.ReplicationManager(host, dirman_passwd) - r.suffix = get_suffix() - - if args[0] == "list": - list_masters(r, options.verbose) - elif args[0] == "del": - if len(args) != 2: - print "must provide hostname of master to delete" - sys.exit(1) - del_master(r, args[1]) - elif args[0] == "add": - if len(args) != 2: - print "must provide hostname of master to add" - sys.exit(1) - add_master(r, args[1], options) - elif args[0] == "init": - if len(args) != 2: - print "hostname of master to initialize is required." - sys.exit(1) - init_master(r, dirman_passwd, args[1]) - elif args[0] == "synch": - if len(args) != 2: - print "must provide hostname of supplier to synchronize with" - sys.exit(1) - synch_master(r, args[1]) - -try: - main() -except KeyboardInterrupt: - sys.exit(1) -except SystemExit, e: - sys.exit(e) -except ldap.INVALID_CREDENTIALS: - print "Invalid password" - sys.exit(1) -except Exception, e: - print "unexpected error: %s" % str(e) diff --git a/ipa-server/ipa-install/ipa-replica-prepare b/ipa-server/ipa-install/ipa-replica-prepare deleted file mode 100644 index eb962b4c..00000000 --- a/ipa-server/ipa-install/ipa-replica-prepare +++ /dev/null @@ -1,294 +0,0 @@ -#! /usr/bin/python -E -# Authors: Karl MacMillan -# -# Copyright (C) 2007 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 -# - -import sys - -import logging, tempfile, shutil, os, pwd -import traceback -from ConfigParser import SafeConfigParser -import krbV -from optparse import OptionParser - -import ipa.config -from ipa import ipautil -from ipaserver import dsinstance, installutils, certs, ipaldap -from ipa import version -import ldap - -def parse_options(): - usage = "%prog [options] FQDN (e.g. replica.example.com)" - parser = OptionParser(usage=usage, version=version.VERSION) - - parser.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12", - help="install certificate for the directory server") - parser.add_option("--http_pkcs12", dest="http_pkcs12", - help="install certificate for the http server") - parser.add_option("--dirsrv_pin", dest="dirsrv_pin", - help="PIN for the Directory Server PKCS#12 file") - parser.add_option("--http_pin", dest="http_pin", - help="PIN for the Apache Server PKCS#12 file") - parser.add_option("-p", "--password", dest="password", - help="Directory Manager (existing master) password") - - ipa.config.add_standard_options(parser) - options, args = parser.parse_args() - - # If any of the PKCS#12 options are selected, all are required. Create a - # list of the options and count it to enforce that all are required without - # having a huge set of it blocks. - pkcs12 = [options.dirsrv_pkcs12, options.http_pkcs12, options.dirsrv_pin, options.http_pin] - cnt = pkcs12.count(None) - if cnt > 0 and cnt < 4: - parser.error("error: All PKCS#12 options are required if any are used.") - - if len(args) != 1: - parser.error("must provide the fully-qualified name of the replica") - - ipa.config.init_config(options) - - return options, args - -def get_host_name(): - hostname = installutils.get_fqdn() - try: - installutils.verify_fqdn(hostname) - except RuntimeError, e: - logging.error(str(e)) - sys.exit(1) - - return hostname - -def get_realm_name(): - try: - c = krbV.default_context() - return c.default_realm - except Exception, e: - return None - -def get_domain_name(): - try: - ipa.config.init_config() - domain_name = ipa.config.config.get_domain() - except Exception, e: - return None - - return domain_name - -def check_ipa_configuration(realm_name): - config_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name)) - if not ipautil.dir_exists(config_dir): - logging.error("could not find directory instance: %s" % config_dir) - sys.exit(1) - -def export_certdb(realm_name, ds_dir, dir, passwd_fname, fname, subject): - """realm is the kerberos realm for the IPA server. - ds_dir is the location of the master DS we are creating a replica for. - dir is the location of the files for the replica we are creating. - passwd_fname is the file containing the PKCS#12 password - fname is the filename of the PKCS#12 file for this cert (minus the .p12). - subject is the subject of the certificate we are creating - """ - try: - ds_ca = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name))) - ca = certs.CertDB(dir) - ca.create_from_cacert(ds_ca.cacert_fname) - ca.create_server_cert("Server-Cert", subject, ds_ca) - except Exception, e: - raise e - - pkcs12_fname = dir + "/" + fname + ".p12" - - try: - ca.export_pkcs12(pkcs12_fname, passwd_fname, "Server-Cert") - except ipautil.CalledProcessError, e: - print "error exporting CA certificate: " + str(e) - try: - os.unlink(pkcs12_fname) - os.unlink(passwd_fname) - except: - pass - - os.unlink(dir + "/cert8.db") - os.unlink(dir + "/key3.db") - os.unlink(dir + "/secmod.db") - os.unlink(dir + "/noise.txt") - if ipautil.file_exists(passwd_fname + ".orig"): - os.unlink(passwd_fname + ".orig") - -def get_ds_user(ds_dir): - uid = os.stat(ds_dir).st_uid - user = pwd.getpwuid(uid)[0] - - return user - -def save_config(dir, realm_name, host_name, ds_user, domain_name, dest_host): - config = SafeConfigParser() - config.add_section("realm") - config.set("realm", "realm_name", realm_name) - config.set("realm", "master_host_name", host_name) - config.set("realm", "ds_user", ds_user) - config.set("realm", "domain_name", domain_name) - config.set("realm", "destination_host", dest_host) - fd = open(dir + "/realm_info", "w") - config.write(fd) - -def copy_files(realm_name, dir): - config_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name)) - - try: - shutil.copy("/var/kerberos/krb5kdc/ldappwd", dir + "/ldappwd") - shutil.copy("/var/kerberos/krb5kdc/kpasswd.keytab", dir + "/kpasswd.keytab") - shutil.copy("/usr/share/ipa/html/ca.crt", dir + "/ca.crt") - if ipautil.file_exists("/usr/share/ipa/html/preferences.html"): - shutil.copy("/usr/share/ipa/html/preferences.html", dir + "/preferences.html") - shutil.copy("/usr/share/ipa/html/configure.jar", dir + "/configure.jar") - except Exception, e: - print "error copying files: " + str(e) - sys.exit(1) - -def get_dirman_password(): - return installutils.read_password("Directory Manager (existing master)", confirm=False, validate=False) - -def main(): - options, args = parse_options() - - replica_fqdn = args[0] - - if not ipautil.file_exists(certs.CA_SERIALNO) and not options.dirsrv_pin: - sys.exit("The replica must be created on the primary IPA server.\nIf you installed IPA with your own certificates using PKCS#12 files you must provide PKCS#12 files for any replicas you create as well.") - - print "Determining current realm name" - realm_name = get_realm_name() - if realm_name is None: - print "Unable to determine default realm" - sys.exit(1) - - check_ipa_configuration(realm_name) - - print "Getting domain name from LDAP" - domain_name = get_domain_name() - if domain_name is None: - print "Unable to determine LDAP default domain" - sys.exit(1) - - host_name = get_host_name() - if host_name == replica_fqdn: - print "You can't create a replica on itself" - sys.exit(1) - ds_dir = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm_name)) - ds_user = get_ds_user(ds_dir) - - # get the directory manager password - dirman_password = options.password - if not options.password: - try: - dirman_password = get_dirman_password() - except KeyboardInterrupt: - sys.exit(0) - - # Try out the password - try: - conn = ipaldap.IPAdmin(host_name) - conn.do_simple_bind(bindpw=dirman_password) - conn.unbind() - except ldap.CONNECT_ERROR, e: - sys.exit("\nUnable to connect to LDAP server %s" % host_name) - except ldap.SERVER_DOWN, e: - sys.exit("\nUnable to connect to LDAP server %s" % host_name) - except ldap.INVALID_CREDENTIALS, e : - sys.exit("\nThe password provided is incorrect for LDAP server %s" % host_name) - - print "Preparing replica for %s from %s" % (replica_fqdn, host_name) - - top_dir = tempfile.mkdtemp("ipa") - dir = top_dir + "/realm_info" - os.mkdir(dir, 0700) - - if options.dirsrv_pin: - passwd = options.dirsrv_pin - else: - passwd = "" - - passwd_fname = dir + "/dirsrv_pin.txt" - fd = open(passwd_fname, "w") - fd.write("%s\n" % passwd) - fd.close() - - if options.dirsrv_pkcs12: - print "Copying SSL certificate for the Directory Server from %s" % options.dirsrv_pkcs12 - try: - shutil.copy(options.dirsrv_pkcs12, dir + "/dscert.p12") - except IOError, e: - print "Copy failed %s" % e - sys.exit(1) - else: - print "Creating SSL certificate for the Directory Server" - export_certdb(realm_name, ds_dir, dir, passwd_fname, "dscert", "cn=%s,ou=Fedora Directory Server" % replica_fqdn) - - if options.http_pin: - passwd = options.http_pin - else: - passwd = "" - - passwd_fname = dir + "/http_pin.txt" - fd = open(passwd_fname, "w") - fd.write("%s\n" % passwd) - fd.close() - - if options.http_pkcs12: - print "Copying SSL certificate for the Web Server from %s" % options.http_pkcs12 - try: - shutil.copy(options.http_pkcs12, dir + "/httpcert.p12") - except IOError, e: - print "Copy failed %s" % e - sys.exit(1) - else: - print "Creating SSL certificate for the Web Server" - export_certdb(realm_name, ds_dir, dir, passwd_fname, "httpcert", "cn=%s,ou=Apache Web Server" % replica_fqdn) - print "Copying additional files" - copy_files(realm_name, dir) - print "Finalizing configuration" - save_config(dir, realm_name, host_name, ds_user, domain_name, replica_fqdn) - - replicafile = "/var/lib/ipa/replica-info-" + replica_fqdn - encfile = replicafile+".gpg" - - print "Packaging replica information into %s" % encfile - ipautil.run(["/bin/tar", "cf", replicafile, "-C", top_dir, "realm_info"]) - ipautil.encrypt_file(replicafile, encfile, dirman_password, top_dir); - - os.remove(replicafile) - shutil.rmtree(dir) - -try: - if not os.geteuid()==0: - sys.exit("\nYou must be root to run this script.\n") - - main() -except SystemExit, e: - sys.exit(e) -except Exception, e: - print "preparation of replica failed: %s" % str(e) - message = str(e) - for str in traceback.format_tb(sys.exc_info()[2]): - message = message + "\n" + str - logging.debug(message) - print message - sys.exit(1) diff --git a/ipa-server/ipa-install/ipa-server-certinstall b/ipa-server/ipa-install/ipa-server-certinstall deleted file mode 100644 index a0d11856..00000000 --- a/ipa-server/ipa-install/ipa-server-certinstall +++ /dev/null @@ -1,157 +0,0 @@ -#! /usr/bin/python -E -# Authors: Karl MacMillan -# -# Copyright (C) 2007 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 -# - -import sys -import os -import pwd -import tempfile - -import traceback - -import krbV, ldap, getpass - -from ipa.ipautil import user_input -from ipaserver import certs, dsinstance, httpinstance, ipaldap, installutils - -def get_realm_name(): - c = krbV.default_context() - return c.default_realm - -def parse_options(): - from optparse import OptionParser - parser = OptionParser() - - parser.add_option("-d", "--dirsrv", dest="dirsrv", action="store_true", - default=False, help="install certificate for the directory server") - parser.add_option("-w", "--http", dest="http", action="store_true", - default=False, help="install certificate for the http server") - parser.add_option("--dirsrv_pin", dest="dirsrv_pin", - help="The password of the Directory Server PKCS#12 file") - parser.add_option("--http_pin", dest="http_pin", - help="The password of the Apache Server PKCS#12 file") - - options, args = parser.parse_args() - - if not options.dirsrv and not options.http: - parser.error("you must specify dirsrv and/or http") - if ((options.dirsrv and not options.dirsrv_pin) or - (options.http and not options.http_pin)): - parser.error("you must provide the password for the PKCS#12 file") - - if len(args) != 1: - parser.error("you must provide a pkcs12 filename") - - return options, args[0] - -def set_ds_cert_name(cert_name, dm_password): - conn = ipaldap.IPAdmin("127.0.0.1") - conn.simple_bind_s("cn=directory manager", dm_password) - - mod = [(ldap.MOD_REPLACE, "nsSSLPersonalitySSL", cert_name)] - - conn.modify_s("cn=RSA,cn=encryption,cn=config", mod) - - conn.unbind() - -def choose_server_cert(server_certs): - print "Please select the certificate to use:" - num = 1 - for cert in server_certs: - print "%d. %s" % (num, cert[0]) - num += 1 - - while 1: - num = user_input("Certificate number", 1) - print "" - if num < 1 or num > len(server_certs): - print "number out of range" - else: - break - - return server_certs[num - 1] - -def import_cert(dirname, pkcs12_fname, pkcs12_passwd, db_password): - cdb = certs.CertDB(dirname) - cdb.create_passwd_file(db_password) - cdb.create_certdbs() - [pw_fd, pw_name] = tempfile.mkstemp() - os.write(pw_fd, pkcs12_passwd) - os.close(pw_fd) - - try: - try: - cdb.import_pkcs12(pkcs12_fname, pw_name) - except RuntimeError, e: - print str(e) - sys.exit(1) - finally: - os.remove(pw_name) - - server_certs = cdb.find_server_certs() - if len(server_certs) == 0: - print "could not find a suitable server cert in import" - sys.exit(1) - elif len(server_certs) == 1: - server_cert = server_certs[0] - else: - server_cert = choose_server_cert(server_certs) - - cdb.trust_root_cert(server_cert[0]) - - return server_cert - -def main(): - options, pkcs12_fname = parse_options() - - try: - if options.dirsrv: - dm_password = getpass.getpass("Directory Manager password: ") - realm = get_realm_name() - dirname = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm)) - fd = open(dirname + "/pwdfile.txt") - passwd = fd.read() - fd.close() - - server_cert = import_cert(dirname, pkcs12_fname, options.dirsrv_pin, passwd) - set_ds_cert_name(server_cert[0], dm_password) - - if options.http: - dirname = httpinstance.NSS_DIR - server_cert = import_cert(dirname, pkcs12_fname, options.http_pin, "") - installutils.set_directive(httpinstance.NSS_CONF, 'NSSNickname', server_cert[0]) - - # Fix the database permissions - os.chmod(dirname + "/cert8.db", 0640) - os.chmod(dirname + "/key3.db", 0640) - os.chmod(dirname + "/secmod.db", 0640) - - pent = pwd.getpwnam("apache") - os.chown(dirname + "/cert8.db", 0, pent.pw_gid ) - os.chown(dirname + "/key3.db", 0, pent.pw_gid ) - os.chown(dirname + "/secmod.db", 0, pent.pw_gid ) - - except Exception, e: - print "an unexpected error occurred: %s" % str(e) - traceback.print_exc() - return 1 - - return 0 - -sys.exit(main()) diff --git a/ipa-server/ipa-install/ipa-server-install b/ipa-server/ipa-install/ipa-server-install deleted file mode 100644 index c9d5c5bf..00000000 --- a/ipa-server/ipa-install/ipa-server-install +++ /dev/null @@ -1,622 +0,0 @@ -#! /usr/bin/python -E -# Authors: Karl MacMillan -# -# Copyright (C) 2007 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 -# - - -# requires the following packages: -# fedora-ds-base -# openldap-clients -# nss-tools - -import sys -import os -import socket -import errno -import logging -import pwd -import subprocess -import signal -import shutil -import glob -import traceback -from optparse import OptionParser - -import ipaserver.dsinstance -import ipaserver.krbinstance -import ipaserver.bindinstance -import ipaserver.httpinstance -import ipaserver.ntpinstance - -from ipaserver import service -from ipa import version -from ipaserver.installutils import * - -from ipa import sysrestore -from ipa.ipautil import * - -pw_name = None - -def parse_options(): - parser = OptionParser(version=version.VERSION) - parser.add_option("-u", "--user", dest="ds_user", - help="ds user") - parser.add_option("-r", "--realm", dest="realm_name", - help="realm name") - parser.add_option("-n", "--domain", dest="domain_name", - help="domain name") - parser.add_option("-p", "--ds-password", dest="dm_password", - help="admin password") - parser.add_option("-P", "--master-password", dest="master_password", - help="kerberos master password (normally autogenerated)") - parser.add_option("-a", "--admin-password", dest="admin_password", - help="admin user kerberos password") - parser.add_option("-d", "--debug", dest="debug", action="store_true", - default=False, help="print debugging information") - parser.add_option("--hostname", dest="host_name", help="fully qualified name of server") - parser.add_option("--ip-address", dest="ip_address", help="Master Server IP Address") - parser.add_option("--setup-bind", dest="setup_bind", action="store_true", - default=False, help="configure bind with our zone file") - parser.add_option("-U", "--unattended", dest="unattended", action="store_true", - default=False, help="unattended installation never prompts the user") - parser.add_option("", "--uninstall", dest="uninstall", action="store_true", - default=False, help="uninstall an existing installation") - parser.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false", - help="do not configure ntp", default=True) - parser.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12", - help="PKCS#12 file containing the Directory Server SSL certificate") - parser.add_option("--http_pkcs12", dest="http_pkcs12", - help="PKCS#12 file containing the Apache Server SSL certificate") - parser.add_option("--dirsrv_pin", dest="dirsrv_pin", - help="The password of the Directory Server PKCS#12 file") - parser.add_option("--http_pin", dest="http_pin", - help="The password of the Apache Server PKCS#12 file") - parser.add_option("--no-host-dns", dest="no_host_dns", action="store_true", - default=False, - help="Do not use DNS for hostname lookup during installation") - - options, args = parser.parse_args() - - if options.uninstall: - if (options.ds_user or options.realm_name or - options.dm_password or options.admin_password or - options.master_password): - parser.error("error: In uninstall mode, -u, r, -p and -P options are not allowed") - elif options.unattended: - if (not options.ds_user or not options.realm_name or - not options.dm_password or not options.admin_password): - parser.error("error: In unattended mode you need to provide at least -u, -r, -p and -a options") - - # If any of the PKCS#12 options are selected, all are required. Create a - # list of the options and count it to enforce that all are required without - # having a huge set of it blocks. - pkcs12 = [options.dirsrv_pkcs12, options.http_pkcs12, options.dirsrv_pin, options.http_pin] - cnt = pkcs12.count(None) - if cnt > 0 and cnt < 4: - parser.error("error: All PKCS#12 options are required if any are used.") - - return options - -def signal_handler(signum, frame): - global ds - print "\nCleaning up..." - if ds: - print "Removing configuration for %s instance" % ds.serverid - ds.stop() - if ds.serverid: - ipaserver.dsinstance.erase_ds_instance_data (ds.serverid) - sys.exit(1) - -def read_host_name(host_default,no_host_dns=False): - host_name = "" - - print "Enter the fully qualified domain name of the computer" - print "on which you're setting up server software. Using the form" - print "." - print "Example: master.example.com." - print "" - print "" - if host_default == "": - host_default = "master.example.com" - while True: - host_name = user_input("Server host name", host_default, allow_empty = False) - print "" - try: - verify_fqdn(host_name,no_host_dns) - except Exception, e: - raise e - else: - break - return host_name - -def resolve_host(host_name): - ip = "" - try: - ip = socket.gethostbyname(host_name) - - if ip == "127.0.0.1" or ip == "::1": - print "The hostname resolves to the localhost address (127.0.0.1/::1)" - print "Please change your /etc/hosts file so that the hostname" - print "resolves to the ip address of your network interface." - print "The KDC service does not listen on localhost" - print "" - print "Please fix your /etc/hosts file and restart the setup program" - return None - - except: - print "Unable to lookup the IP address of the provided host" - return ip - -def verify_ip_address(ip): - is_ok = True - try: - socket.inet_pton(socket.AF_INET, ip) - except: - try: - socket.inet_pton(socket.AF_INET6, ip) - except: - print "Unable to verify IP address" - is_ok = False - return is_ok - -def read_ip_address(host_name): - while True: - ip = user_input("Please provide the IP address to be used for this host name", allow_empty = False) - - if ip == "127.0.0.1" or ip == "::1": - print "The IPA Server can't use localhost as a valid IP" - continue - - if not verify_ip_address(ip): - continue - - print "Adding ["+ip+" "+host_name+"] to your /etc/hosts file" - fstore.backup_file("/etc/hosts") - hosts_fd = open('/etc/hosts', 'r+') - hosts_fd.seek(0, 2) - hosts_fd.write(ip+'\t'+host_name+' '+host_name.split('.')[0]+'\n') - hosts_fd.close() - - return ip - -def read_ds_user(): - print "The server must run as a specific user in a specific group." - print "It is strongly recommended that this user should have no privileges" - print "on the computer (i.e. a non-root user). The setup procedure" - print "will give this user/group some permissions in specific paths/files" - print "to perform server-specific operations." - print "" - - ds_user = "" - try: - pwd.getpwnam('dirsrv') - - print "A user account named 'dirsrv' already exists. This is the user id" - print "that the Directory Server will run as." - print "" - if user_input("Do you want to use the existing 'dirsrv' account?", True): - ds_user = "dirsrv" - else: - print "" - ds_user = user_input_plain("Which account name do you want to use for the DS instance?", allow_empty = False, allow_spaces = False) - print "" - except KeyError: - ds_user = "dirsrv" - - return ds_user - -def read_domain_name(domain_name, unattended): - print "The domain name has been calculated based on the host name." - print "" - if not unattended: - domain_name = user_input("Please confirm the domain name", domain_name) - print "" - return domain_name - -def read_realm_name(domain_name, unattended): - print "The kerberos protocol requires a Realm name to be defined." - print "This is typically the domain name converted to uppercase." - print "" - - if unattended: - return domain_name.upper() - realm_name = user_input("Please provide a realm name", domain_name.upper()) - upper_dom = realm_name.upper() - if upper_dom != realm_name: - print "An upper-case realm name is required." - if not user_input("Do you want to use " + upper_dom + " as realm name?", True): - print "" - print "An upper-case realm name is required. Unable to continue." - sys.exit(1) - else: - realm_name = upper_dom - print "" - return realm_name - - -def read_dm_password(): - print "Certain directory server operations require an administrative user." - print "This user is referred to as the Directory Manager and has full access" - print "to the Directory for system management tasks and will be added to the" - print "instance of directory server created for IPA." - print "The password must be at least 8 characters long." - print "" - #TODO: provide the option of generating a random password - dm_password = read_password("Directory Manager") - return dm_password - -def read_admin_password(): - print "The IPA server requires an administrative user, named 'admin'." - print "This user is a regular system account used for IPA server administration." - print "" - #TODO: provide the option of generating a random password - admin_password = read_password("IPA admin") - return admin_password - -def check_dirsrv(unattended): - serverids = ipaserver.dsinstance.check_existing_installation() - if serverids: - print "" - print "An existing Directory Server has been detected." - if unattended or not user_input("Do you wish to remove it and create a new one?", False): - print "" - print "Only a single Directory Server instance is allowed on an IPA" - print "server, the one used by IPA itself." - sys.exit(1) - - try: - service.stop("dirsrv") - except: - pass - - for serverid in serverids: - ipaserver.dsinstance.erase_ds_instance_data(serverid) - - (ds_unsecure, ds_secure) = ipaserver.dsinstance.check_ports() - if not ds_unsecure or not ds_secure: - print "IPA requires ports 389 and 636 for the Directory Server." - print "These are currently in use:" - if not ds_unsecure: - print "\t389" - if not ds_secure: - print "\t636" - sys.exit(1) - -def uninstall(): - try: - run(["/usr/sbin/ipa-client-install", "--on-master", "--unattended", "--uninstall"]) - except Exception, e: - print "Uninstall of client side components failed!" - print "ipa-client-install returned: " + str(e) - pass - - ipaserver.ntpinstance.NTPInstance(fstore).uninstall() - ipaserver.bindinstance.BindInstance(fstore).uninstall() - ipaserver.httpinstance.WebGuiInstance().uninstall() - ipaserver.httpinstance.HTTPInstance(fstore).uninstall() - ipaserver.krbinstance.KrbInstance(fstore).uninstall() - ipaserver.dsinstance.DsInstance().uninstall() - fstore.restore_all_files() - return 0 - -def main(): - global ds - global pw_name - ds = None - - options = parse_options() - - if os.getegid() != 0: - print "Must be root to setup server" - return 1 - - signal.signal(signal.SIGTERM, signal_handler) - signal.signal(signal.SIGINT, signal_handler) - - if options.uninstall: - standard_logging_setup("/var/log/ipaserver-uninstall.log", options.debug) - else: - standard_logging_setup("/var/log/ipaserver-install.log", options.debug) - print "\nThe log file for this installation can be found in /var/log/ipaserver-install.log" - - global fstore - fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') - - if options.uninstall: - if not options.unattended: - print "\nThis is a NON REVERSIBLE operation and will delete all data and configuration!\n" - if not user_input("Are you sure you want to continue with the uninstall procedure?", False): - print "" - print "Aborting uninstall operation." - sys.exit(1) - - return uninstall() - - print "==============================================================================" - print "This program will setup the FreeIPA Server." - print "" - print "This includes:" - if options.conf_ntp: - print " * Configure the Network Time Daemon (ntpd)" - print " * Create and configure an instance of Directory Server" - print " * Create and configure a Kerberos Key Distribution Center (KDC)" - print " * Configure Apache (httpd)" - print " * Configure TurboGears" - if options.setup_bind: - print " * Configure DNS (bind)" - if not options.conf_ntp: - print "" - print "Excluded by options:" - print " * Configure the Network Time Daemon (ntpd)" - print "" - print "To accept the default shown in brackets, press the Enter key." - print "" - - check_dirsrv(options.unattended) - - ds_user = "" - realm_name = "" - host_name = "" - domain_name = "" - ip_address = "" - master_password = "" - dm_password = "" - admin_password = "" - - # check bind packages are installed - if options.setup_bind: - if not ipaserver.bindinstance.check_inst(): - print "--setup-bind was specified but bind is not installed on the system" - print "Please install bind and restart the setup program" - return 1 - - # check the hostname is correctly configured, it must be as the kldap - # utilities just use the hostname as returned by gethostbyname to set - # up some of the standard entries - - host_default = "" - if options.host_name: - host_default = options.host_name - else: - host_default = get_fqdn() - - if options.unattended: - try: - verify_fqdn(host_default,options.no_host_dns) - except RuntimeError, e: - logging.error(str(e) + "\n") - return 1 - - host_name = host_default - else: - host_name = read_host_name(host_default,options.no_host_dns) - - host_name = host_name.lower() - - if not options.domain_name: - domain_name = read_domain_name(host_name[host_name.find(".")+1:], options.unattended) - else: - domain_name = options.domain_name - - domain_name = domain_name.lower() - - # Check we have a public IP that is associated with the hostname - ip = resolve_host(host_name) - if ip is None: - if options.ip_address: - ip = options.ip_address - if ip is None and options.unattended: - print "Unable to resolve IP address for host name" - return 1 - - if not verify_ip_address(ip): - ip = "" - if options.unattended: - return 1 - - if options.ip_address and options.ip_address != ip: - if options.setup_bind: - ip = options.ip_address - else: - print "Error: the hostname resolves to an IP address that is different" - print "from the one provided on the command line. Please fix your DNS" - print "or /etc/hosts file and restart the installation." - return 1 - - if options.unattended: - if not ip: - print "Unable to resolve IP address" - return 1 - - if not ip: - ip = read_ip_address(host_name) - ip_address = ip - - print "The IPA Master Server will be configured with" - print "Hostname: " + host_name - print "IP address: " + ip_address - print "Domain name: " + domain_name - print "" - - if not options.ds_user: - ds_user = read_ds_user() - if ds_user == "": - return 1 - else: - ds_user = options.ds_user - - if not options.realm_name: - realm_name = read_realm_name(domain_name, options.unattended) - else: - realm_name = options.realm_name.upper() - - if not options.dm_password: - dm_password = read_dm_password() - else: - dm_password = options.dm_password - - if not options.master_password: - master_password = ipa_generate_password() - else: - master_password = options.master_password - - if not options.admin_password: - admin_password = read_admin_password() - else: - admin_password = options.admin_password - - if not options.unattended: - print "" - print "The following operations may take some minutes to complete." - print "Please wait until the prompt is returned." - - # Configure ntpd - if options.conf_ntp: - ntp = ipaserver.ntpinstance.NTPInstance(fstore) - ntp.create_instance() - - if options.dirsrv_pin: - [pw_fd, pw_name] = tempfile.mkstemp() - os.write(pw_fd, options.dirsrv_pin) - os.close(pw_fd) - - # Create a directory server instance - ds = ipaserver.dsinstance.DsInstance() - if options.dirsrv_pkcs12: - pkcs12_info = (options.dirsrv_pkcs12, pw_name) - ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, pkcs12_info) - os.remove(pw_name) - else: - ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password) - - # Create a kerberos instance - krb = ipaserver.krbinstance.KrbInstance(fstore) - krb.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, master_password) - - # Create a HTTP instance - - if options.http_pin: - [pw_fd, pw_name] = tempfile.mkstemp() - os.write(pw_fd, options.http_pin) - os.close(pw_fd) - - http = ipaserver.httpinstance.HTTPInstance(fstore) - if options.http_pkcs12: - pkcs12_info = (options.http_pkcs12, pw_name) - http.create_instance(realm_name, host_name, domain_name, autoconfig=False, pkcs12_info=pkcs12_info) - os.remove(pw_name) - else: - http.create_instance(realm_name, host_name, domain_name, autoconfig=True) - - # Create the config file - fstore.backup_file("/etc/ipa/ipa.conf") - fd = open("/etc/ipa/ipa.conf", "w") - fd.write("[defaults]\n") - fd.write("server=" + host_name + "\n") - fd.write("realm=" + realm_name + "\n") - fd.write("domain=" + domain_name + "\n") - fd.close() - - # Create a Web Gui instance - webgui = ipaserver.httpinstance.WebGuiInstance() - webgui.create_instance() - - bind = ipaserver.bindinstance.BindInstance(fstore) - bind.setup(host_name, ip_address, realm_name, domain_name) - if options.setup_bind: - bind.create_instance() - else: - bind.create_sample_bind_zone() - - # Apply any LDAP updates. Needs to be done after the configuration file - # is created - service.print_msg("Applying LDAP updates") - ds.apply_updates() - - # Restart ds and krb after configurations have been changed - service.print_msg("restarting the directory server") - ds.restart() - - service.print_msg("restarting the KDC") - krb.restart() - - # Set the admin user kerberos password - ds.change_admin_password(admin_password) - - # Call client install script - try: - run(["/usr/sbin/ipa-client-install", "--on-master", "--unattended", "--domain", domain_name, "--server", host_name, "--realm", realm_name]) - except Exception, e: - print "Configuration of client side components failed!" - print "ipa-client-install returned: " + str(e) - return 1 - - print "==============================================================================" - print "Setup complete" - print "" - print "Next steps:" - print "\t1. You must make sure these network ports are open:" - print "\t\tTCP Ports:" - print "\t\t * 80, 443: HTTP/HTTPS" - print "\t\t * 389, 636: LDAP/LDAPS" - print "\t\t * 88, 464: kerberos" - if options.setup_bind: - print "\t\t * 53: bind" - print "\t\tUDP Ports:" - print "\t\t * 88, 464: kerberos" - if options.setup_bind: - print "\t\t * 53: bind" - if options.conf_ntp: - print "\t\t * 123: ntp" - print "" - print "\t2. You can now obtain a kerberos ticket using the command: 'kinit admin'" - print "\t This ticket will allow you to use the IPA tools (e.g., ipa-adduser)" - print "\t and the web user interface." - - if not service.is_running("ntpd"): - print "\t3. Kerberos requires time synchronization between clients" - print "\t and servers for correct operation. You should consider enabling ntpd." - - print "" - if not options.dirsrv_pkcs12: - print "Be sure to back up the CA certificate stored in " + ipaserver.dsinstance.config_dirname(ds.serverid) + "cacert.p12" - print "The password for this file is in " + ipaserver.dsinstance.config_dirname(ds.serverid) + "pwdfile.txt" - else: - print "In order for Firefox autoconfiguration to work you will need to" - print "use a SSL signing certificate. See the IPA documentation for more details." - print "You also need to install a PEM copy of the HTTP issuing CA into" - print "/usr/share/ipa/html/ca.crt" - - return 0 - -try: - try: - sys.exit(main()) - except SystemExit, e: - sys.exit(e) - except Exception, e: - message = "Unexpected error - see ipaserver-install.log for details:\n %s" % str(e) - print message - message = str(e) - for str in traceback.format_tb(sys.exc_info()[2]): - message = message + "\n" + str - logging.debug(message) - sys.exit(1) -finally: - if pw_name and ipautil.file_exists(pw_name): - os.remove(pw_name) diff --git a/ipa-server/ipa-install/ipactl b/ipa-server/ipa-install/ipactl deleted file mode 100644 index 11038394..00000000 --- a/ipa-server/ipa-install/ipactl +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 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 -# -# -# IPA control to start/stop the various services required for IPA in the -# proper order -# - -function start() { - /sbin/service dirsrv start - /sbin/service ntpd start - /sbin/service krb5kdc start - /sbin/service ipa_kpasswd start - /sbin/service ipa_webgui start - /sbin/service httpd start -} - -function stop() { - /sbin/service ipa_webgui stop - /sbin/service ipa_kpasswd stop - /sbin/service httpd stop - /sbin/service krb5kdc stop - /sbin/service dirsrv stop - /sbin/service ntpd stop -} - -case "$1" in -restart) - stop - start - ;; -start) - start - ;; -stop) - stop - ;; -*) - echo "Usage: ipactl {start|stop|restart}" - exit 1 - ;; -esac diff --git a/ipa-server/ipa-install/share/60ipaconfig.ldif b/ipa-server/ipa-install/share/60ipaconfig.ldif deleted file mode 100644 index f4edbcc9..00000000 --- a/ipa-server/ipa-install/share/60ipaconfig.ldif +++ /dev/null @@ -1,42 +0,0 @@ -## schema file for ipa configuration -## -## IPA Base OID: 2.16.840.1.113730.3.8 -## -## Attributes: 2.16.840.1.113730.3.8.1 -## ObjectClasses: 2.16.840.1.113730.3.8.2 -dn: cn=schema -############################################### -## -## Attributes -## -## ipaUserSearchFields - attribute names to search against when looking for users -attributetypes: ( 2.16.840.1.113730.3.8.1.1 NAME 'ipaUserSearchFields' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) -## ipaGroupSearchFields - attribute names to search against when looking for groups -attributetypes: ( 2.16.840.1.113730.3.8.1.2 NAME 'ipaGroupSearchFields' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) -## ipaSearchTimeLimit - search time limit in seconds -attributetypes: ( 2.16.840.1.113730.3.8.1.3 NAME 'ipaSearchTimeLimit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -## ipaSearchRecordsLimit - maximum number of records to return -attributetypes: ( 2.16.840.1.113730.3.8.1.4 NAME 'ipaSearchRecordsLimit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -## ipaCustomFields - custom fields to show in the UI in addition to pre-defined ones -attributetypes: ( 2.16.840.1.113730.3.8.1.5 NAME 'ipaCustomFields' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) -## ipaHomesRootDir - default posix home directory root dir to use when creating new accounts -attributetypes: ( 2.16.840.1.113730.3.8.1.6 NAME 'ipaHomesRootDir' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) -## ipaDefaultLoginShell - default posix login shell to use when creating new accounts -attributetypes: ( 2.16.840.1.113730.3.8.1.7 NAME 'ipaDefaultLoginShell' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) -## ipaDefaultPrimaryGroup - default posix primary group to assign when creating new accounts -attributetypes: ( 2.16.840.1.113730.3.8.1.8 NAME 'ipaDefaultPrimaryGroup' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) -## ipaMaxUsernameLength - maximum username length to allow in the UI -attributetypes: ( 2.16.840.1.113730.3.8.1.9 NAME 'ipaMaxUsernameLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -## ipaPwdExpAdvNotify - time in days to send out paswword expiration notification before passwpord actually expires -attributetypes: ( 2.16.840.1.113730.3.8.1.10 NAME 'ipaPwdExpAdvNotify' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -# ipaUserObjectClasses - required objectclasses for users -attributetypes: ( 2.16.840.1.113730.3.8.1.11 NAME 'ipaUserObjectClasses' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) -# ipaGroupObjectClasses - required objectclasses for groups -attributetypes: ( 2.16.840.1.113730.3.8.1.12 NAME 'ipaGroupObjectClasses' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) -attributetypes: ( 2.16.840.1.113730.3.8.1.13 NAME 'ipaDefaultEmailDomain' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) -############################################### -## -## ObjectClasses -## -## ipaGuiConfig - GUI config parameters objectclass -objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses $ ipaDefaultEmailDomain) ) diff --git a/ipa-server/ipa-install/share/60kerberos.ldif b/ipa-server/ipa-install/share/60kerberos.ldif deleted file mode 100644 index 3431d22e..00000000 --- a/ipa-server/ipa-install/share/60kerberos.ldif +++ /dev/null @@ -1,283 +0,0 @@ -dn: cn=schema -# Novell Kerberos Schema Definitions -# Novell Inc. -# 1800 South Novell Place -# Provo, UT 84606 -# -# VeRsIoN=1.0 -# CoPyRiGhT=(c) Copyright 2006, Novell, Inc. All rights reserved -# -# OIDs: -# joint-iso-ccitt(2) -# country(16) -# us(840) -# organization(1) -# Novell(113719) -# applications(1) -# kerberos(301) -# Kerberos Attribute Type(4) attr# version# -# specific attribute definitions -# Kerberos Attribute Syntax(5) -# specific syntax definitions -# Kerberos Object Class(6) class# version# -# specific class definitions -######################################################################## -######################################################################## -# Attribute Type Definitions # -######################################################################## -##### This is the principal name in the RFC 1964 specified format -attributetypes: ( 2.16.840.1.113719.1.301.4.1.1 NAME 'krbPrincipalName' EQUALITY caseExactIA5Match SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) -##### This specifies the type of the principal, the types could be any of -##### the types mentioned in section 6.2 of RFC 4120 -attributetypes: ( 2.16.840.1.113719.1.301.4.3.1 NAME 'krbPrincipalType' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### This flag is used to find whether directory User Password has to be used -##### as kerberos password. -##### TRUE, if User Password is to be used as the kerberos password. -##### FALSE, if User Password and the kerberos password are different. -attributetypes: ( 2.16.840.1.113719.1.301.4.5.1 NAME 'krbUPEnabled' DESC 'Boolean' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE) -##### The time at which the principal expires -attributetypes: ( 2.16.840.1.113719.1.301.4.6.1 NAME 'krbPrincipalExpiration' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) -##### The krbTicketFlags attribute holds information about the kerberos flags for a principal -##### The values (0x00000001 - 0x00800000) are reserved for standards and -##### values (0x01000000 - 0x80000000) can be used for proprietary extensions. -##### The flags and values as per RFC 4120 and MIT implementation are, -##### DISALLOW_POSTDATED 0x00000001 -##### DISALLOW_FORWARDABLE 0x00000002 -##### DISALLOW_TGT_BASED 0x00000004 -##### DISALLOW_RENEWABLE 0x00000008 -##### DISALLOW_PROXIABLE 0x00000010 -##### DISALLOW_DUP_SKEY 0x00000020 -##### DISALLOW_ALL_TIX 0x00000040 -##### REQUIRES_PRE_AUTH 0x00000080 -##### REQUIRES_HW_AUTH 0x00000100 -##### REQUIRES_PWCHANGE 0x00000200 -##### DISALLOW_SVR 0x00001000 -##### PWCHANGE_SERVICE 0x00002000 -attributetypes: ( 2.16.840.1.113719.1.301.4.8.1 NAME 'krbTicketFlags' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### The maximum ticket lifetime for a principal in seconds -attributetypes: ( 2.16.840.1.113719.1.301.4.9.1 NAME 'krbMaxTicketLife' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### Maximum renewable lifetime for a principal's ticket in seconds -attributetypes: ( 2.16.840.1.113719.1.301.4.10.1 NAME 'krbMaxRenewableAge' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### Forward reference to the Realm object. -##### (FDN of the krbRealmContainer object). -##### Example: cn=ACME.COM, cn=Kerberos, cn=Security -attributetypes: ( 2.16.840.1.113719.1.301.4.14.1 NAME 'krbRealmReferences' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) -##### List of LDAP servers that kerberos servers can contact. -##### The attribute holds data in the ldap uri format, -##### Example: ldaps://acme.com:636 -##### -##### The values of this attribute need to be updated, when -##### the LDAP servers listed here are renamed, moved or deleted. -attributetypes: ( 2.16.840.1.113719.1.301.4.15.1 NAME 'krbLdapServers' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) -##### A set of forward references to the KDC Service objects. -##### (FDNs of the krbKdcService objects). -##### Example: cn=kdc - server 1, ou=uvw, o=xyz -attributetypes: ( 2.16.840.1.113719.1.301.4.17.1 NAME 'krbKdcServers' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) -##### A set of forward references to the Password Service objects. -##### (FDNs of the krbPwdService objects). -##### Example: cn=kpasswdd - server 1, ou=uvw, o=xyz -attributetypes: ( 2.16.840.1.113719.1.301.4.18.1 NAME 'krbPwdServers' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) -##### This attribute holds the Host Name or the ip address, -##### transport protocol and ports of the kerberos service host -##### The format is host_name-or-ip_address#protocol#port -##### Protocol can be 0 or 1. 0 is for UDP. 1 is for TCP. -attributetypes: ( 2.16.840.1.113719.1.301.4.24.1 NAME 'krbHostServer' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) -##### This attribute holds the scope for searching the principals -##### under krbSubTree attribute of krbRealmContainer -##### The value can either be 1 (ONE) or 2 (SUB_TREE). -attributetypes: ( 2.16.840.1.113719.1.301.4.25.1 NAME 'krbSearchScope' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### FDNs pointing to Kerberos principals -attributetypes: ( 2.16.840.1.113719.1.301.4.26.1 NAME 'krbPrincipalReferences' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) -##### This attribute specifies which attribute of the user objects -##### be used as the principal name component for Kerberos. -##### The allowed values are cn, sn, uid, givenname, fullname. -attributetypes: ( 2.16.840.1.113719.1.301.4.28.1 NAME 'krbPrincNamingAttr' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) -##### A set of forward references to the Administration Service objects. -##### (FDNs of the krbAdmService objects). -##### Example: cn=kadmindd - server 1, ou=uvw, o=xyz -attributetypes: ( 2.16.840.1.113719.1.301.4.29.1 NAME 'krbAdmServers' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) -##### Maximum lifetime of a principal's password -attributetypes: ( 2.16.840.1.113719.1.301.4.30.1 NAME 'krbMaxPwdLife' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### Minimum lifetime of a principal's password -attributetypes: ( 2.16.840.1.113719.1.301.4.31.1 NAME 'krbMinPwdLife' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### Minimum number of character clases allowed in a password -attributetypes: ( 2.16.840.1.113719.1.301.4.32.1 NAME 'krbPwdMinDiffChars' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### Minimum length of the password -attributetypes: ( 2.16.840.1.113719.1.301.4.33.1 NAME 'krbPwdMinLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### Number of previous versions of passwords that are stored -attributetypes: ( 2.16.840.1.113719.1.301.4.34.1 NAME 'krbPwdHistoryLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### FDN pointing to a Kerberos Password Policy object -attributetypes: ( 2.16.840.1.113719.1.301.4.36.1 NAME 'krbPwdPolicyReference' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE) -##### The time at which the principal's password expires -attributetypes: ( 2.16.840.1.113719.1.301.4.37.1 NAME 'krbPasswordExpiration' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) -##### This attribute holds the principal's key (krbPrincipalKey) that is encrypted with -##### the master key (krbMKey). -##### The attribute is ASN.1 encoded. -##### -##### The format of the value for this attribute is explained below, -##### 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 -##### } -attributetypes: ( 2.16.840.1.113719.1.301.4.39.1 NAME 'krbPrincipalKey' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40) -##### FDN pointing to a Kerberos Ticket Policy object. -attributetypes: ( 2.16.840.1.113719.1.301.4.40.1 NAME 'krbTicketPolicyReference' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE) -##### Forward reference to an entry that starts sub-trees -##### where principals and other kerberos objects in the realm are configured. -##### Example: ou=acme, ou=pq, o=xyz -attributetypes: ( 2.16.840.1.113719.1.301.4.41.1 NAME 'krbSubTrees' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) -##### Holds the default encryption/salt type combinations of principals for -##### the Realm. Stores in the form of key:salt strings. -##### Example: des-cbc-crc:normal -attributetypes: ( 2.16.840.1.113719.1.301.4.42.1 NAME 'krbDefaultEncSaltTypes' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) -##### Holds the Supported encryption/salt type combinations of principals for -##### the Realm. Stores in the form of key:salt strings. -##### The supported encryption types are mentioned in RFC 3961 -##### The supported salt types are, -##### NORMAL -##### V4 -##### NOREALM -##### ONLYREALM -##### SPECIAL -##### AFS3 -##### Example: des-cbc-crc:normal -##### -##### This attribute obsoletes the krbSupportedEncTypes and krbSupportedSaltTypes -##### attributes. -attributetypes: ( 2.16.840.1.113719.1.301.4.43.1 NAME 'krbSupportedEncSaltTypes' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) -##### This attribute holds the principal's old keys (krbPwdHistory) that is encrypted with -##### the kadmin/history key. -##### The attribute is ASN.1 encoded. -##### -##### The format of the value for this attribute is explained below, -##### KrbKeySet ::= SEQUENCE { -##### attribute-major-vno [0] UInt16, -##### attribute-minor-vno [1] UInt16, -##### kvno [2] UInt32, -##### mkvno [3] UInt32 OPTIONAL -- actually kadmin/history key, -##### 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 -##### } -attributetypes: ( 2.16.840.1.113719.1.301.4.44.1 NAME 'krbPwdHistory' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40) -##### The time at which the principal's password last password change happened. -attributetypes: ( 2.16.840.1.113719.1.301.4.45.1 NAME 'krbLastPwdChange' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) -##### This attribute holds the kerberos master key. -##### This can be used to encrypt principal keys. -##### This attribute has to be secured in directory. -##### -##### This attribute is ASN.1 encoded. -##### The format of the value for this attribute is explained below, -##### KrbMKey ::= SEQUENCE { -##### kvno [0] UInt32, -##### key [1] MasterKey -##### } -##### -##### MasterKey ::= SEQUENCE { -##### keytype [0] Int32, -##### keyvalue [1] OCTET STRING -##### } -attributetypes: ( 2.16.840.1.113719.1.301.4.46.1 NAME 'krbMKey' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40) -##### This stores the alternate principal names for the principal in the RFC 1961 specified format -attributetypes: ( 2.16.840.1.113719.1.301.4.47.1 NAME 'krbPrincipalAliases' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) -##### The time at which the principal's last successful authentication happened. -attributetypes: ( 2.16.840.1.113719.1.301.4.48.1 NAME 'krbLastSuccessfulAuth' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) -##### The time at which the principal's last failed authentication happened. -attributetypes: ( 2.16.840.1.113719.1.301.4.49.1 NAME 'krbLastFailedAuth' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE) -##### This attribute stores the number of failed authentication attempts -##### happened for the principal since the last successful authentication. -attributetypes: ( 2.16.840.1.113719.1.301.4.50.1 NAME 'krbLoginFailedCount' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE) -##### This attribute holds the application specific data. -attributetypes: ( 2.16.840.1.113719.1.301.4.51.1 NAME 'krbExtraData' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40) -##### This attributes holds references to the set of directory objects. -##### This stores the DNs of the directory objects to which the -##### principal object belongs to. -attributetypes: ( 2.16.840.1.113719.1.301.4.52.1 NAME 'krbObjectReferences' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) -##### This attribute holds references to a Container object where -##### the additional principal objects and stand alone principal -##### objects (krbPrincipal) can be created. -attributetypes: ( 2.16.840.1.113719.1.301.4.53.1 NAME 'krbPrincContainerRef' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) -######################################################################## -######################################################################## -# Object Class Definitions # -######################################################################## -#### This is a kerberos container for all the realms in a tree. -objectClasses: ( 2.16.840.1.113719.1.301.6.1.1 NAME 'krbContainer' SUP top MUST ( cn ) ) -##### The krbRealmContainer is created per realm and holds realm specific data. -objectClasses: ( 2.16.840.1.113719.1.301.6.2.1 NAME 'krbRealmContainer' SUP top MUST ( cn ) MAY ( krbMKey $ krbUPEnabled $ krbSubTrees $ krbSearchScope $ krbLdapServers $ krbSupportedEncSaltTypes $ krbDefaultEncSaltTypes $ krbTicketPolicyReference $ krbKdcServers $ krbPwdServers $ krbAdmServers $ krbPrincNamingAttr $krbPwdPolicyReference $ krbPrincContainerRef ) ) -##### An instance of a class derived from krbService is created per -##### kerberos authentication or administration server in an realm and holds -##### references to the realm objects. These references is used to further read -##### realm specific data to service AS/TGS requests. Additionally this object -##### contains some server specific data like pathnames and ports that the -##### server uses. This is the identity the kerberos server logs in with. A key -##### pair for the same is created and the kerberos server logs in with the same. -##### -##### krbKdcService, krbAdmService and krbPwdService derive from this class. -objectClasses: ( 2.16.840.1.113719.1.301.6.3.1 NAME 'krbService' ABSTRACT SUP ( top ) MUST ( cn ) MAY ( krbHostServer $ krbRealmReferences ) ) -##### Representative object for the KDC server to bind into a LDAP directory -##### and have a connection to access Kerberos data with the required -##### access rights. -objectClasses: ( 2.16.840.1.113719.1.301.6.4.1 NAME 'krbKdcService' SUP ( krbService ) ) -##### Representative object for the Kerberos Password server to bind into a LDAP directory -##### and have a connection to access Kerberos data with the required -##### access rights. -objectClasses: ( 2.16.840.1.113719.1.301.6.5.1 NAME 'krbPwdService' SUP ( krbService ) ) -###### The principal data auxiliary class. Holds principal information -###### and is used to store principal information for Person, Service objects. -objectClasses: ( 2.16.840.1.113719.1.301.6.8.1 NAME 'krbPrincipalAux' AUXILIARY MAY ( krbPrincipalName $ krbUPEnabled $ krbPrincipalKey $ krbTicketPolicyReference $ krbPrincipalExpiration $ krbPasswordExpiration $ krbPwdPolicyReference $ krbPrincipalType $ krbPwdHistory $ krbLastPwdChange $ krbPrincipalAliases $ krbLastSuccessfulAuth $ krbLastFailedAuth $ krbLoginFailedCount $ krbExtraData ) ) -###### This class is used to create additional principals and stand alone principals. -objectClasses: ( 2.16.840.1.113719.1.301.6.9.1 NAME 'krbPrincipal' SUP ( top ) MUST ( krbPrincipalName ) MAY ( krbObjectReferences ) ) -###### The principal references auxiliary class. Holds all principals referred -###### from a service -objectClasses: ( 2.16.840.1.113719.1.301.6.11.1 NAME 'krbPrincRefAux' SUP top AUXILIARY MAY krbPrincipalReferences ) -##### Representative object for the Kerberos Administration server to bind into a LDAP directory -##### and have a connection Id to access Kerberos data with the required access rights. -objectClasses: ( 2.16.840.1.113719.1.301.6.13.1 NAME 'krbAdmService' SUP ( krbService ) ) -##### The krbPwdPolicy object is a template password policy that -##### can be applied to principals when they are created. -##### These policy attributes will be in effect, when the Kerberos -##### passwords are different from users' passwords (UP). -objectClasses: ( 2.16.840.1.113719.1.301.6.14.1 NAME 'krbPwdPolicy' SUP top MUST ( cn ) MAY ( krbMaxPwdLife $ krbMinPwdLife $ krbPwdMinDiffChars $ krbPwdMinLength $ krbPwdHistoryLength ) ) -##### The krbTicketPolicyAux holds Kerberos ticket policy attributes. -##### This class can be attached to a principal object or realm object. -objectClasses: ( 2.16.840.1.113719.1.301.6.16.1 NAME 'krbTicketPolicyAux' AUXILIARY MAY ( krbTicketFlags $ krbMaxTicketLife $ krbMaxRenewableAge ) ) -##### The krbTicketPolicy object is an effective ticket policy that is associated with a realm or a principal -objectClasses: ( 2.16.840.1.113719.1.301.6.17.1 NAME 'krbTicketPolicy' SUP top MUST ( cn ) ) diff --git a/ipa-server/ipa-install/share/60radius.ldif b/ipa-server/ipa-install/share/60radius.ldif deleted file mode 100644 index 93a5ba31..00000000 --- a/ipa-server/ipa-install/share/60radius.ldif +++ /dev/null @@ -1,559 +0,0 @@ -# This is a LDAPv3 schema for RADIUS attributes. -# Tested on OpenLDAP 2.0.7 -# Posted by Javier Fernandez-Sanguino Pena -# LDAP v3 version by Jochen Friedrich -# Updates by Adrian Pavlykevych -# Modified by John Dennis for use with Directory Sever/IPA -# -# Note: These OID's do not seem to be registered, the closest I could find -# was 1.3.6.1.4.1.3317 -# {iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) gnome(3317)} -# -############## -dn: cn=schema -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.1 - NAME 'radiusArapFeatures' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.2 - NAME 'radiusArapSecurity' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.3 - NAME 'radiusArapZoneAccess' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.44 - NAME 'radiusAuthType' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.4 - NAME 'radiusCallbackId' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.5 - NAME 'radiusCallbackNumber' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.6 - NAME 'radiusCalledStationId' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.7 - NAME 'radiusCallingStationId' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.8 - NAME 'radiusClass' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.45 - NAME 'radiusClientIPAddress' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.9 - NAME 'radiusFilterId' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.10 - NAME 'radiusFramedAppleTalkLink' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.11 - NAME 'radiusFramedAppleTalkNetwork' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.12 - NAME 'radiusFramedAppleTalkZone' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.13 - NAME 'radiusFramedCompression' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.14 - NAME 'radiusFramedIPAddress' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.15 - NAME 'radiusFramedIPNetmask' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.16 - NAME 'radiusFramedIPXNetwork' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.17 - NAME 'radiusFramedMTU' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.18 - NAME 'radiusFramedProtocol' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.19 - NAME 'radiusFramedRoute' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.20 - NAME 'radiusFramedRouting' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.46 - NAME 'radiusGroupName' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.47 - NAME 'radiusHint' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.48 - NAME 'radiusHuntgroupName' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.21 - NAME 'radiusIdleTimeout' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.22 - NAME 'radiusLoginIPHost' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.23 - NAME 'radiusLoginLATGroup' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.24 - NAME 'radiusLoginLATNode' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.25 - NAME 'radiusLoginLATPort' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.26 - NAME 'radiusLoginLATService' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.27 - NAME 'radiusLoginService' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.28 - NAME 'radiusLoginTCPPort' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.29 - NAME 'radiusPasswordRetry' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.30 - NAME 'radiusPortLimit' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.49 - NAME 'radiusProfileDn' - DESC '' - EQUALITY distinguishedNameMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.31 - NAME 'radiusPrompt' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.50 - NAME 'radiusProxyToRealm' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.51 - NAME 'radiusReplicateToRealm' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.52 - NAME 'radiusRealm' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.32 - NAME 'radiusServiceType' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.33 - NAME 'radiusSessionTimeout' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.34 - NAME 'radiusTerminationAction' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.35 - NAME 'radiusTunnelAssignmentId' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.36 - NAME 'radiusTunnelMediumType' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.37 - NAME 'radiusTunnelPassword' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.38 - NAME 'radiusTunnelPreference' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.39 - NAME 'radiusTunnelPrivateGroupId' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.40 - NAME 'radiusTunnelServerEndpoint' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.41 - NAME 'radiusTunnelType' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.42 - NAME 'radiusVSA' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.43 - NAME 'radiusTunnelClientEndpoint' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -#need to change asn1.id -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.53 - NAME 'radiusSimultaneousUse' - DESC '' - SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.54 - NAME 'radiusLoginTime' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.55 - NAME 'radiusUserCategory' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.56 - NAME 'radiusStripUserName' - DESC '' - SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.57 - NAME 'dialupAccess' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.58 - NAME 'radiusExpiration' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.59 - NAME 'radiusCheckItem' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.60 - NAME 'radiusReplyItem' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.61 - NAME 'radiusNASIpAddress' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.62 - NAME 'radiusReplyMessage' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -objectClasses: - ( 1.3.6.1.4.1.3317.4.3.2.1 - NAME 'radiusprofile' - SUP top AUXILIARY - DESC '' - MUST uid - MAY ( radiusArapFeatures $ radiusArapSecurity $ radiusArapZoneAccess $ - radiusAuthType $ radiusCallbackId $ radiusCallbackNumber $ - radiusCalledStationId $ radiusCallingStationId $ radiusClass $ - radiusClientIPAddress $ radiusFilterId $ radiusFramedAppleTalkLink $ - radiusFramedAppleTalkNetwork $ radiusFramedAppleTalkZone $ - radiusFramedCompression $ radiusFramedIPAddress $ - radiusFramedIPNetmask $ radiusFramedIPXNetwork $ - radiusFramedMTU $ radiusFramedProtocol $ - radiusCheckItem $ radiusReplyItem $ - radiusFramedRoute $ radiusFramedRouting $ radiusIdleTimeout $ - radiusGroupName $ radiusHint $ radiusHuntgroupName $ - radiusLoginIPHost $ radiusLoginLATGroup $ radiusLoginLATNode $ - radiusLoginLATPort $ radiusLoginLATService $ radiusLoginService $ - radiusLoginTCPPort $ radiusLoginTime $ radiusPasswordRetry $ - radiusPortLimit $ radiusPrompt $ radiusProxyToRealm $ - radiusRealm $ radiusReplicateToRealm $ radiusServiceType $ - radiusSessionTimeout $ radiusStripUserName $ - radiusTerminationAction $ radiusTunnelClientEndpoint $ radiusProfileDn $ - radiusSimultaneousUse $ radiusTunnelAssignmentId $ - radiusTunnelMediumType $ radiusTunnelPassword $ radiusTunnelPreference $ - radiusTunnelPrivateGroupId $ radiusTunnelServerEndpoint $ - radiusTunnelType $ radiusUserCategory $ radiusVSA $ - radiusExpiration $ dialupAccess $ radiusNASIpAddress $ - radiusReplyMessage ) - ) -objectClasses: - ( 1.3.6.1.4.1.3317.4.3.2.2 - NAME 'radiusObjectProfile' - SUP top STRUCTURAL - DESC 'A Container Objectclass to be used for creating radius profile object' - MUST cn - MAY ( uid $ userPassword $ description ) - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.64 - NAME 'radiusClientSecret' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.65 - NAME 'radiusClientNASType' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.66 - NAME 'radiusClientShortName' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - ) -objectClasses: - ( 1.3.6.1.4.1.3317.4.3.2.3 - NAME 'radiusClientProfile' - SUP top STRUCTURAL - DESC 'A Container Objectclass to be used for describing radius clients' - MUST (radiusClientIPAddress $ radiusClientSecret) - MAY ( radiusClientNASType $ radiusClientShortName $ description ) - ) diff --git a/ipa-server/ipa-install/share/60samba.ldif b/ipa-server/ipa-install/share/60samba.ldif deleted file mode 100644 index d3a6d31b..00000000 --- a/ipa-server/ipa-install/share/60samba.ldif +++ /dev/null @@ -1,152 +0,0 @@ -## schema file for Fedora DS -## -## Schema for storing Samba user accounts and group maps in LDAP -## OIDs are owned by the Samba Team -## -## Prerequisite schemas - uid (cosine.schema) -## - displayName (inetorgperson.schema) -## - gidNumber (nis.schema) -## -## 1.3.6.1.4.1.7165.2.1.x - attributeTypess -## 1.3.6.1.4.1.7165.2.2.x - objectClasseses -## -## Printer support -## 1.3.6.1.4.1.7165.2.3.1.x - attributeTypess -## 1.3.6.1.4.1.7165.2.3.2.x - objectClasseses -## -## Samba4 -## 1.3.6.1.4.1.7165.4.1.x - attributeTypess -## 1.3.6.1.4.1.7165.4.2.x - objectClasseses -## 1.3.6.1.4.1.7165.4.3.x - LDB/LDAP Controls -## 1.3.6.1.4.1.7165.4.4.x - LDB/LDAP Extended Operations -## 1.3.6.1.4.1.7165.4.255.x - mapped OIDs due to conflicts between AD and standards-track -## -dn: cn=schema -## -####################################################################### -## Attributes used by Samba 3.0 schema ## -####################################################################### -## -## Password hashes## -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.24 NAME 'sambaLMPassword' DESC 'LanManager Password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.25 NAME 'sambaNTPassword' DESC 'MD4 hash of the unicode password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) -## -## Account flags in string format ([UWDX ]) -## -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.26 NAME 'sambaAcctFlags' DESC 'Account Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{16} SINGLE-VALUE ) -## -## Password timestamps & policies -## -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.27 NAME 'sambaPwdLastSet' DESC 'Timestamp of the last password update' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.28 NAME 'sambaPwdCanChange' DESC 'Timestamp of when the user is allowed to update the password' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.29 NAME 'sambaPwdMustChange' DESC 'Timestamp of when the password will expire' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.30 NAME 'sambaLogonTime' DESC 'Timestamp of last logon' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.31 NAME 'sambaLogoffTime' DESC 'Timestamp of last logoff' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.32 NAME 'sambaKickoffTime' DESC 'Timestamp of when the user will be logged off automatically' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.48 NAME 'sambaBadPasswordCount' DESC 'Bad password attempt count' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.49 NAME 'sambaBadPasswordTime' DESC 'Time of the last bad password attempt' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.55 NAME 'sambaLogonHours' DESC 'Logon Hours' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{42} SINGLE-VALUE ) -## -## string settings -## -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.33 NAME 'sambaHomeDrive' DESC 'Driver letter of home directory mapping' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.34 NAME 'sambaLogonScript' DESC 'Logon script path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.35 NAME 'sambaProfilePath' DESC 'Roaming profile path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.36 NAME 'sambaUserWorkstations' DESC 'List of user workstations the user is allowed to logon to' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.37 NAME 'sambaHomePath' DESC 'Home directory UNC path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.38 NAME 'sambaDomainName' DESC 'Windows NT domain to which the user belongs' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.47 NAME 'sambaMungedDial' DESC 'Base64 encoded user parameter string' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.54 NAME 'sambaPasswordHistory' DESC 'Concatenated MD5 hashes of the salted NT passwords used on this account' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} ) -## -## SID, of any type -## -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.20 NAME 'sambaSID' DESC 'Security ID' EQUALITY caseIgnoreIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE ) -## -## Primary group SID, compatible with ntSid -## -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.23 NAME 'sambaPrimaryGroupSID' DESC 'Primary Group Security ID' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.51 NAME 'sambaSIDList' DESC 'Security ID List' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} ) -## -## group mapping attributes -## -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.19 NAME 'sambaGroupType' DESC 'NT Group Type' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -## -## Store info on the domain -## -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.21 NAME 'sambaNextUserRid' DESC 'Next NT rid to give our for users' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.22 NAME 'sambaNextGroupRid' DESC 'Next NT rid to give out for groups' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.39 NAME 'sambaNextRid' DESC 'Next NT rid to give out for anything' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.40 NAME 'sambaAlgorithmicRidBase' DESC 'Base at which the samba RID generation algorithm should operate' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.41 NAME 'sambaShareName' DESC 'Share Name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.42 NAME 'sambaOptionName' DESC 'Option Name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.43 NAME 'sambaBoolOption' DESC 'A boolean option' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.44 NAME 'sambaIntegerOption' DESC 'An integer option' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.45 NAME 'sambaStringOption' DESC 'A string option' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.46 NAME 'sambaStringListOption' DESC 'A string list option' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) -##attributeTypes: ( 1.3.6.1.4.1.7165.2.1.50 NAME 'sambaPrivName' -## SUP name ) -## -##attributeTypes: ( 1.3.6.1.4.1.7165.2.1.52 NAME 'sambaPrivilegeList' -## DESC 'Privileges List' -## EQUALITY caseIgnoreIA5Match -## SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} ) -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.53 NAME 'sambaTrustFlags' DESC 'Trust Password Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) -# "min password length" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.58 NAME 'sambaMinPwdLength' DESC 'Minimal password length (default: 5)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -# "password history" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.59 NAME 'sambaPwdHistoryLength' DESC 'Length of Password History Entries (default: 0 => off)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -# "user must logon to change password" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.60 NAME 'sambaLogonToChgPwd' DESC 'Force Users to logon for password change (default: 0 => off, 2 => on)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -# "maximum password age" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.61 NAME 'sambaMaxPwdAge' DESC 'Maximum password age, in seconds (default: -1 => never expire passwords)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -# "minimum password age" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.62 NAME 'sambaMinPwdAge' DESC 'Minimum password age, in seconds (default: 0 => allow immediate password change)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -# "lockout duration" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.63 NAME 'sambaLockoutDuration' DESC 'Lockout duration in minutes (default: 30, -1 => forever)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -# "reset count minutes" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.64 NAME 'sambaLockoutObservationWindow' DESC 'Reset time after lockout in minutes (default: 30)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -# "bad lockout attempt" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.65 NAME 'sambaLockoutThreshold' DESC 'Lockout users after bad logon attempts (default: 0 => off)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -# "disconnect time" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.66 NAME 'sambaForceLogoff' DESC 'Disconnect Users outside logon hours (default: -1 => off, 0 => on)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -# "refuse machine password change" -attributeTypes: ( 1.3.6.1.4.1.7165.2.1.67 NAME 'sambaRefuseMachinePwdChange' DESC 'Allow Machine Password changes (default: 0 => off)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) -## -####################################################################### -## objectClasses: used by Samba 3.0 schema ## -####################################################################### -## -## The X.500 data model (and therefore LDAPv3) says that each entry can -## only have one structural objectClasses. OpenLDAP 2.0 does not enforce -## this currently but will in v2.1 -## -## added new objectClasses: (and OID) for 3.0 to help us deal with backwards -## compatibility with 2.2 installations (e.g. ldapsam_compat) --jerry -## -objectClasses: ( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' SUP top AUXILIARY DESC 'Samba 3.0 Auxilary SAM Account' MUST ( uid $ sambaSID ) MAY ( cn $ sambaLMPassword $ sambaNTPassword $ sambaPwdLastSet $ sambaLogonTime $ sambaLogoffTime $ sambaKickoffTime $ sambaPwdCanChange $ sambaPwdMustChange $ sambaAcctFlags $ displayName $ sambaHomePath $ sambaHomeDrive $ sambaLogonScript $ sambaProfilePath $ description $ sambaUserWorkstations $ sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $ sambaBadPasswordCount $ sambaBadPasswordTime $ sambaPasswordHistory $ sambaLogonHours)) -## -## Group mapping info -## -objectClasses: ( 1.3.6.1.4.1.7165.2.2.4 NAME 'sambaGroupMapping' SUP top AUXILIARY DESC 'Samba Group Mapping' MUST ( gidNumber $ sambaSID $ sambaGroupType ) MAY ( displayName $ description $ sambaSIDList )) -## -## Trust password for trust relationships (any kind) -## -objectClasses: ( 1.3.6.1.4.1.7165.2.2.14 NAME 'sambaTrustPassword' SUP top STRUCTURAL DESC 'Samba Trust Password' MUST ( sambaDomainName $ sambaNTPassword $ sambaTrustFlags ) MAY ( sambaSID $ sambaPwdLastSet )) -## -## Whole-of-domain info -## -objectClasses: ( 1.3.6.1.4.1.7165.2.2.5 NAME 'sambaDomain' SUP top STRUCTURAL DESC 'Samba Domain Information' MUST ( sambaDomainName $ sambaSID ) MAY ( sambaNextRid $ sambaNextGroupRid $ sambaNextUserRid $ sambaAlgorithmicRidBase $ sambaMinPwdLength $ sambaPwdHistoryLength $ sambaLogonToChgPwd $ sambaMaxPwdAge $ sambaMinPwdAge $ sambaLockoutDuration $ sambaLockoutObservationWindow $ sambaLockoutThreshold $ sambaForceLogoff $ sambaRefuseMachinePwdChange )) -## -## used for idmap_ldap module -## -objectClasses: ( 1.3.6.1.4.1.7165.2.2.7 NAME 'sambaUnixIdPool' SUP top AUXILIARY DESC 'Pool for allocating UNIX uids/gids' MUST ( uidNumber $ gidNumber ) ) -objectClasses: ( 1.3.6.1.4.1.7165.2.2.8 NAME 'sambaIdmapEntry' SUP top AUXILIARY DESC 'Mapping from a SID to an ID' MUST ( sambaSID ) MAY ( uidNumber $ gidNumber ) ) -objectClasses: ( 1.3.6.1.4.1.7165.2.2.9 NAME 'sambaSidEntry' SUP top STRUCTURAL DESC 'Structural Class for a SID' MUST ( sambaSID ) ) -objectClasses: ( 1.3.6.1.4.1.7165.2.2.10 NAME 'sambaConfig' SUP top AUXILIARY DESC 'Samba Configuration Section' MAY ( description ) ) -objectClasses: ( 1.3.6.1.4.1.7165.2.2.11 NAME 'sambaShare' SUP top STRUCTURAL DESC 'Samba Share Section' MUST ( sambaShareName ) MAY ( description ) ) -objectClasses: ( 1.3.6.1.4.1.7165.2.2.12 NAME 'sambaConfigOption' SUP top STRUCTURAL DESC 'Samba Configuration Option' MUST ( sambaOptionName ) MAY ( sambaBoolOption $ sambaIntegerOption $ sambaStringOption $ sambaStringListoption $ description ) ) -## retired during privilege rewrite -##objectClasses: ( 1.3.6.1.4.1.7165.2.2.13 NAME 'sambaPrivilege' SUP top AUXILIARY -## DESC 'Samba Privilege' -## MUST ( sambaSID ) -## MAY ( sambaPrivilegeList ) ) diff --git a/ipa-server/ipa-install/share/Makefile.am b/ipa-server/ipa-install/share/Makefile.am deleted file mode 100644 index 6be2e13d..00000000 --- a/ipa-server/ipa-install/share/Makefile.am +++ /dev/null @@ -1,39 +0,0 @@ -NULL = - -appdir = $(IPA_DATA_DIR) -app_DATA = \ - 60kerberos.ldif \ - 60samba.ldif \ - 60radius.ldif \ - 60ipaconfig.ldif \ - bootstrap-template.ldif \ - default-aci.ldif \ - default-keytypes.ldif \ - kerberos.ldif \ - indices.ldif \ - bind.named.conf.template \ - bind.zone.db.template \ - certmap.conf.template \ - kdc.conf.template \ - krb5.conf.template \ - krb5.ini.template \ - krb.con.template \ - krbrealm.con.template \ - ntp.conf.server.template \ - ntpd.sysconfig.template \ - preferences.html.template \ - referint-conf.ldif \ - dna-posix.ldif \ - master-entry.ldif \ - memberof-task.ldif \ - unique-attributes.ldif \ - schema_compat.uldif \ - $(NULL) - -EXTRA_DIST = \ - $(app_DATA) \ - $(NULL) - -MAINTAINERCLEANFILES = \ - *~ \ - Makefile.in diff --git a/ipa-server/ipa-install/share/bind.named.conf.template b/ipa-server/ipa-install/share/bind.named.conf.template deleted file mode 100644 index c1d2817e..00000000 --- a/ipa-server/ipa-install/share/bind.named.conf.template +++ /dev/null @@ -1,41 +0,0 @@ -options { - /* make named use port 53 for the source of all queries, to allow - * firewalls to block all ports except 53: - */ - query-source port 53; - query-source-v6 port 53; - - // Put files that named is allowed to write in the data/ directory: - directory "/var/named"; // the default - dump-file "data/cache_dump.db"; - statistics-file "data/named_stats.txt"; - memstatistics-file "data/named_mem_stats.txt"; - - /* Not used yet, support only on very recent bind versions */ -# tkey-gssapi-credential "DNS/$FQDN"; -# tkey-domain "$REALM"; -}; - -logging { -/* If you want to enable debugging, eg. using the 'rndc trace' command, - * By default, SELinux policy does not allow named to modify the /var/named directory, - * so put the default debug log file in data/ : - */ - channel default_debug { - file "data/named.run"; - severity dynamic; - }; -}; - -zone "." IN { - type hint; - file "named.ca"; -}; - -include "/etc/named.rfc1912.zones"; - -zone "$DOMAIN" { - type master; - file "$DOMAIN.zone.db"; -}; - diff --git a/ipa-server/ipa-install/share/bind.zone.db.template b/ipa-server/ipa-install/share/bind.zone.db.template deleted file mode 100644 index aca7d2d2..00000000 --- a/ipa-server/ipa-install/share/bind.zone.db.template +++ /dev/null @@ -1,28 +0,0 @@ -$$ORIGIN $DOMAIN. -$$TTL 86400 -@ IN SOA $DOMAIN. root.$DOMAIN. ( - 01 ; serial - 3H ; refresh - 15M ; retry - 1W ; expiry - 1D ) ; minimum - - IN NS $HOST -$HOST IN A $IP -; -; ldap servers -_ldap._tcp IN SRV 0 100 389 $HOST - -;kerberos realm -_kerberos IN TXT $REALM - -; kerberos servers -_kerberos._tcp IN SRV 0 100 88 $HOST -_kerberos._udp IN SRV 0 100 88 $HOST -_kerberos-master._tcp IN SRV 0 100 88 $HOST -_kerberos-master._udp IN SRV 0 100 88 $HOST -_kpasswd._tcp IN SRV 0 100 464 $HOST -_kpasswd._udp IN SRV 0 100 464 $HOST - -;ntp server -_ntp._udp IN SRV 0 100 123 $HOST diff --git a/ipa-server/ipa-install/share/bootstrap-template.ldif b/ipa-server/ipa-install/share/bootstrap-template.ldif deleted file mode 100644 index eb69ae4d..00000000 --- a/ipa-server/ipa-install/share/bootstrap-template.ldif +++ /dev/null @@ -1,202 +0,0 @@ -dn: cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: nsContainer -objectClass: krbPwdPolicy -cn: accounts -krbMinPwdLife: 3600 -krbPwdMinDiffChars: 0 -krbPwdMinLength: 8 -krbPwdHistoryLength: 0 -krbMaxPwdLife: 7776000 - -dn: cn=users,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: nsContainer -cn: users - -dn: cn=groups,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: nsContainer -cn: groups - -dn: cn=services,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: nsContainer -cn: services - -dn: cn=computers,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: nsContainer -cn: computers - -dn: cn=etc,$SUFFIX -changetype: add -objectClass: nsContainer -objectClass: top -cn: etc - -dn: cn=sysaccounts,cn=etc,$SUFFIX -changetype: add -objectClass: nsContainer -objectClass: top -cn: sysaccounts - -dn: cn=ipa,cn=etc,$SUFFIX -changetype: add -objectClass: nsContainer -objectClass: top -cn: ipa - -dn: cn=masters,cn=ipa,cn=etc,$SUFFIX -changetype: add -objectClass: nsContainer -objectClass: top -cn: masters - -dn: uid=admin,cn=users,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: person -objectClass: posixAccount -objectClass: KrbPrincipalAux -objectClass: inetUser -uid: admin -krbPrincipalName: admin@$REALM -cn: Administrator -sn: Administrator -uidNumber: 999 -gidNumber: 1001 -homeDirectory: /home/admin -loginShell: /bin/bash -gecos: Administrator -nsAccountLock: False - -dn: cn=radius,$SUFFIX -changetype: add -objectClass: nsContainer -objectClass: top -cn: radius - -dn: cn=clients,cn=radius,$SUFFIX -changetype: add -objectClass: nsContainer -objectClass: top -cn: clients - -dn: cn=profiles,cn=radius,$SUFFIX -changetype: add -objectClass: nsContainer -objectClass: top -cn: profiles - -dn: uid=ipa_default, cn=profiles,cn=radius,$SUFFIX -changetype: add -objectClass: top -objectClass: radiusprofile -uid: ipa_default - -dn: cn=admins,cn=groups,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: groupofnames -objectClass: posixGroup -cn: admins -description: Account administrators group -gidNumber: 1001 -member: uid=admin,cn=users,cn=accounts,$SUFFIX -nsAccountLock: False - -dn: cn=ipausers,cn=groups,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: groupofnames -objectClass: posixGroup -gidNumber: 1002 -description: Default group for all users -cn: ipausers - -dn: cn=editors,cn=groups,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: groupofnames -objectClass: posixGroup -gidNumber: 1003 -description: Limited admins who can edit other users -cn: editors - -dn: cn=ipaConfig,cn=etc,$SUFFIX -changetype: add -objectClass: nsContainer -objectClass: top -objectClass: ipaGuiConfig -ipaUserSearchFields: uid,givenName,sn,telephoneNumber,ou,title -ipaGroupSearchFields: cn,description -ipaSearchTimeLimit: 2 -ipaSearchRecordsLimit: 0 -ipaHomesRootDir: /home -ipaDefaultLoginShell: /bin/sh -ipaDefaultPrimaryGroup: ipausers -ipaMaxUsernameLength: 8 -ipaPwdExpAdvNotify: 4 -ipaGroupObjectClasses: top -ipaGroupObjectClasses: groupofnames -ipaGroupObjectClasses: posixGroup -ipaGroupObjectClasses: inetUser -ipaUserObjectClasses: top -ipaUserObjectClasses: person -ipaUserObjectClasses: organizationalPerson -ipaUserObjectClasses: inetOrgPerson -ipaUserObjectClasses: inetUser -ipaUserObjectClasses: posixAccount -ipaUserObjectClasses: krbPrincipalAux -ipaUserObjectClasses: radiusprofile -ipaDefaultEmailDomain: $DOMAIN - -dn: cn=account inactivation,cn=accounts,$SUFFIX -changetype: add -description: Lock accounts based on group membership -objectClass: top -objectClass: ldapsubentry -objectClass: cosSuperDefinition -objectClass: cosClassicDefinition -cosTemplateDn: cn=cosTemplates,cn=accounts,$SUFFIX -cosAttribute: nsAccountLock operational -cosSpecifier: memberOf -cn: Account Inactivation - -dn: cn=cosTemplates,cn=accounts,$SUFFIX -changetype: add -objectclass: top -objectclass: nsContainer -cn: cosTemplates - -dn: cn="cn=inactivated,cn=account inactivation,cn=accounts,$SUFFIX", cn=cosTemplates,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: cosTemplate -objectClass: extensibleobject -nsAccountLock: true -cosPriority: 1 - -dn: cn=inactivated,cn=account inactivation,cn=accounts,$SUFFIX -changetype: add -objectclass: top -objectclass: groupofnames - -dn: cn="cn=activated,cn=account inactivation,cn=accounts,$SUFFIX", cn=cosTemplates,cn=accounts,$SUFFIX -changetype: add -objectClass: top -objectClass: cosTemplate -objectClass: extensibleobject -nsAccountLock: false -cosPriority: 0 - -dn: cn=Activated,cn=Account Inactivation,cn=accounts,$SUFFIX -changetype: add -objectclass: top -objectclass: groupofnames diff --git a/ipa-server/ipa-install/share/certmap.conf.template b/ipa-server/ipa-install/share/certmap.conf.template deleted file mode 100644 index 676d3ef3..00000000 --- a/ipa-server/ipa-install/share/certmap.conf.template +++ /dev/null @@ -1,82 +0,0 @@ -# -# 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) 2001 Sun Microsystems, Inc. Used by permission. -# Copyright (C) 2005 Red Hat, Inc. -# All rights reserved. -# END COPYRIGHT BLOCK -# -# -# This file configures how a certificate is mapped to an LDAP entry. See the -# documentation for more information on this file. -# -# The format of this file is as follows: -# certmap -# : [] -# : [] -# -# Notes: -# -# 1. Mapping can be defined per issuer of a certificate. If mapping doesn't -# exists for a particular 'issuerDN' then the server uses the default -# mapping. -# -# 2. There must be an entry for =default and issuerDN "default". -# This mapping is the default mapping. -# -# 3. '#' can be used to comment out a line. -# -# 4. DNComps & FilterComps are used to form the base DN and filter resp. for -# performing an LDAP search while mapping the cert to a user entry. -# -# 5. DNComps can be one of the following: -# commented out - take the user's DN from the cert as is -# empty - search the entire LDAP tree (DN == suffix) -# attr names - a comma separated list of attributes to form DN -# -# 6. FilterComps can be one of the following: -# commented out - set the filter to "objectclass=*" -# empty - set the filter to "objectclass=*" -# attr names - a comma separated list of attributes to form the filter -# - -certmap default default -#default:DNComps -#default:FilterComps e, uid -#default:verifycert on -#default:CmapLdapAttr certSubjectDN -#default:library -#default:InitFn -default:DNComps -default:FilterComps uid diff --git a/ipa-server/ipa-install/share/default-aci.ldif b/ipa-server/ipa-install/share/default-aci.ldif deleted file mode 100644 index 25bd3b22..00000000 --- a/ipa-server/ipa-install/share/default-aci.ldif +++ /dev/null @@ -1,38 +0,0 @@ -# $SUFFIX (base entry) -# FIXME: We need to allow truly anonymous access only to NIS data for older clients. We need to allow broad access to most attributes only to authenticated users -dn: $SUFFIX -changetype: modify -add: aci -aci: (targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey")(version 3.0; acl "Enable Anonymous access"; allow (read, search, compare) userdn = "ldap:///anyone";) -aci: (targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey")(version 3.0; acl "Admin can manage any entry"; allow (all) userdn = "ldap:///uid=admin,cn=users,cn=accounts,$SUFFIX";) -aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword")(version 3.0; acl "Self can write own password"; allow (write) userdn="ldap:///self";) -aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Admins can write passwords"; allow (add,delete,write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) -aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Password change service can read/write passwords"; allow (read, write) userdn="ldap:///krbprincipalname=kadmin/changepw@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";) -aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "KDC System Account can access passwords"; allow (all) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) -aci: (targetattr = "krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCount")(version 3.0; acl "KDC System Account can update some fields"; allow (write) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) -aci: (targetattr = "krbPrincipalName || krbUPEnabled || krbMKey || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData || krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCount")(version 3.0; acl "Only the KDC System Account has access to kerberos material"; allow (read, search, compare) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) -aci: (targetfilter = "(|(objectClass=person)(objectClass=krbPrincipalAux)(objectClass=posixAccount)(objectClass=groupOfNames)(objectClass=posixGroup))")(targetattr != "aci || userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Account Admins can manage Users and Groups"; allow (add, delete, read, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) -aci: (targetfilter = "(objectClass=krbPwdPolicy)")(targetattr = "krbMaxPwdLife || krbMinPwdLife || krbPwdMinDiffChars || krbPwdMinLength || krbPwdHistoryLength")(version 3.0;acl "Admins can write password policies"; allow (read, search, compare, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) -aci: (targetattr = "givenName || sn || cn || displayName || title || initials || loginShell || gecos || homePhone || mobile || pager || facsimileTelephoneNumber || telephoneNumber || street || roomNumber || l || st || postalCode || manager || secretary || description || carLicense || labeledURI || inetUserHTTPURL || seeAlso || employeeType || businessCategory || ou")(version 3.0;acl "Self service";allow (write) userdn = "ldap:///self";) - -dn: cn=ipaConfig,cn=etc,$SUFFIX -changetype: modify -add: aci -aci: (targetfilter = "(objectClass=ipaGuiConfig)")(targetattr != "aci")(version 3.0;acl "Admins can change GUI config"; allow (read, search, compare, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) - -dn: cn=accounts,$SUFFIX -changetype: modify -add: aci -aci: (targetattr = "krbMaxPwdLife || krbMinPwdLife || krbPwdMinDiffChars || krbPwdMinLength || krbPwdHistoryLength")(version 3.0;acl "Admins can write password policy"; allow (write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) -aci: (targetattr = "aci")(version 3.0;acl "Admins can manage delegations"; allow (write, delete) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) - -dn: cn=radius,$SUFFIX -changetype: modify -add: aci -aci: (targetattr = "*")(version 3.0; acl "Only radius and admin can access radius service data"; deny (all) userdn!="ldap:///uid=admin,cn=users,cn=accounts,$SUFFIX || ldap:///krbprincipalname=radius/$FQDN@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";) -aci: (targetfilter = "(objectClass=radiusprofile)")(targetattr != "aci || userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Account Admins can manage Users and Groups"; allow (add, delete, read, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) - -dn: cn=services,cn=accounts,$SUFFIX -changetype: modify -add: aci -aci: (targetattr="krbPrincipalName || krbUPEnabled || krbPrincipalKey || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData")(version 3.0; acl "KDC System Account"; allow (read, search, compare, write) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) diff --git a/ipa-server/ipa-install/share/default-keytypes.ldif b/ipa-server/ipa-install/share/default-keytypes.ldif deleted file mode 100644 index 1d54a059..00000000 --- a/ipa-server/ipa-install/share/default-keytypes.ldif +++ /dev/null @@ -1,25 +0,0 @@ -#kerberos keytypes -dn: cn=$REALM,cn=kerberos,$SUFFIX -changetype: modify -add: krbSupportedEncSaltTypes -krbSupportedEncSaltTypes: aes256-cts:normal -krbSupportedEncSaltTypes: aes128-cts:normal -krbSupportedEncSaltTypes: des3-hmac-sha1:normal -krbSupportedEncSaltTypes: arcfour-hmac:normal -krbSupportedEncSaltTypes: des-hmac-sha1:normal -krbSupportedEncSaltTypes: des-cbc-md5:normal -krbSupportedEncSaltTypes: des-cbc-crc:normal -krbSupportedEncSaltTypes: des-cbc-crc:v4 -krbSupportedEncSaltTypes: des-cbc-crc:afs3 - -#kerberos keytypes -dn: cn=$REALM,cn=kerberos,$SUFFIX -changetype: modify -add: krbDefaultEncSaltTypes -krbDefaultEncSaltTypes: aes256-cts:normal -krbDefaultEncSaltTypes: aes128-cts:normal -krbDefaultEncSaltTypes: des3-hmac-sha1:normal -krbDefaultEncSaltTypes: arcfour-hmac:normal -krbDefaultEncSaltTypes: des-hmac-sha1:normal -krbDefaultEncSaltTypes: des-cbc-md5:normal - diff --git a/ipa-server/ipa-install/share/dna-posix.ldif b/ipa-server/ipa-install/share/dna-posix.ldif deleted file mode 100644 index a8848545..00000000 --- a/ipa-server/ipa-install/share/dna-posix.ldif +++ /dev/null @@ -1,39 +0,0 @@ -# add container for posix configuration - -dn: cn=Posix,cn=ipa-dna,cn=plugins,cn=config -changetype: add -objectclass: top -objectclass: nsContainer -objectclass: extensibleObject -cn: Posix - -# add plugin configuration for posix users - -dn: cn=Accounts,cn=Posix,cn=ipa-dna,cn=plugins,cn=config -changetype: add -objectclass: top -objectclass: extensibleObject -cn: Accounts -dnaType: uidNumber -dnaNextValue: 1100 -dnaInterval: 1 -dnaMaxValue: 1000000000 -dnaMagicRegen: 999 -dnaFilter: (objectclass=posixAccount) -dnaScope: $SUFFIX - -# add plugin configuration for posix groups - -dn: cn=Groups,cn=Posix,cn=ipa-dna,cn=plugins,cn=config -changetype: add -objectclass: top -objectclass: extensibleObject -cn: Groups -dnaType: gidNumber -dnaNextValue: 1100 -dnaInterval: 1 -dnaMaxValue: 1000000000 -dnaMagicRegen: 999 -dnaFilter: (objectclass=posixGroup) -dnaScope: $SUFFIX - diff --git a/ipa-server/ipa-install/share/encrypted_attribute.ldif b/ipa-server/ipa-install/share/encrypted_attribute.ldif deleted file mode 100644 index 3f5e1b43..00000000 --- a/ipa-server/ipa-install/share/encrypted_attribute.ldif +++ /dev/null @@ -1,6 +0,0 @@ -dn: cn=$ENCRYPTED_ATTRIBUTE, cn=encrypted attributes, cn=userRoot, cn=ldbm database, cn=plugins, cn=config -changetype: add -objectClass: top -objectClass: nsAttributeEncryption -cn: $ENCRYPTED_ATTRIBUTE -nsEncryptionAlgorithm: AES diff --git a/ipa-server/ipa-install/share/fedora-ds.init.patch b/ipa-server/ipa-install/share/fedora-ds.init.patch deleted file mode 100644 index 865611d9..00000000 --- a/ipa-server/ipa-install/share/fedora-ds.init.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- /etc/init.d/dirsrv.orig 2007-07-06 18:21:30.000000000 -0400 -+++ /etc/init.d/dirsrv 2007-05-18 19:36:24.000000000 -0400 -@@ -10,6 +10,9 @@ - # datadir: /var/lib/dirsrv/slapd- - # - -+# Get config. -+[ -r /etc/sysconfig/dirsrv ] && . /etc/sysconfig/dirsrv -+ - # Source function library. - if [ -f /etc/rc.d/init.d/functions ] ; then - . /etc/rc.d/init.d/functions diff --git a/ipa-server/ipa-install/share/indices.ldif b/ipa-server/ipa-install/share/indices.ldif deleted file mode 100644 index 05c27655..00000000 --- a/ipa-server/ipa-install/share/indices.ldif +++ /dev/null @@ -1,93 +0,0 @@ -dn: cn=krbPrincipalName,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: add -objectClass:top -objectClass:nsIndex -cn:krbPrincipalName -nsSystemIndex:false -nsIndexType:eq -nsIndexType:sub - -dn: cn=ou,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: add -objectClass:top -objectClass:nsIndex -cn:ou -nsSystemIndex:false -nsIndexType:eq -nsIndexType:sub - -dn: cn=carLicense,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: add -objectClass:top -objectClass:nsIndex -cn:carLicense -nsSystemIndex:false -nsIndexType:eq -nsIndexType:sub - -dn: cn=title,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: add -objectClass:top -objectClass:nsIndex -cn:title -nsSystemIndex:false -nsIndexType:eq -nsIndexType:sub - -dn: cn=manager,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: add -objectClass:top -objectClass:nsIndex -cn:manager -nsSystemIndex:false -nsIndexType:eq - -dn: cn=secretary,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: add -objectClass:top -objectClass:nsIndex -cn:secretary -nsSystemIndex:false -nsIndexType:eq - -dn: cn=displayname,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: add -objectClass:top -objectClass:nsIndex -cn:displayname -nsSystemIndex:false -nsIndexType:eq -nsIndexType:sub - -dn: cn=uid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: modify -add: nsIndexType -nsIndexType:sub - -dn: cn=uidnumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: add -objectClass:top -objectClass:nsIndex -cn:uidnumber -nsSystemIndex:false -nsIndexType:eq -nsMatchingRule: integerOrderingMatch - -dn: cn=gidnumber,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: add -objectClass:top -objectClass:nsIndex -cn:gidnumber -nsSystemIndex:false -nsIndexType:eq -nsMatchingRule: integerOrderingMatch - -dn: cn=ntUniqueId,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: modify -replace: nsIndexType -nsIndexType: eq,pres - -dn: cn=ntUserDomainId,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -changetype: modify -replace: nsIndexType -nsIndexType: eq,pres diff --git a/ipa-server/ipa-install/share/kdc.conf.template b/ipa-server/ipa-install/share/kdc.conf.template deleted file mode 100644 index 0a574783..00000000 --- a/ipa-server/ipa-install/share/kdc.conf.template +++ /dev/null @@ -1,15 +0,0 @@ -[kdcdefaults] - kdc_ports = 88 - kdc_tcp_ports = 88 - -[realms] - $REALM = { - master_key_type = des3-hmac-sha1 - supported_enctypes = aes256-cts:normal aes128-cts:normal 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 - max_life = 7d - max_renewable_life = 14d - acl_file = /var/kerberos/krb5kdc/kadm5.acl - dict_file = /usr/share/dict/words - default_principal_flags = +preauth -; admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab - } diff --git a/ipa-server/ipa-install/share/kerberos.ldif b/ipa-server/ipa-install/share/kerberos.ldif deleted file mode 100644 index f1b651d5..00000000 --- a/ipa-server/ipa-install/share/kerberos.ldif +++ /dev/null @@ -1,16 +0,0 @@ -#kerberos user -dn: uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX -changetype: add -objectclass: account -objectclass: simplesecurityobject -uid: kdc -userPassword: $PASSWORD - -#kerberos base object -dn: cn=kerberos,$SUFFIX -changetype: add -objectClass: krbContainer -objectClass: top -cn: kerberos -aci: (targetattr="*")(version 3.0; acl "KDC System Account"; allow (all) userdn= "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) - diff --git a/ipa-server/ipa-install/share/krb.con.template b/ipa-server/ipa-install/share/krb.con.template deleted file mode 100644 index d75a8f60..00000000 --- a/ipa-server/ipa-install/share/krb.con.template +++ /dev/null @@ -1,2 +0,0 @@ -$REALM $DOMAIN -$REALM $DOMAIN admin server diff --git a/ipa-server/ipa-install/share/krb5.conf.template b/ipa-server/ipa-install/share/krb5.conf.template deleted file mode 100644 index b81cedfe..00000000 --- a/ipa-server/ipa-install/share/krb5.conf.template +++ /dev/null @@ -1,42 +0,0 @@ -[logging] - default = FILE:/var/log/krb5libs.log - kdc = FILE:/var/log/krb5kdc.log - admin_server = FILE:/var/log/kadmind.log - -[libdefaults] - default_realm = $REALM - dns_lookup_realm = true - dns_lookup_kdc = true - ticket_lifetime = 24h - forwardable = yes - -[realms] - $REALM = { - kdc = $FQDN:88 - admin_server = $FQDN:749 - default_domain = $DOMAIN -} - -[domain_realm] - .$DOMAIN = $REALM - $DOMAIN = $REALM - -[appdefaults] - pam = { - debug = false - ticket_lifetime = 36000 - renew_lifetime = 36000 - forwardable = true - krb4_convert = false - } - -[dbmodules] - $REALM = { - db_library = kldap - ldap_servers = ldap://127.0.0.1/ - ldap_kerberos_container_dn = cn=kerberos,$SUFFIX - ldap_kdc_dn = uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX - ldap_kadmind_dn = uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX - ldap_service_password_file = /var/kerberos/krb5kdc/ldappwd - } - diff --git a/ipa-server/ipa-install/share/krb5.ini.template b/ipa-server/ipa-install/share/krb5.ini.template deleted file mode 100644 index 89f4a370..00000000 --- a/ipa-server/ipa-install/share/krb5.ini.template +++ /dev/null @@ -1,19 +0,0 @@ -[libdefaults] - default_realm = $REALM - krb4_config = /usr/kerberos/lib/krb.conf - krb4_realms = /usr/kerberos/lib/krb.realms - dns_lookup_kdc = true - -[realms] - $REALM = { - admin_server = $FQDN - kdc = $FQDN - default_domain = $REALM - } - -[domain_realm] - .$DOMAIN = $REALM - $DOMAIN = $REALM - -[logging] -# kdc = CONSOLE diff --git a/ipa-server/ipa-install/share/krbrealm.con.template b/ipa-server/ipa-install/share/krbrealm.con.template deleted file mode 100644 index c6781386..00000000 --- a/ipa-server/ipa-install/share/krbrealm.con.template +++ /dev/null @@ -1,3 +0,0 @@ -.$REALM $REALM -.$REALM. $REALM -$REALM $REALM diff --git a/ipa-server/ipa-install/share/master-entry.ldif b/ipa-server/ipa-install/share/master-entry.ldif deleted file mode 100644 index 09c1d44f..00000000 --- a/ipa-server/ipa-install/share/master-entry.ldif +++ /dev/null @@ -1,7 +0,0 @@ -dn: cn=$FQHN,cn=masters,cn=ipa,cn=etc,$SUFFIX -changetype: add -objectclass: top -objectclass: extensibleObject -cn: $FQHN -dnabase: 1100 -dnainterval: 4 diff --git a/ipa-server/ipa-install/share/memberof-task.ldif b/ipa-server/ipa-install/share/memberof-task.ldif deleted file mode 100644 index 827949e3..00000000 --- a/ipa-server/ipa-install/share/memberof-task.ldif +++ /dev/null @@ -1,8 +0,0 @@ -dn: cn=IPA install $TIME, cn=memberof task, cn=tasks, cn=config -changetype: add -objectClass: top -objectClass: extensibleObject -cn: IPA install -basedn: $SUFFIX -filter: (objectclass=*) -ttl: 10 diff --git a/ipa-server/ipa-install/share/ntp.conf.server.template b/ipa-server/ipa-install/share/ntp.conf.server.template deleted file mode 100644 index 09149dfc..00000000 --- a/ipa-server/ipa-install/share/ntp.conf.server.template +++ /dev/null @@ -1,50 +0,0 @@ -# Permit time synchronization with our time source, but do not -# permit the source to query or modify the service on this system. -restrict default kod nomodify notrap -restrict -6 default kod nomodify notrap - -# Permit all access over the loopback interface. This could -# be tightened as well, but to do so would effect some of -# the administrative functions. -restrict 127.0.0.1 -restrict -6 ::1 - -# Hosts on local network are less restricted. -#restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap - -# Use public servers from the pool.ntp.org project. -# Please consider joining the pool (http://www.pool.ntp.org/join.html). -server $SERVERA -server $SERVERB -server $SERVERC - -#broadcast 192.168.1.255 key 42 # broadcast server -#broadcastclient # broadcast client -#broadcast 224.0.1.1 key 42 # multicast server -#multicastclient 224.0.1.1 # multicast client -#manycastserver 239.255.254.254 # manycast server -#manycastclient 239.255.254.254 key 42 # manycast client - -# Undisciplined Local Clock. This is a fake driver intended for backup -# and when no outside source of synchronized time is available. -server 127.127.1.0 # local clock -#fudge 127.127.1.0 stratum 10 - -# Drift file. Put this in a directory which the daemon can write to. -# No symbolic links allowed, either, since the daemon updates the file -# by creating a temporary in the same directory and then rename()'ing -# it to the file. -driftfile /var/lib/ntp/drift - -# Key file containing the keys and key identifiers used when operating -# with symmetric key cryptography. -keys /etc/ntp/keys - -# Specify the key identifiers which are trusted. -#trustedkey 4 8 42 - -# Specify the key identifier to use with the ntpdc utility. -#requestkey 8 - -# Specify the key identifier to use with the ntpq utility. -#controlkey 8 diff --git a/ipa-server/ipa-install/share/ntpd.sysconfig.template b/ipa-server/ipa-install/share/ntpd.sysconfig.template deleted file mode 100644 index 3412a0e8..00000000 --- a/ipa-server/ipa-install/share/ntpd.sysconfig.template +++ /dev/null @@ -1,8 +0,0 @@ -# Drop root to id 'ntp:ntp' by default. -OPTIONS="-x -u ntp:ntp -p /var/run/ntpd.pid" - -# Set to 'yes' to sync hw clock after successful ntpdate -SYNC_HWCLOCK=yes - -# Additional options for ntpdate -NTPDATE_OPTIONS="" diff --git a/ipa-server/ipa-install/share/preferences.html.template b/ipa-server/ipa-install/share/preferences.html.template deleted file mode 100644 index 2d3684dc..00000000 --- a/ipa-server/ipa-install/share/preferences.html.template +++ /dev/null @@ -1,33 +0,0 @@ - - - - Automatically set browser preferences - - -
- -
- - - - - diff --git a/ipa-server/ipa-install/share/referint-conf.ldif b/ipa-server/ipa-install/share/referint-conf.ldif deleted file mode 100644 index 533b97de..00000000 --- a/ipa-server/ipa-install/share/referint-conf.ldif +++ /dev/null @@ -1,11 +0,0 @@ -dn: cn=referential integrity postoperation,cn=plugins,cn=config -changetype: modify -replace: nsslapd-pluginenabled -nsslapd-pluginenabled: on -- -add: nsslapd-pluginArg7 -nsslapd-pluginArg7: manager -- -add: nsslapd-pluginArg8 -nsslapd-pluginArg8: secretary - diff --git a/ipa-server/ipa-install/share/schema_compat.uldif b/ipa-server/ipa-install/share/schema_compat.uldif deleted file mode 100644 index 71732c99..00000000 --- a/ipa-server/ipa-install/share/schema_compat.uldif +++ /dev/null @@ -1,50 +0,0 @@ -# -# Enable the Schema Compatibility plugin provided by slapi-nis. -# -# http://slapi-nis.fedorahosted.org/ -# -dn: cn=Schema Compatibility, cn=plugins, cn=config -default:objectclass: top -default:objectclass: nsSlapdPlugin -default:objectclass: extensibleObject -default:cn: Schema Compatibility -default:nsslapd-pluginpath: /usr/lib$LIBARCH/dirsrv/plugins/schemacompat-plugin.so -default:nsslapd-plugininitfunc: schema_compat_plugin_init -default:nsslapd-plugintype: object -default:nsslapd-pluginenabled: on -default:nsslapd-pluginid: schema-compat-plugin -default:nsslapd-pluginversion: 0.8 -default:nsslapd-pluginvendor: redhat.com -default:nsslapd-plugindescription: Schema Compatibility Plugin - -dn: cn=users, cn=Schema Compatibility, cn=plugins, cn=config -default:objectClass: top -default:objectClass: extensibleObject -default:cn: users -default:schema-compat-container-group: cn=compat, $SUFFIX -default:schema-compat-container-rdn: cn=users -default:schema-compat-search-base: cn=users, cn=accounts, $SUFFIX -default:schema-compat-search-filter: objectclass=posixAccount -default:schema-compat-entry-rdn: uid=%{uid} -default:schema-compat-entry-attribute: objectclass=posixAccount -default:schema-compat-entry-attribute: gecos=%{cn} -default:schema-compat-entry-attribute: cn=%{cn} -default:schema-compat-entry-attribute: uidNumber=%{uidNumber} -default:schema-compat-entry-attribute: gidNumber=%{gidNumber} -default:schema-compat-entry-attribute: loginShell=%{loginShell} -default:schema-compat-entry-attribute: homeDirectory=%{homeDirectory} - -dn: cn=groups, cn=Schema Compatibility, cn=plugins, cn=config -default:objectClass: top -default:objectClass: extensibleObject -default:cn: groups -default:schema-compat-container-group: cn=compat, $SUFFIX -default:schema-compat-container-rdn: cn=groups -default:schema-compat-search-base: cn=groups, cn=accounts, $SUFFIX -default:schema-compat-search-filter: objectclass=posixGroup -default:schema-compat-entry-rdn: cn=%{cn} -default:schema-compat-entry-attribute: objectclass=posixGroup -default:schema-compat-entry-attribute: gidNumber=%{gidNumber} -default:schema-compat-entry-attribute: memberUid=%{memberUid} -default:schema-compat-entry-attribute: memberUid=%deref("member","uid") -default:schema-compat-entry-attribute: memberUid=%referred("cn=users","memberOf","uid") diff --git a/ipa-server/ipa-install/share/unique-attributes.ldif b/ipa-server/ipa-install/share/unique-attributes.ldif deleted file mode 100644 index 82ec52d1..00000000 --- a/ipa-server/ipa-install/share/unique-attributes.ldif +++ /dev/null @@ -1,35 +0,0 @@ -dn: cn=krbPrincipalName uniqueness,cn=plugins,cn=config -changetype: add -objectClass: top -objectClass: nsSlapdPlugin -objectClass: extensibleObject -cn: krbPrincipalName uniqueness -nsslapd-pluginPath: libattr-unique-plugin -nsslapd-pluginInitfunc: NSUniqueAttr_Init -nsslapd-pluginType: preoperation -nsslapd-pluginEnabled: on -nsslapd-pluginarg0: krbPrincipalName -nsslapd-pluginarg1: $SUFFIX -nsslapd-plugin-depends-on-type: database -nsslapd-pluginId: NSUniqueAttr -nsslapd-pluginVersion: 1.1.0 -nsslapd-pluginVendor: Fedora Project -nsslapd-pluginDescription: Enforce unique attribute values - -#dn: cn=uid uniqueness,cn=plugins,cn=config -#objectClass: top -#objectClass: nsSlapdPlugin -#objectClass: extensibleObject -#cn: uid uniqueness -#nsslapd-pluginPath: libattr-unique-plugin -#nsslapd-pluginInitfunc: NSUniqueAttr_Init -#nsslapd-pluginType: preoperation -#nsslapd-pluginEnabled: on -#nsslapd-pluginarg0: uid -#nsslapd-pluginarg1: cn=accounts,$SUFFIX -#nsslapd-plugin-depends-on-type: database -#nsslapd-pluginId: NSUniqueAttr -#nsslapd-pluginVersion: 1.1.0 -#nsslapd-pluginVendor: Fedora Project -#nsslapd-pluginDescription: Enforce unique attribute values -# diff --git a/ipa-server/ipa-install/updates/Makefile.am b/ipa-server/ipa-install/updates/Makefile.am deleted file mode 100644 index 11d20ddd..00000000 --- a/ipa-server/ipa-install/updates/Makefile.am +++ /dev/null @@ -1,19 +0,0 @@ -NULL = - -appdir = $(IPA_DATA_DIR)/updates -app_DATA = \ - RFC4876.update \ - RFC2307bis.update \ - nss_ldap.update \ - winsync_index.update \ - replication.update \ - indices.update \ - $(NULL) - -EXTRA_DIST = \ - $(app_DATA) \ - $(NULL) - -MAINTAINERCLEANFILES = \ - *~ \ - Makefile.in diff --git a/ipa-server/ipa-install/updates/RFC2307bis.update b/ipa-server/ipa-install/updates/RFC2307bis.update deleted file mode 100644 index 1ddebc1a..00000000 --- a/ipa-server/ipa-install/updates/RFC2307bis.update +++ /dev/null @@ -1,65 +0,0 @@ -# -# Schema derived from RFC 2307bis: -# "An Approach for Using LDAP as a Network Information Service" -# -dn: cn=schema -add: attributeTypes: - ( 1.3.6.1.1.1.1.28 NAME 'nisPublickey' - DESC 'nisPublickey' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC2307bis' ) -add:attributeTypes: - ( 1.3.6.1.1.1.1.29 NAME 'nisSecretkey' - DESC 'nisSecretkey' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC2307bis' ) -add:attributeTypes: - ( 1.3.6.1.4.1.1.1.1.12 NAME 'nisDomain' - DESC 'NIS domain' - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC2307bis' ) -add:attributeTypes: - ( 2.16.840.1.113730.3.1.30 NAME 'mgrpRFC822MailMember' - DESC 'mgrpRFC822MailMember' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC2307bis' ) -add:attributeTypes: - ( 1.3.6.1.4.1.42.2.27.1.1.12 NAME 'nisNetIdUser' - DESC 'nisNetIdUser' - EQUALITY caseExactIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC2307bis' ) -add:attributeTypes: - ( 1.3.6.1.4.1.42.2.27.1.1.13 NAME 'nisNetIdGroup' - DESC 'nisNetIdGroup' - EQUALITY caseExactIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC2307bis' ) -add:attributeTypes: - ( 1.3.6.1.4.1.42.2.27.1.1.14 NAME 'nisNetIdHost' - DESC 'nisNetIdHost' - EQUALITY caseExactIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC2307bis' ) -add:objectClasses: - ( 1.3.6.1.1.1.2.14 NAME 'nisKeyObject' - DESC 'nisKeyObject' SUP top - MUST ( cn $ nisPublickey $ nisSecretkey ) - MAY ( uidNumber $ description ) ) -add:objectClasses: - ( 1.3.1.6.1.1.1.2.15 NAME 'nisDomainObject' - DESC 'nisDomainObject' SUP top AUXILIARY - MUST ( nisDomain ) ) -add:objectClasses: - ( 2.16.840.1.113730.3.2.4 NAME 'mailGroup' - DESC 'mailGroup' SUP top - MUST ( mail ) - MAY ( cn $ mgrpRFC822MailMember ) ) -add:objectClasses: - ( 1.3.6.1.4.1.42.2.27.1.2.6 NAME 'nisNetId' - DESC 'nisNetId' SUP top - MUST ( cn ) - MAY ( nisNetIdUser $ nisNetIdGroup $ nisNetIdHost ) ) diff --git a/ipa-server/ipa-install/updates/RFC4876.update b/ipa-server/ipa-install/updates/RFC4876.update deleted file mode 100644 index 5a372c20..00000000 --- a/ipa-server/ipa-install/updates/RFC4876.update +++ /dev/null @@ -1,146 +0,0 @@ -# -# Schema more or less verbatim from RFC 4876: -# "A Configuration Profile Schema for Lightweight Directory Access -# Protocol (LDAP)-Based Agents" -# -dn: cn=schema -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.0 NAME 'defaultServerList' - DESC 'List of default servers' - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.1 NAME 'defaultSearchBase' - DESC 'Default base for searches' - EQUALITY distinguishedNameMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.2 NAME 'preferredServerList' - DESC 'List of preferred servers' - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.3 NAME 'searchTimeLimit' - DESC 'Maximum time an agent or service allows for a - search to complete' - EQUALITY integerMatch - ORDERING integerOrderingMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.4 NAME 'bindTimeLimit' - DESC 'Maximum time an agent or service allows for a - bind operation to complete' - EQUALITY integerMatch - ORDERING integerOrderingMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.5 NAME 'followReferrals' - DESC 'An agent or service does or should follow referrals' - EQUALITY booleanMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.6 NAME 'authenticationMethod' - DESC 'Identifies the types of authentication methods either - used, required, or provided by a service or peer' - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.7 NAME 'profileTTL' - DESC 'Time to live, in seconds, before a profile is - considered stale' - EQUALITY integerMatch - ORDERING integerOrderingMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.9 NAME 'attributeMap' - DESC 'Attribute mappings used, required, or supported by an - agent or service' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.10 NAME 'credentialLevel' - DESC 'Identifies type of credentials either used, required, - or supported by an agent or service' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.11 NAME 'objectclassMap' - DESC 'Object class mappings used, required, or supported by - an agent or service' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.12 NAME 'defaultSearchScope' - DESC 'Default scope used when performing a search' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.13 NAME 'serviceCredentialLevel' - DESC 'Specifies the type of credentials either used, required, - or supported by a specific service' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.14 NAME 'serviceSearchDescriptor' - DESC 'Specifies search descriptors required, used, or - supported by a particular service or agent' - EQUALITY caseExactMatch - SUBSTR caseExactSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.15 NAME 'serviceAuthenticationMethod' - DESC 'Specifies types authentication methods either - used, required, or supported by a particular service' - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - X-ORIGIN 'RFC4876' ) -add:attributeTypes: - ( 1.3.6.1.4.1.11.1.3.1.1.16 NAME 'dereferenceAliases' - DESC 'Specifies if a service or agent either requires, - supports, or uses dereferencing of aliases.' - EQUALITY booleanMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 - SINGLE-VALUE - X-ORIGIN 'RFC4876' ) -add:objectClasses: - ( 1.3.6.1.4.1.11.1.3.1.2.5 NAME 'DUAConfigProfile' - SUP top STRUCTURAL - DESC 'Abstraction of a base configuration for a DUA' - MUST ( cn ) - MAY ( defaultServerList $ preferredServerList $ - defaultSearchBase $ defaultSearchScope $ - searchTimeLimit $ bindTimeLimit $ - credentialLevel $ authenticationMethod $ - followReferrals $ dereferenceAliases $ - serviceSearchDescriptor $ serviceCredentialLevel $ - serviceAuthenticationMethod $ objectclassMap $ - attributeMap $ profileTTL ) - X-ORIGIN 'RFC4876' ) diff --git a/ipa-server/ipa-install/updates/indices.update b/ipa-server/ipa-install/updates/indices.update deleted file mode 100644 index 3d0e42af..00000000 --- a/ipa-server/ipa-install/updates/indices.update +++ /dev/null @@ -1,18 +0,0 @@ -# -# Some nss_ldap implementations will always ask for memberuid so we must -# have an index for it. -# -dn: cn=memberuid,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -default:cn: memberuid -default:ObjectClass: top -default:ObjectClass: nsIndex -default:nsSystemIndex: false -default:nsIndexType: eq,pres - -dn: cn=memberof,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -default:cn: memberof -default:ObjectClass: top -default:ObjectClass: nsIndex -default:nsSystemIndex: false -default:nsIndexType: eq - diff --git a/ipa-server/ipa-install/updates/nss_ldap.update b/ipa-server/ipa-install/updates/nss_ldap.update deleted file mode 100644 index e8c1e00f..00000000 --- a/ipa-server/ipa-install/updates/nss_ldap.update +++ /dev/null @@ -1,33 +0,0 @@ -# -# Add profile for RFC 4876 agents (Solaris and HP/ux) -# - -# Update the top-level entry -dn: $SUFFIX -add:objectClass: domain -add:objectClass: domainRelatedObject -add:objectClass: nisDomainObject -add:associatedDomain: $DOMAIN -add:nisDomain: $DOMAIN - -# Add a place to store the nss_ldap default profile -dn: ou=profile,$SUFFIX -add: objectClass: top -add: objectClass: organizationalUnit -add: ou: profiles - -# The DUA profile. On Solaris one can run: -# ldap_client init ipa.example.com -dn: cn=default,ou=profile,$SUFFIX -default:ObjectClass: top -default:ObjectClass: DUAConfigProfile -default:defaultServerList: $FQDN -default:defaultSearchBase: $SUFFIX -default:authenticationMethod: none -default:searchTimeLimit: 15 -default:cn: default -default:serviceSearchDescriptor: passwd:cn=users,cn=accounts,$SUFFIX -default:serviceSearchDescriptor: group:cn=groups,cn=compat,$SUFFIX -default:bindTimeLimit: 5 -default:objectClassMap: shadow:shadowAccount=posixAccount -default:followReferrals:TRUE diff --git a/ipa-server/ipa-install/updates/replication.update b/ipa-server/ipa-install/updates/replication.update deleted file mode 100644 index 29823a6f..00000000 --- a/ipa-server/ipa-install/updates/replication.update +++ /dev/null @@ -1,9 +0,0 @@ -# -# Counter used to store the next replica id -# -# Start at 3 to avoid conflicts with v1.0 replica ids. The value itself -# isn't important but each replica needs a unique id. -dn: cn=replication,cn=etc,$SUFFIX -add: objectclass: nsDS5Replica -add: nsDS5ReplicaId: 3 -add: nsDS5ReplicaRoot: '$SUFFIX' diff --git a/ipa-server/ipa-install/updates/winsync_index.update b/ipa-server/ipa-install/updates/winsync_index.update deleted file mode 100644 index f24bdf8b..00000000 --- a/ipa-server/ipa-install/updates/winsync_index.update +++ /dev/null @@ -1,10 +0,0 @@ -# -# Make sure winsync attributes have the correct indexing -# - -dn: cn=ntUniqueId,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -only: nsIndexType: eq,pres - -dn: cn=ntUserDomainId,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config -only: nsIndexType: eq,pres - diff --git a/ipa-server/ipa-kpasswd/Makefile.am b/ipa-server/ipa-kpasswd/Makefile.am deleted file mode 100644 index 5f95fdef..00000000 --- a/ipa-server/ipa-kpasswd/Makefile.am +++ /dev/null @@ -1,58 +0,0 @@ -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/ipa-server/ipa-kpasswd/README b/ipa-server/ipa-kpasswd/README deleted file mode 100644 index c0a2767a..00000000 --- a/ipa-server/ipa-kpasswd/README +++ /dev/null @@ -1,2 +0,0 @@ -This is an implementation of the RFC3244 kpasswd protocol. -It is used to proxy password change operations to Directory Server. diff --git a/ipa-server/ipa-kpasswd/ipa_kpasswd.c b/ipa-server/ipa-kpasswd/ipa_kpasswd.c deleted file mode 100644 index f2d3490f..00000000 --- a/ipa-server/ipa-kpasswd/ipa_kpasswd.c +++ /dev/null @@ -1,1388 +0,0 @@ - -/* 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/ipa-server/ipa-kpasswd/ipa_kpasswd.init b/ipa-server/ipa-kpasswd/ipa_kpasswd.init deleted file mode 100644 index d7244bed..00000000 --- a/ipa-server/ipa-kpasswd/ipa_kpasswd.init +++ /dev/null @@ -1,83 +0,0 @@ -#!/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/ipa-server/ipa-ldap-updater b/ipa-server/ipa-ldap-updater deleted file mode 100755 index 28fb1a17..00000000 --- a/ipa-server/ipa-ldap-updater +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python -# Authors: Rob Crittenden -# -# Copyright (C) 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 -# - -# Documentation can be found at http://freeipa.org/page/LdapUpdate - -# TODO -# save undo files? - -import sys -try: - from optparse import OptionParser - from ipaserver import ipaldap - from ipa import entity, ipaerror, ipautil, config - from ipaserver import installutils - from ipaserver.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR - import ldap - import logging - import re - import krbV - import platform - import shlex - import time - import random -except ImportError: - print >> sys.stderr, """\ -There was a problem importing one of the required Python modules. The -error was: - - %s -""" % sys.exc_value - sys.exit(1) - -def parse_options(): - usage = "%prog [options] input_file(s)\n" - usage += "%prog [options]\n" - parser = OptionParser(usage=usage, formatter=config.IPAFormatter()) - - parser.add_option("-d", "--debug", action="store_true", dest="debug", - help="Display debugging information about the update(s)") - parser.add_option("-t", "--test", action="store_true", dest="test", - help="Run through the update without changing anything") - parser.add_option("-y", dest="password", - help="File containing the Directory Manager password") - - config.add_standard_options(parser) - options, args = parser.parse_args() - - config.init_config(options) - - return options, args - -def get_dirman_password(): - """Prompt the user for the Directory Manager password and verify its - correctness. - """ - password = installutils.read_password("Directory Manager", confirm=False, validate=False) - - return password - -def main(): - loglevel = logging.INFO - - options, args = parse_options() - if options.debug: - loglevel = logging.DEBUG - - logging.basicConfig(level=loglevel, - format='%(levelname)s %(message)s') - - dirman_password = "" - if options.password: - pw = ipautil.template_file(options.password, []) - dirman_password = pw.strip() - else: - dirman_password = get_dirman_password() - - ld = LDAPUpdate(dm_password=dirman_password, sub_dict={}, live_run=not options.test) - - files=[] - if len(args) < 1: - files = ld.get_all_files(UPDATES_DIR) - else: - files = args - - modified = ld.update(files) - - if modified and options.test: - return 2 - else: - return 0 - -try: - if __name__ == "__main__": - sys.exit(main()) -except BadSyntax, e: - print "There is a syntax error in this update file:" - print " %s" % e - sys.exit(1) -except RuntimeError, e: - print "%s" % e - sys.exit(1) -except SystemExit, e: - sys.exit(e) -except KeyboardInterrupt, e: - sys.exit(1) -except config.IPAConfigError, e: - print "An IPA server to update cannot be found. Has one been configured yet?" - print "The error was: %s" % e - sys.exit(1) diff --git a/ipa-server/ipa-slapi-plugins/Makefile.am b/ipa-server/ipa-slapi-plugins/Makefile.am deleted file mode 100644 index f316371c..00000000 --- a/ipa-server/ipa-slapi-plugins/Makefile.am +++ /dev/null @@ -1,16 +0,0 @@ -NULL = - -SUBDIRS = \ - ipa-pwd-extop \ - ipa-memberof \ - dna \ - ipa-winsync \ - $(NULL) - -EXTRA_DIST = \ - README \ - $(NULL) - -MAINTAINERCLEANFILES = \ - *~ \ - Makefile.in diff --git a/ipa-server/ipa-slapi-plugins/README b/ipa-server/ipa-slapi-plugins/README deleted file mode 100644 index e69de29b..00000000 diff --git a/ipa-server/ipa-slapi-plugins/dna/Makefile.am b/ipa-server/ipa-slapi-plugins/dna/Makefile.am deleted file mode 100644 index 4a54b8d5..00000000 --- a/ipa-server/ipa-slapi-plugins/dna/Makefile.am +++ /dev/null @@ -1,42 +0,0 @@ -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/ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif b/ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif deleted file mode 100644 index 02532b4e..00000000 --- a/ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif +++ /dev/null @@ -1,14 +0,0 @@ -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/ipa-server/ipa-slapi-plugins/dna/dna.c b/ipa-server/ipa-slapi-plugins/dna/dna.c deleted file mode 100644 index cb6a0629..00000000 --- a/ipa-server/ipa-slapi-plugins/dna/dna.c +++ /dev/null @@ -1,1462 +0,0 @@ -/** 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/ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile.am b/ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile.am deleted file mode 100644 index d0ac7f93..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile.am +++ /dev/null @@ -1,43 +0,0 @@ -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/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c b/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c deleted file mode 100644 index 3baf2f6c..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c +++ /dev/null @@ -1,2244 +0,0 @@ -/** 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/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h b/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h deleted file mode 100644 index 3e7b5cf4..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h +++ /dev/null @@ -1,100 +0,0 @@ -/** 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/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c b/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c deleted file mode 100644 index b2bd374a..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c +++ /dev/null @@ -1,312 +0,0 @@ -/** 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/ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif b/ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif deleted file mode 100644 index 1441afea..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif +++ /dev/null @@ -1,14 +0,0 @@ -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/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am deleted file mode 100644 index 540646f0..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am +++ /dev/null @@ -1,46 +0,0 @@ -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/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README deleted file mode 100644 index e69de29b..00000000 diff --git a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c deleted file mode 100644 index 24acc887..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c +++ /dev/null @@ -1,4058 +0,0 @@ -/** 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/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif deleted file mode 100644 index e31a8e79..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif +++ /dev/null @@ -1,16 +0,0 @@ -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/ipa-server/ipa-slapi-plugins/ipa-winsync/Makefile.am b/ipa-server/ipa-slapi-plugins/ipa-winsync/Makefile.am deleted file mode 100644 index 94bc2dc6..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-winsync/Makefile.am +++ /dev/null @@ -1,43 +0,0 @@ -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/ipa-server/ipa-slapi-plugins/ipa-winsync/README b/ipa-server/ipa-slapi-plugins/ipa-winsync/README deleted file mode 100644 index e69de29b..00000000 diff --git a/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif b/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif deleted file mode 100644 index 5b5c56ac..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif +++ /dev/null @@ -1,27 +0,0 @@ -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/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c b/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c deleted file mode 100644 index 45efa6df..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c +++ /dev/null @@ -1,975 +0,0 @@ -/** 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/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c b/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c deleted file mode 100644 index 9ee8805b..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c +++ /dev/null @@ -1,1177 +0,0 @@ -/** 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/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h b/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h deleted file mode 100644 index 58a9a6c4..00000000 --- a/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h +++ /dev/null @@ -1,160 +0,0 @@ -/** 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 */ diff --git a/ipa-server/ipa-upgradeconfig b/ipa-server/ipa-upgradeconfig deleted file mode 100644 index 48c4117d..00000000 --- a/ipa-server/ipa-upgradeconfig +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/python -# -# Upgrade configuration files to a newer template. - -import sys -try: - from ipa import ipautil - import krbV - import re - import os - import shutil - import fileinput -except ImportError: - print >> sys.stderr, """\ -There was a problem importing one of the required Python modules. The -error was: - - %s -""" % sys.exc_value - sys.exit(1) - -def backup_file(filename, ext): - """Make a backup of filename using ext as the extension. Do not overwrite - previous backups.""" - if not os.path.isabs(filename): - raise ValueError("Absolute path required") - - backupfile = filename + ".bak" - (reldir, file) = os.path.split(filename) - - while os.path.exists(backupfile): - backupfile = backupfile + "." + str(ext) - - shutil.copy2(filename, backupfile) - -def update_conf(sub_dict, filename, template_filename): - template = ipautil.template_file(template_filename, sub_dict) - fd = open(filename, "w") - fd.write(template) - fd.close() - -def find_hostname(): - """Find the hostname currently configured in ipa-rewrite.conf""" - filename="/etc/httpd/conf.d/ipa-rewrite.conf" - pattern = "^[\s#]*.*https:\/\/([A-Za-z0-9\.\-]*)\/.*" - p = re.compile(pattern) - for line in fileinput.input(filename): - if p.search(line): - fileinput.close() - return p.search(line).group(1) - fileinput.close() - - return None - -def find_version(filename): - """Find the version of a configuration file""" - if os.path.exists(filename): - pattern = "^[\s#]*VERSION\s+([0-9]+)\s+.*" - p = re.compile(pattern) - for line in fileinput.input(filename): - if p.search(line): - fileinput.close() - return p.search(line).group(1) - fileinput.close() - - # no VERSION found - return 0 - else: - return -1 - -def upgrade(sub_dict, filename, template): - old = int(find_version(filename)) - new = int(find_version(template)) - - if old < 0: - print "%s not found." % filename - sys.exit(1) - - if new < 0: - print "%s not found." % template - - if old < new: - backup_file(filename, new) - update_conf(sub_dict, filename, template) - print "Upgraded %s to version %d" % (filename, new) - -def check_certs(realm_name): - """Check ca.crt is in the right place, and try to fix if not""" - if not os.path.exists("/usr/share/ipa/html/ca.crt"): - ca_file = "/etc/dirsrv/slapd-" + ("-".join(realm_name.split("."))) + "/cacert.asc" - if os.path.exists(ca_file): - shutil.copyfile(ca_file, "/usr/share/ipa/html/ca.crt") - else: - print "Missing Certification Authority file." - print "You should place a copy of the CA certificate in /usr/share/ipa/html/ca.crt" - -def main(): - try: - krbctx = krbV.default_context() - except krbV.Krb5Error, e: - print "Unable to get default kerberos realm: %s" % e[1] - sys.exit(1) - - try: - check_certs(krbctx.default_realm) - except Error, e: - print "Failed to check CA certificate: %s" % e - - try: - fqdn = find_hostname() - except IOError: - # ipa-rewrite.conf doesn't exist, nothing to do - sys.exit(0) - - if fqdn is None: - print "Unable to determine hostname from ipa-rewrite.conf" - sys.exit(1) - - sub_dict = { "REALM" : krbctx.default_realm, "FQDN": fqdn } - - upgrade(sub_dict, "/etc/httpd/conf.d/ipa.conf", ipautil.SHARE_DIR + "ipa.conf") - upgrade(sub_dict, "/etc/httpd/conf.d/ipa-rewrite.conf", ipautil.SHARE_DIR + "ipa-rewrite.conf") - -try: - if __name__ == "__main__": - sys.exit(main()) -except SystemExit, e: - sys.exit(e) -except KeyboardInterrupt, e: - sys.exit(1) diff --git a/ipa-server/ipaserver/Makefile.am b/ipa-server/ipaserver/Makefile.am deleted file mode 100644 index 999dcf24..00000000 --- a/ipa-server/ipaserver/Makefile.am +++ /dev/null @@ -1,24 +0,0 @@ -NULL = - -appdir = $(pythondir)/ipaserver -app_PYTHON = \ - __init__.py \ - bindinstance.py \ - dsinstance.py \ - ipaldap.py \ - krbinstance.py \ - httpinstance.py \ - ntpinstance.py \ - service.py \ - installutils.py \ - replication.py \ - certs.py \ - ldapupdate.py \ - $(NULL) - -EXTRA_DIST = \ - $(NULL) - -MAINTAINERCLEANFILES = \ - *~ \ - Makefile.in diff --git a/ipa-server/ipaserver/__init__.py b/ipa-server/ipaserver/__init__.py deleted file mode 100644 index ef86f9ec..00000000 --- a/ipa-server/ipaserver/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Authors: Karl MacMillan -# see inline -# -# Copyright (C) 2007 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 or later -# -# 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 -# - -__all__ = ["dsinstance", "krbinstance"] diff --git a/ipa-server/ipaserver/bindinstance.py b/ipa-server/ipaserver/bindinstance.py deleted file mode 100644 index 5badf860..00000000 --- a/ipa-server/ipaserver/bindinstance.py +++ /dev/null @@ -1,156 +0,0 @@ -# Authors: Simo Sorce -# -# Copyright (C) 2007 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 -# - -import string -import tempfile -import shutil -import os -import socket -import logging - -import service -from ipa import sysrestore -from ipa import ipautil - -def check_inst(): - # So far this file is always present in both RHEL5 and Fedora if all the necessary - # bind packages are installed (RHEL5 requires also the pkg: caching-nameserver) - if not os.path.exists('/etc/named.rfc1912.zones'): - return False - - return True - -class BindInstance(service.Service): - def __init__(self, fstore=None): - service.Service.__init__(self, "named") - self.fqdn = None - self.domain = None - self.host = None - self.ip_address = None - self.realm = None - self.sub_dict = None - - if fstore: - self.fstore = fstore - else: - self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') - - def setup(self, fqdn, ip_address, realm_name, domain_name): - self.fqdn = fqdn - self.ip_address = ip_address - self.realm = realm_name - self.domain = domain_name - self.host = fqdn.split(".")[0] - - self.__setup_sub_dict() - - def create_sample_bind_zone(self): - bind_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.zone.db.template", self.sub_dict) - [bind_fd, bind_name] = tempfile.mkstemp(".db","sample.zone.") - os.write(bind_fd, bind_txt) - os.close(bind_fd) - print "Sample zone file for bind has been created in "+bind_name - - def create_instance(self): - - try: - self.stop() - except: - pass - - self.step("Setting up our zone", self.__setup_zone) - self.step("Setting up named.conf", self.__setup_named_conf) - - self.step("restarting named", self.__start) - self.step("configuring named to start on boot", self.__enable) - - self.step("Changing resolv.conf to point to ourselves", self.__setup_resolv_conf) - self.start_creation("Configuring bind:") - - def __start(self): - try: - self.backup_state("running", self.is_running()) - self.restart() - except: - print "named service failed to start" - - def __enable(self): - self.backup_state("enabled", self.is_running()) - self.chkconfig_on() - - def __setup_sub_dict(self): - self.sub_dict = dict(FQDN=self.fqdn, - IP=self.ip_address, - DOMAIN=self.domain, - HOST=self.host, - REALM=self.realm) - - def __setup_zone(self): - self.backup_state("domain", self.domain) - zone_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.zone.db.template", self.sub_dict) - self.fstore.backup_file('/var/named/'+self.domain+'.zone.db') - zone_fd = open('/var/named/'+self.domain+'.zone.db', 'w') - zone_fd.write(zone_txt) - zone_fd.close() - - def __setup_named_conf(self): - self.fstore.backup_file('/etc/named.conf') - named_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.named.conf.template", self.sub_dict) - named_fd = open('/etc/named.conf', 'w') - named_fd.seek(0) - named_fd.truncate(0) - named_fd.write(named_txt) - named_fd.close() - - def __setup_resolv_conf(self): - self.fstore.backup_file('/etc/resolv.conf') - resolv_txt = "search "+self.domain+"\nnameserver "+self.ip_address+"\n" - resolv_fd = open('/etc/resolv.conf', 'w') - resolv_fd.seek(0) - resolv_fd.truncate(0) - resolv_fd.write(resolv_txt) - resolv_fd.close() - - def uninstall(self): - running = self.restore_state("running") - enabled = self.restore_state("enabled") - domain = self.restore_state("domain") - - if not running is None: - self.stop() - - if not domain is None: - try: - self.fstore.restore_file(os.path.join ("/var/named/", domain + ".zone.db")) - except ValueError, error: - logging.debug(error) - pass - - for f in ["/etc/named.conf", "/etc/resolv.conf"]: - try: - self.fstore.restore_file(f) - except ValueError, error: - logging.debug(error) - pass - - if not enabled is None and not enabled: - self.chkconfig_off() - - if not running is None and running: - self.start() diff --git a/ipa-server/ipaserver/certs.py b/ipa-server/ipaserver/certs.py deleted file mode 100644 index 8cb1d088..00000000 --- a/ipa-server/ipaserver/certs.py +++ /dev/null @@ -1,424 +0,0 @@ -# Authors: Karl MacMillan -# -# Copyright (C) 2007 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 -# - -import os, stat, subprocess, re -import sha -import errno -import tempfile -import shutil - -from ipa import sysrestore -from ipa import ipautil - -CA_SERIALNO="/var/lib/ipa/ca_serialno" - -class CertDB(object): - def __init__(self, dir, fstore=None): - self.secdir = dir - - self.noise_fname = self.secdir + "/noise.txt" - self.passwd_fname = self.secdir + "/pwdfile.txt" - self.certdb_fname = self.secdir + "/cert8.db" - self.keydb_fname = self.secdir + "/key3.db" - self.secmod_fname = self.secdir + "/secmod.db" - self.cacert_fname = self.secdir + "/cacert.asc" - self.pk12_fname = self.secdir + "/cacert.p12" - self.pin_fname = self.secdir + "/pin.txt" - self.reqdir = tempfile.mkdtemp('', 'ipa-', '/var/lib/ipa') - self.certreq_fname = self.reqdir + "/tmpcertreq" - self.certder_fname = self.reqdir + "/tmpcert.der" - - # Making this a starting value that will generate - # unique values for the current DB is the - # responsibility of the caller for now. In the - # future we might automatically determine this - # for a given db. - self.cur_serial = -1 - - self.cacert_name = "CA certificate" - self.valid_months = "120" - self.keysize = "1024" - - # We are going to set the owner of all of the cert - # files to the owner of the containing directory - # instead of that of the process. This works when - # this is called by root for a daemon that runs as - # a normal user - mode = os.stat(self.secdir) - self.uid = mode[stat.ST_UID] - self.gid = mode[stat.ST_GID] - - if fstore: - self.fstore = fstore - else: - self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') - - def __del__(self): - shutil.rmtree(self.reqdir, ignore_errors=True) - - def set_serial_from_pkcs12(self): - """A CA cert was loaded from a PKCS#12 file. Set up our serial file""" - - self.cur_serial = self.find_cacert_serial() - try: - f=open(CA_SERIALNO,"w") - f.write(str(self.cur_serial)) - f.close() - except IOError, e: - raise RuntimeError("Unable to increment serial number: %s" % str(e)) - - def next_serial(self): - try: - f=open(CA_SERIALNO,"r") - r = f.readline() - try: - self.cur_serial = int(r) + 1 - except ValueError: - raise RuntimeError("The value in %s is not an integer" % CA_SERIALNO) - f.close() - except IOError, e: - if e.errno == errno.ENOENT: - self.cur_serial = 1000 - f=open(CA_SERIALNO,"w") - f.write(str(self.cur_serial)) - f.close() - else: - raise RuntimeError("Unable to determine serial number: %s" % str(e)) - - try: - f=open(CA_SERIALNO,"w") - f.write(str(self.cur_serial)) - f.close() - except IOError, e: - raise RuntimeError("Unable to increment serial number: %s" % str(e)) - - return str(self.cur_serial) - - def set_perms(self, fname, write=False): - os.chown(fname, self.uid, self.gid) - perms = stat.S_IRUSR - if write: - perms |= stat.S_IWUSR - os.chmod(fname, perms) - - def gen_password(self): - return sha.sha(ipautil.ipa_generate_password()).hexdigest() - - def run_certutil(self, args, stdin=None): - new_args = ["/usr/bin/certutil", "-d", self.secdir] - new_args = new_args + args - return ipautil.run(new_args, stdin) - - def run_signtool(self, args, stdin=None): - new_args = ["/usr/bin/signtool", "-d", self.secdir] - new_args = new_args + args - ipautil.run(new_args, stdin) - - def create_noise_file(self): - ipautil.backup_file(self.noise_fname) - f = open(self.noise_fname, "w") - f.write(self.gen_password()) - self.set_perms(self.noise_fname) - - def create_passwd_file(self, passwd=None): - ipautil.backup_file(self.passwd_fname) - f = open(self.passwd_fname, "w") - if passwd is not None: - f.write("%s\n" % passwd) - else: - f.write(self.gen_password()) - f.close() - self.set_perms(self.passwd_fname) - - def create_certdbs(self): - ipautil.backup_file(self.certdb_fname) - ipautil.backup_file(self.keydb_fname) - ipautil.backup_file(self.secmod_fname) - self.run_certutil(["-N", - "-f", self.passwd_fname]) - self.set_perms(self.passwd_fname, write=True) - - def create_ca_cert(self): - # Generate the encryption key - self.run_certutil(["-G", "-z", self.noise_fname, "-f", self.passwd_fname]) - # Generate the self-signed cert - self.run_certutil(["-S", "-n", self.cacert_name, - "-s", "cn=IPA Test Certificate Authority", - "-x", - "-t", "CT,,C", - "-m", self.next_serial(), - "-v", self.valid_months, - "-z", self.noise_fname, - "-f", self.passwd_fname]) - - def export_ca_cert(self, nickname, create_pkcs12=False): - """create_pkcs12 tells us whether we should create a PKCS#12 file - of the CA or not. If we are running on a replica then we won't - have the private key to make a PKCS#12 file so we don't need to - do that step.""" - # export the CA cert for use with other apps - ipautil.backup_file(self.cacert_fname) - self.run_certutil(["-L", "-n", nickname, - "-a", - "-o", self.cacert_fname]) - self.set_perms(self.cacert_fname) - if create_pkcs12: - ipautil.backup_file(self.pk12_fname) - ipautil.run(["/usr/bin/pk12util", "-d", self.secdir, - "-o", self.pk12_fname, - "-n", self.cacert_name, - "-w", self.passwd_fname, - "-k", self.passwd_fname]) - self.set_perms(self.pk12_fname) - - def load_cacert(self, cacert_fname): - self.run_certutil(["-A", "-n", self.cacert_name, - "-t", "CT,,C", - "-a", - "-i", cacert_fname]) - - def find_cacert_serial(self): - (out,err) = self.run_certutil(["-L", "-n", self.cacert_name]) - data = out.split('\n') - for line in data: - x = re.match(r'\s+Serial Number: (\d+) .*', line) - if x is not None: - return x.group(1) - - raise RuntimeError("Unable to find serial number") - - def create_server_cert(self, nickname, name, other_certdb=None): - cdb = other_certdb - if not cdb: - cdb = self - self.request_cert(name) - cdb.issue_server_cert(self.certreq_fname, self.certder_fname) - self.add_cert(self.certder_fname, nickname) - os.unlink(self.certreq_fname) - os.unlink(self.certder_fname) - - def create_signing_cert(self, nickname, name, other_certdb=None): - cdb = other_certdb - if not cdb: - cdb = self - self.request_cert(name) - cdb.issue_signing_cert(self.certreq_fname, self.certder_fname) - self.add_cert(self.certder_fname, nickname) - os.unlink(self.certreq_fname) - os.unlink(self.certder_fname) - - def request_cert(self, name): - self.run_certutil(["-R", "-s", name, - "-o", self.certreq_fname, - "-g", self.keysize, - "-z", self.noise_fname, - "-f", self.passwd_fname]) - - def issue_server_cert(self, certreq_fname, cert_fname): - p = subprocess.Popen(["/usr/bin/certutil", - "-d", self.secdir, - "-C", "-c", self.cacert_name, - "-i", certreq_fname, - "-o", cert_fname, - "-m", self.next_serial(), - "-v", self.valid_months, - "-f", self.passwd_fname, - "-1", "-5"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - - # Bah - this sucks, but I guess it isn't possible to fully - # control this with command line arguments. - # - # What this is requesting is: - # -1 (Create key usage extension) - # 2 - Key encipherment - # 9 - done - # n - not critical - # - # -5 (Create netscape cert type extension) - # 1 - SSL Server - # 9 - done - # n - not critical - p.stdin.write("2\n9\nn\n1\n9\nn\n") - p.wait() - - def issue_signing_cert(self, certreq_fname, cert_fname): - p = subprocess.Popen(["/usr/bin/certutil", - "-d", self.secdir, - "-C", "-c", self.cacert_name, - "-i", certreq_fname, - "-o", cert_fname, - "-m", self.next_serial(), - "-v", self.valid_months, - "-f", self.passwd_fname, - "-1", "-5"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - - # Bah - this sucks, but I guess it isn't possible to fully - # control this with command line arguments. - # - # What this is requesting is: - # -1 (Create key usage extension) - # 0 - Digital Signature - # 5 - Cert signing key - # 9 - done - # n - not critical - # - # -5 (Create netscape cert type extension) - # 3 - Object Signing - # 9 - done - # n - not critical - p.stdin.write("0\n5\n9\nn\n3\n9\nn\n") - p.wait() - - def add_cert(self, cert_fname, nickname): - self.run_certutil(["-A", "-n", nickname, - "-t", "u,u,u", - "-i", cert_fname, - "-f", cert_fname]) - - def create_pin_file(self): - ipautil.backup_file(self.pin_fname) - f = open(self.pin_fname, "w") - f.write("Internal (Software) Token:") - pwd = open(self.passwd_fname) - f.write(pwd.read()) - f.close() - self.set_perms(self.pin_fname) - - def find_root_cert(self, nickname): - p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir, - "-O", "-n", nickname], stdout=subprocess.PIPE) - - chain = p.stdout.read() - chain = chain.split("\n") - - root_nickname = re.match('\ *"(.*)".*', chain[0]).groups()[0] - - return root_nickname - - def trust_root_cert(self, nickname): - root_nickname = self.find_root_cert(nickname) - - self.run_certutil(["-M", "-n", root_nickname, - "-t", "CT,CT,"]) - - def find_server_certs(self): - p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir, - "-L"], stdout=subprocess.PIPE) - - certs = p.stdout.read() - - certs = certs.split("\n") - - server_certs = [] - - for cert in certs: - fields = cert.split() - if not len(fields): - continue - flags = fields[-1] - if 'u' in flags: - name = " ".join(fields[0:-1]) - # NSS 3.12 added a header to the certutil output - if name == "Certificate Nickname Trust": - continue - server_certs.append((name, flags)) - - return server_certs - - def import_pkcs12(self, pkcs12_fname, passwd_fname=None): - args = ["/usr/bin/pk12util", "-d", self.secdir, - "-i", pkcs12_fname, - "-k", self.passwd_fname] - if passwd_fname: - args = args + ["-w", passwd_fname] - try: - ipautil.run(args) - except ipautil.CalledProcessError, e: - if e.returncode == 17: - raise RuntimeError("incorrect password") - else: - raise RuntimeError("unknown error import pkcs#12 file") - - def export_pkcs12(self, pkcs12_fname, pkcs12_pwd_fname, nickname="CA certificate"): - ipautil.run(["/usr/bin/pk12util", "-d", self.secdir, - "-o", pkcs12_fname, - "-n", nickname, - "-k", self.passwd_fname, - "-w", pkcs12_pwd_fname]) - - def create_self_signed(self, passwd=None): - self.create_noise_file() - self.create_passwd_file(passwd) - self.create_certdbs() - self.create_ca_cert() - self.export_ca_cert(self.cacert_name, True) - self.create_pin_file() - - def create_from_cacert(self, cacert_fname, passwd=""): - self.create_noise_file() - self.create_passwd_file(passwd) - self.create_certdbs() - self.load_cacert(cacert_fname) - - def create_from_pkcs12(self, pkcs12_fname, pkcs12_pwd_fname, passwd=None): - """Create a new NSS database using the certificates in a PKCS#12 file. - - pkcs12_fname: the filename of the PKCS#12 file - pkcs12_pwd_fname: the file containing the pin for the PKCS#12 file - nickname: the nickname/friendly-name of the cert we are loading - passwd: The password to use for the new NSS database we are creating - """ - self.create_noise_file() - self.create_passwd_file(passwd) - self.create_certdbs() - self.import_pkcs12(pkcs12_fname, pkcs12_pwd_fname) - server_certs = self.find_server_certs() - if len(server_certs) == 0: - raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_fname) - - # We only handle one server cert - nickname = server_certs[0][0] - - self.cacert_name = self.find_root_cert(nickname) - self.trust_root_cert(nickname) - self.create_pin_file() - self.export_ca_cert(self.cacert_name, False) - - # This file implies that we have our own self-signed CA. Ensure - # that it no longer exists (from previous installs, for example). - try: - os.remove(CA_SERIALNO) - except: - pass - - def backup_files(self): - self.fstore.backup_file(self.noise_fname) - self.fstore.backup_file(self.passwd_fname) - self.fstore.backup_file(self.certdb_fname) - self.fstore.backup_file(self.keydb_fname) - self.fstore.backup_file(self.secmod_fname) - self.fstore.backup_file(self.cacert_fname) - self.fstore.backup_file(self.pk12_fname) - self.fstore.backup_file(self.pin_fname) - self.fstore.backup_file(self.certreq_fname) - self.fstore.backup_file(self.certder_fname) diff --git a/ipa-server/ipaserver/dsinstance.py b/ipa-server/ipaserver/dsinstance.py deleted file mode 100644 index e9826bf6..00000000 --- a/ipa-server/ipaserver/dsinstance.py +++ /dev/null @@ -1,479 +0,0 @@ -# Authors: Karl MacMillan -# Simo Sorce -# -# Copyright (C) 2007 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 -# - -import shutil -import logging -import pwd -import glob -import sys -import os -import re -import time -import tempfile -import stat - -from ipa import ipautil - -import service -import installutils -import certs -import ipaldap, ldap -from ipaserver import ldapupdate - -SERVER_ROOT_64 = "/usr/lib64/dirsrv" -SERVER_ROOT_32 = "/usr/lib/dirsrv" - -def realm_to_suffix(realm_name): - s = realm_name.split(".") - terms = ["dc=" + x.lower() for x in s] - return ",".join(terms) - -def find_server_root(): - if ipautil.dir_exists(SERVER_ROOT_64): - return SERVER_ROOT_64 - else: - return SERVER_ROOT_32 - -def realm_to_serverid(realm_name): - return "-".join(realm_name.split(".")) - -def config_dirname(serverid): - return "/etc/dirsrv/slapd-" + serverid + "/" - -def schema_dirname(serverid): - return config_dirname(serverid) + "/schema/" - -def erase_ds_instance_data(serverid): - try: - shutil.rmtree("/etc/dirsrv/slapd-%s" % serverid) - except: - pass - try: - shutil.rmtree("/usr/lib/dirsrv/slapd-%s" % serverid) - except: - pass - try: - shutil.rmtree("/usr/lib64/dirsrv/slapd-%s" % serverid) - except: - pass - try: - shutil.rmtree("/var/lib/dirsrv/slapd-%s" % serverid) - except: - pass - try: - shutil.rmtree("/var/lock/dirsrv/slapd-%s" % serverid) - except: - pass -# try: -# shutil.rmtree("/var/log/dirsrv/slapd-%s" % serverid) -# except: -# pass - -def check_existing_installation(): - dirs = glob.glob("/etc/dirsrv/slapd-*") - if not dirs: - return [] - - serverids = [] - for d in dirs: - serverids.append(os.path.basename(d).split("slapd-", 1)[1]) - - return serverids - -def check_ports(): - ds_unsecure = installutils.port_available(389) - ds_secure = installutils.port_available(636) - return (ds_unsecure, ds_secure) - -def is_ds_running(): - """The DS init script always returns 0 when requesting status so it cannot - be used to determine if the server is running. We have to look at the - output. - """ - ret = True - try: - (sout, serr) = ipautil.run(["/sbin/service", "dirsrv", "status"]) - if sout.find("is stopped") >= 0: - ret = False - except ipautil.CalledProcessError: - ret = False - return ret - - -INF_TEMPLATE = """ -[General] -FullMachineName= $FQHN -SuiteSpotUserID= $USER -ServerRoot= $SERVER_ROOT -[slapd] -ServerPort= 389 -ServerIdentifier= $SERVERID -Suffix= $SUFFIX -RootDN= cn=Directory Manager -RootDNPwd= $PASSWORD -InstallLdifFile= /var/lib/dirsrv/boot.ldif -""" - -BASE_TEMPLATE = """ -dn: $SUFFIX -objectClass: top -objectClass: domain -objectClass: pilotObject -dc: $BASEDC -info: IPA V1.0 -""" - -class DsInstance(service.Service): - def __init__(self, realm_name=None, domain_name=None, dm_password=None): - service.Service.__init__(self, "dirsrv") - self.realm_name = realm_name - self.dm_password = dm_password - self.sub_dict = None - self.domain = domain_name - self.serverid = None - self.host_name = None - self.pkcs12_info = None - self.ds_user = None - if realm_name: - self.suffix = realm_to_suffix(self.realm_name) - self.__setup_sub_dict() - else: - self.suffix = None - - def create_instance(self, ds_user, realm_name, host_name, domain_name, dm_password, pkcs12_info=None): - self.ds_user = ds_user - self.realm_name = realm_name.upper() - self.serverid = realm_to_serverid(self.realm_name) - self.suffix = realm_to_suffix(self.realm_name) - self.host_name = host_name - self.dm_password = dm_password - self.domain = domain_name - self.pkcs12_info = pkcs12_info - self.__setup_sub_dict() - - self.step("creating directory server user", self.__create_ds_user) - self.step("creating directory server instance", self.__create_instance) - self.step("adding default schema", self.__add_default_schemas) - self.step("enabling memberof plugin", self.__add_memberof_module) - self.step("enabling referential integrity plugin", self.__add_referint_module) - self.step("enabling distributed numeric assignment plugin", self.__add_dna_module) - self.step("enabling winsync plugin", self.__add_winsync_module) - self.step("configuring uniqueness plugin", self.__set_unique_attrs) - self.step("creating indices", self.__create_indices) - self.step("configuring ssl for ds instance", self.__enable_ssl) - self.step("configuring certmap.conf", self.__certmap_conf) - self.step("restarting directory server", self.__restart_instance) - self.step("adding default layout", self.__add_default_layout) - self.step("configuring Posix uid/gid generation as first master", - self.__config_uidgid_gen_first_master) - self.step("adding master entry as first master", - self.__add_master_entry_first_master) - self.step("initializing group membership", - self.init_memberof) - - self.step("configuring directory to start on boot", self.__enable) - - self.start_creation("Configuring directory server:") - - def __enable(self): - self.backup_state("enabled", self.is_enabled()) - self.chkconfig_on() - - def __setup_sub_dict(self): - server_root = find_server_root() - self.sub_dict = dict(FQHN=self.host_name, SERVERID=self.serverid, - PASSWORD=self.dm_password, SUFFIX=self.suffix.lower(), - REALM=self.realm_name, USER=self.ds_user, - SERVER_ROOT=server_root, DOMAIN=self.domain, - TIME=int(time.time())) - - def __create_ds_user(self): - user_exists = True - try: - pwd.getpwnam(self.ds_user) - logging.debug("ds user %s exists" % self.ds_user) - except KeyError: - user_exists = False - logging.debug("adding ds user %s" % self.ds_user) - args = ["/usr/sbin/useradd", "-c", "DS System User", "-d", "/var/lib/dirsrv", "-M", "-r", "-s", "/sbin/nologin", self.ds_user] - try: - ipautil.run(args) - logging.debug("done adding user") - except ipautil.CalledProcessError, e: - logging.critical("failed to add user %s" % e) - - self.backup_state("user", self.ds_user) - self.backup_state("user_exists", user_exists) - - def __create_instance(self): - self.backup_state("running", is_ds_running()) - self.backup_state("serverid", self.serverid) - - self.sub_dict['BASEDC'] = self.realm_name.split('.')[0].lower() - base_txt = ipautil.template_str(BASE_TEMPLATE, self.sub_dict) - logging.debug(base_txt) - base_fd = file("/var/lib/dirsrv/boot.ldif", "w") - base_fd.write(base_txt) - base_fd.flush() - base_fd.close() - - inf_txt = ipautil.template_str(INF_TEMPLATE, self.sub_dict) - logging.debug("writing inf template") - inf_fd = ipautil.write_tmp_file(inf_txt) - inf_txt = re.sub(r"RootDNPwd=.*\n", "", inf_txt) - logging.debug(inf_txt) - if ipautil.file_exists("/usr/sbin/setup-ds.pl"): - args = ["/usr/sbin/setup-ds.pl", "--silent", "--logfile", "-", "-f", inf_fd.name] - logging.debug("calling setup-ds.pl") - else: - args = ["/usr/bin/ds_newinst.pl", inf_fd.name] - logging.debug("calling ds_newinst.pl") - try: - ipautil.run(args) - logging.debug("completed creating ds instance") - except ipautil.CalledProcessError, e: - logging.critical("failed to restart ds instance %s" % e) - logging.debug("restarting ds instance") - try: - self.restart() - logging.debug("done restarting ds instance") - except ipautil.CalledProcessError, e: - print "failed to restart ds instance", e - logging.debug("failed to restart ds instance %s" % e) - inf_fd.close() - os.remove("/var/lib/dirsrv/boot.ldif") - - def __add_default_schemas(self): - shutil.copyfile(ipautil.SHARE_DIR + "60kerberos.ldif", - schema_dirname(self.serverid) + "60kerberos.ldif") - shutil.copyfile(ipautil.SHARE_DIR + "60samba.ldif", - schema_dirname(self.serverid) + "60samba.ldif") - shutil.copyfile(ipautil.SHARE_DIR + "60radius.ldif", - schema_dirname(self.serverid) + "60radius.ldif") - shutil.copyfile(ipautil.SHARE_DIR + "60ipaconfig.ldif", - schema_dirname(self.serverid) + "60ipaconfig.ldif") - - def __restart_instance(self): - try: - self.restart() - if not is_ds_running(): - logging.critical("Failed to restart the directory server. See the installation log for details.") - sys.exit(1) - except SystemExit, e: - raise e - except Exception, e: - # TODO: roll back here? - logging.critical("Failed to restart the directory server. See the installation log for details.") - - def __ldap_mod(self, ldif, sub_dict = None): - fd = None - path = ipautil.SHARE_DIR + ldif - - if not sub_dict is None: - txt = ipautil.template_file(path, sub_dict) - fd = ipautil.write_tmp_file(txt) - path = fd.name - - [pw_fd, pw_name] = tempfile.mkstemp() - os.write(pw_fd, self.dm_password) - os.close(pw_fd) - - args = ["/usr/bin/ldapmodify", "-h", "127.0.0.1", "-xv", - "-D", "cn=Directory Manager", "-y", pw_name, "-f", path] - - try: - try: - ipautil.run(args) - except ipautil.CalledProcessError, e: - logging.critical("Failed to load %s: %s" % (ldif, str(e))) - finally: - os.remove(pw_name) - - if not fd is None: - fd.close() - - def __add_memberof_module(self): - self.__ldap_mod("memberof-conf.ldif") - - def init_memberof(self): - self.__ldap_mod("memberof-task.ldif", self.sub_dict) - - def apply_updates(self): - ld = ldapupdate.LDAPUpdate(dm_password=self.dm_password) - files = ld.get_all_files(ldapupdate.UPDATES_DIR) - ld.update(files) - - def __add_referint_module(self): - self.__ldap_mod("referint-conf.ldif") - - def __add_dna_module(self): - self.__ldap_mod("dna-conf.ldif") - - def __set_unique_attrs(self): - self.__ldap_mod("unique-attributes.ldif", self.sub_dict) - - def __config_uidgid_gen_first_master(self): - self.__ldap_mod("dna-posix.ldif", self.sub_dict) - - def __add_master_entry_first_master(self): - self.__ldap_mod("master-entry.ldif", self.sub_dict) - - def __add_winsync_module(self): - self.__ldap_mod("ipa-winsync-conf.ldif") - - def __enable_ssl(self): - dirname = config_dirname(self.serverid) - ca = certs.CertDB(dirname) - if self.pkcs12_info: - ca.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1]) - server_certs = ca.find_server_certs() - if len(server_certs) == 0: - raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_info[0]) - - # We only handle one server cert - nickname = server_certs[0][0] - else: - ca.create_self_signed() - ca.create_server_cert("Server-Cert", "cn=%s,ou=Fedora Directory Server" % self.host_name) - nickname = "Server-Cert" - - conn = ipaldap.IPAdmin("127.0.0.1") - conn.simple_bind_s("cn=directory manager", self.dm_password) - - mod = [(ldap.MOD_REPLACE, "nsSSLClientAuth", "allowed"), - (ldap.MOD_REPLACE, "nsSSL3Ciphers", - "-rsa_null_md5,+rsa_rc4_128_md5,+rsa_rc4_40_md5,+rsa_rc2_40_md5,\ -+rsa_des_sha,+rsa_fips_des_sha,+rsa_3des_sha,+rsa_fips_3des_sha,+fortezza,\ -+fortezza_rc4_128_sha,+fortezza_null,+tls_rsa_export1024_with_rc4_56_sha,\ -+tls_rsa_export1024_with_des_cbc_sha")] - conn.modify_s("cn=encryption,cn=config", mod) - - mod = [(ldap.MOD_ADD, "nsslapd-security", "on"), - (ldap.MOD_REPLACE, "nsslapd-ssl-check-hostname", "off")] - conn.modify_s("cn=config", mod) - - entry = ipaldap.Entry("cn=RSA,cn=encryption,cn=config") - - entry.setValues("objectclass", "top", "nsEncryptionModule") - entry.setValues("cn", "RSA") - entry.setValues("nsSSLPersonalitySSL", nickname) - entry.setValues("nsSSLToken", "internal (software)") - entry.setValues("nsSSLActivation", "on") - - conn.addEntry(entry) - - conn.unbind() - - def __add_default_layout(self): - self.__ldap_mod("bootstrap-template.ldif", self.sub_dict) - - def __create_indices(self): - self.__ldap_mod("indices.ldif") - - def __certmap_conf(self): - shutil.copyfile(ipautil.SHARE_DIR + "certmap.conf.template", - config_dirname(self.serverid) + "certmap.conf") - - def change_admin_password(self, password): - logging.debug("Changing admin password") - dirname = config_dirname(self.serverid) - if ipautil.dir_exists("/usr/lib64/mozldap"): - app = "/usr/lib64/mozldap/ldappasswd" - else: - app = "/usr/lib/mozldap/ldappasswd" - args = [app, - "-D", "cn=Directory Manager", "-w", self.dm_password, - "-P", dirname+"/cert8.db", "-ZZZ", "-s", password, - "uid=admin,cn=users,cn=accounts,"+self.suffix] - try: - ipautil.run(args) - logging.debug("ldappasswd done") - except ipautil.CalledProcessError, e: - print "Unable to set admin password", e - logging.debug("Unable to set admin password %s" % e) - - def uninstall(self): - running = self.restore_state("running") - enabled = self.restore_state("enabled") - - if not running is None: - self.stop() - - if not enabled is None and not enabled: - self.chkconfig_off() - - serverid = self.restore_state("serverid") - if not serverid is None: - erase_ds_instance_data(serverid) - - ds_user = self.restore_state("user") - user_exists = self.restore_state("user_exists") - - if not ds_user is None and not user_exists is None and not user_exists: - try: - ipautil.run(["/usr/sbin/userdel", ds_user]) - except ipautil.CalledProcessError, e: - logging.critical("failed to delete user %s" % e) - - if self.restore_state("running"): - self.start() - - # we could probably move this function into the service.Service - # class - it's very generic - all we need is a way to get an - # instance of a particular Service - def add_ca_cert(self, cacert_fname, cacert_name=''): - """Add a CA certificate to the directory server cert db. We - first have to shut down the directory server in case it has - opened the cert db read-only. Then we use the CertDB class - to add the CA cert. We have to provide a nickname, and we - do not use 'CA certificate' since that's the default, so - we use 'Imported CA' if none specified. Then we restart - the server.""" - # first make sure we have a valid cacert_fname - try: - if not os.access(cacert_fname, os.R_OK): - logging.critical("The given CA cert file named [%s] could not be read" % - cacert_fname) - return False - except OSError, e: - logging.critical("The given CA cert file named [%s] could not be read: %s" % - (cacert_fname, str(e))) - return False - # ok - ca cert file can be read - # shutdown the server - self.stop() - - dirname = config_dirname(realm_to_serverid(self.realm_name)) - certdb = certs.CertDB(dirname) - if not cacert_name or len(cacert_name) == 0: - cacert_name = "Imported CA" - # we can't pass in the nickname, so we set the instance variable - certdb.cacert_name = cacert_name - status = True - try: - certdb.load_cacert(cacert_fname) - except ipalib.CalledProcessError, e: - logging.critical("Error importing CA cert file named [%s]: %s" % - (cacert_fname, str(e))) - status = False - # restart the directory server - self.start() - - return status diff --git a/ipa-server/ipaserver/httpinstance.py b/ipa-server/ipaserver/httpinstance.py deleted file mode 100644 index f5a903b3..00000000 --- a/ipa-server/ipaserver/httpinstance.py +++ /dev/null @@ -1,231 +0,0 @@ -# Authors: Rob Crittenden -# -# Copyright (C) 2007 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 -# - -import os -import os.path -import subprocess -import string -import tempfile -import logging -import pwd -import fileinput -import sys -import shutil - -import service -import certs -import dsinstance -import installutils -from ipa import sysrestore -from ipa import ipautil - -HTTPD_DIR = "/etc/httpd" -SSL_CONF = HTTPD_DIR + "/conf.d/ssl.conf" -NSS_CONF = HTTPD_DIR + "/conf.d/nss.conf" -NSS_DIR = HTTPD_DIR + "/alias" - -selinux_warning = """WARNING: could not set selinux boolean httpd_can_network_connect to true. -The web interface may not function correctly until this boolean is -successfully change with the command: - /usr/sbin/setsebool -P httpd_can_network_connect true -Try updating the policycoreutils and selinux-policy packages. -""" - -class WebGuiInstance(service.SimpleServiceInstance): - def __init__(self): - service.SimpleServiceInstance.__init__(self, "ipa_webgui") - -class HTTPInstance(service.Service): - def __init__(self, fstore = None): - service.Service.__init__(self, "httpd") - if fstore: - self.fstore = fstore - else: - self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') - - def create_instance(self, realm, fqdn, domain_name, autoconfig=True, pkcs12_info=None): - self.fqdn = fqdn - self.realm = realm - self.domain = domain_name - self.pkcs12_info = pkcs12_info - self.sub_dict = { "REALM" : realm, "FQDN": fqdn, "DOMAIN" : self.domain } - - self.step("disabling mod_ssl in httpd", self.__disable_mod_ssl) - self.step("Setting mod_nss port to 443", self.__set_mod_nss_port) - self.step("Adding URL rewriting rules", self.__add_include) - self.step("configuring httpd", self.__configure_http) - self.step("creating a keytab for httpd", self.__create_http_keytab) - self.step("Setting up ssl", self.__setup_ssl) - if autoconfig: - self.step("Setting up browser autoconfig", self.__setup_autoconfig) - self.step("configuring SELinux for httpd", self.__selinux_config) - self.step("restarting httpd", self.__start) - self.step("configuring httpd to start on boot", self.__enable) - - self.start_creation("Configuring the web interface") - - def __start(self): - self.backup_state("running", self.is_running()) - self.restart() - - def __enable(self): - self.backup_state("enabled", self.is_running()) - self.chkconfig_on() - - def __selinux_config(self): - selinux=0 - try: - if (os.path.exists('/usr/sbin/selinuxenabled')): - ipautil.run(["/usr/sbin/selinuxenabled"]) - selinux=1 - except ipautil.CalledProcessError: - # selinuxenabled returns 1 if not enabled - pass - - if selinux: - try: - # returns e.g. "httpd_can_network_connect --> off" - (stdout, stderr) = ipautils.run(["/usr/sbin/getsebool", - "httpd_can_network_connect"]) - self.backup_state("httpd_can_network_connect", stdout.split()[2]) - except: - pass - - # Allow apache to connect to the turbogears web gui - # This can still fail even if selinux is enabled - try: - ipautil.run(["/usr/sbin/setsebool", "-P", "httpd_can_network_connect", "true"]) - except: - self.print_msg(selinux_warning) - - def __create_http_keytab(self): - http_principal = "HTTP/" + self.fqdn + "@" + self.realm - installutils.kadmin_addprinc(http_principal) - installutils.create_keytab("/etc/httpd/conf/ipa.keytab", http_principal) - - pent = pwd.getpwnam("apache") - os.chown("/etc/httpd/conf/ipa.keytab", pent.pw_uid, pent.pw_gid) - - def __configure_http(self): - http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa.conf", self.sub_dict) - self.fstore.backup_file("/etc/httpd/conf.d/ipa.conf") - http_fd = open("/etc/httpd/conf.d/ipa.conf", "w") - http_fd.write(http_txt) - http_fd.close() - - http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa-rewrite.conf", self.sub_dict) - self.fstore.backup_file("/etc/httpd/conf.d/ipa-rewrite.conf") - http_fd = open("/etc/httpd/conf.d/ipa-rewrite.conf", "w") - http_fd.write(http_txt) - http_fd.close() - - def __disable_mod_ssl(self): - if os.path.exists(SSL_CONF): - self.fstore.backup_file(SSL_CONF) - os.unlink(SSL_CONF) - - def __set_mod_nss_port(self): - self.fstore.backup_file(NSS_CONF) - if installutils.update_file(NSS_CONF, '8443', '443') != 0: - print "Updating port in %s failed." % NSS_CONF - - def __set_mod_nss_nickname(self, nickname): - installutils.set_directive(NSS_CONF, 'NSSNickname', nickname) - - def __add_include(self): - """This should run after __set_mod_nss_port so is already backed up""" - if installutils.update_file(NSS_CONF, '', 'Include conf.d/ipa-rewrite.conf\n') != 0: - print "Adding Include conf.d/ipa-rewrite to %s failed." % NSS_CONF - - def __setup_ssl(self): - ds_ca = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(self.realm))) - ca = certs.CertDB(NSS_DIR) - if self.pkcs12_info: - ca.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1], passwd="") - server_certs = ca.find_server_certs() - if len(server_certs) == 0: - raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_info[0]) - - # We only handle one server cert - nickname = server_certs[0][0] - - self.__set_mod_nss_nickname(nickname) - else: - ca.create_from_cacert(ds_ca.cacert_fname) - ca.create_server_cert("Server-Cert", "cn=%s,ou=Apache Web Server" % self.fqdn, ds_ca) - ca.create_signing_cert("Signing-Cert", "cn=%s,ou=Signing Certificate,o=Identity Policy Audit" % self.fqdn, ds_ca) - - # Fix the database permissions - os.chmod(NSS_DIR + "/cert8.db", 0640) - os.chmod(NSS_DIR + "/key3.db", 0640) - os.chmod(NSS_DIR + "/secmod.db", 0640) - - pent = pwd.getpwnam("apache") - os.chown(NSS_DIR + "/cert8.db", 0, pent.pw_gid ) - os.chown(NSS_DIR + "/key3.db", 0, pent.pw_gid ) - os.chown(NSS_DIR + "/secmod.db", 0, pent.pw_gid ) - - def __setup_autoconfig(self): - prefs_txt = ipautil.template_file(ipautil.SHARE_DIR + "preferences.html.template", self.sub_dict) - prefs_fd = open("/usr/share/ipa/html/preferences.html", "w") - prefs_fd.write(prefs_txt) - prefs_fd.close() - - # The signing cert is generated in __setup_ssl - ds_ca = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(self.realm))) - ca = certs.CertDB(NSS_DIR) - - # Publish the CA certificate - shutil.copy(ds_ca.cacert_fname, "/usr/share/ipa/html/ca.crt") - os.chmod("/usr/share/ipa/html/ca.crt", 0444) - - tmpdir = tempfile.mkdtemp(prefix = "tmp-") - shutil.copy("/usr/share/ipa/html/preferences.html", tmpdir) - ca.run_signtool(["-k", "Signing-Cert", - "-Z", "/usr/share/ipa/html/configure.jar", - "-e", ".html", - tmpdir]) - shutil.rmtree(tmpdir) - - def uninstall(self): - running = self.restore_state("running") - enabled = self.restore_state("enabled") - - if not running is None: - self.stop() - - if not enabled is None and not enabled: - self.chkconfig_off() - - for f in ["/etc/httpd/conf.d/ipa.conf", SSL_CONF, NSS_CONF]: - try: - self.fstore.restore_file(f) - except ValueError, error: - logging.debug(error) - pass - - sebool_state = self.restore_state("httpd_can_network_connect") - if not sebool_state is None: - try: - ipautil.run(["/usr/sbin/setsebool", "-P", "httpd_can_network_connect", sebool_state]) - except: - self.print_msg(selinux_warning) - - if not running is None and running: - self.start() diff --git a/ipa-server/ipaserver/installutils.py b/ipa-server/ipaserver/installutils.py deleted file mode 100644 index 563b168e..00000000 --- a/ipa-server/ipaserver/installutils.py +++ /dev/null @@ -1,248 +0,0 @@ -# Authors: Simo Sorce -# -# Copyright (C) 2007 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 -# - -import logging -import socket -import errno -import getpass -import os -import re -import fileinput -import sys -import time -import struct -import fcntl - -from ipa import ipautil -from ipa import dnsclient - -def get_fqdn(): - fqdn = "" - try: - fqdn = socket.getfqdn() - except: - try: - fqdn = socket.gethostname() - except: - fqdn = "" - return fqdn - -def verify_fqdn(host_name,no_host_dns=False): - - if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain": - raise RuntimeError("Invalid hostname: " + host_name) - - try: - hostaddr = socket.getaddrinfo(host_name, None) - except: - raise RuntimeError("Unable to resolve host name, check /etc/hosts or DNS name resolution") - - if len(hostaddr) == 0: - raise RuntimeError("Unable to resolve host name, check /etc/hosts or DNS name resolution") - - for a in hostaddr: - if a[4][0] == '127.0.0.1' or a[4][0] == '::1': - raise RuntimeError("The IPA Server hostname cannot resolve to localhost (%s). A routable IP address must be used. Check /etc/hosts to see if %s is an alias for %s" % (a[4][0], host_name, a[4][0])) - try: - revname = socket.gethostbyaddr(a[4][0])[0] - except: - raise RuntimeError("Unable to resolve the reverse ip address, check /etc/hosts or DNS name resolution") - if revname != host_name: - raise RuntimeError("The host name %s does not match the reverse lookup %s" % (host_name, revname)) - - if no_host_dns: - print "Warning: skipping DNS resolution of host", host_name - return - - # Verify this is NOT a CNAME - rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_CNAME) - if len(rs) != 0: - for rsn in rs: - if rsn.dns_type == dnsclient.DNS_T_CNAME: - raise RuntimeError("The IPA Server Hostname cannot be a CNAME, only A names are allowed.") - - # Verify that it is a DNS A record - rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_A) - if len(rs) == 0: - print "Warning: Hostname (%s) not found in DNS" % host_name - return - - rec = None - for rsn in rs: - if rsn.dns_type == dnsclient.DNS_T_A: - rec = rsn - break - - if rec == None: - print "Warning: Hostname (%s) not found in DNS" % host_name - return - - # Compare the forward and reverse - forward = rec.dns_name - - addr = socket.inet_ntoa(struct.pack(' -# Rob Crittenden 0 - - def hasAttr(self,name): - """Return True if this entry has an attribute named name, False otherwise""" - return self.data and self.data.has_key(name) - - def __getattr__(self,name): - """If name is the name of an LDAP attribute, return the first value for that - attribute - equivalent to getValue - this allows the use of - entry.cn - instead of - entry.getValue('cn') - This also allows us to return None if an attribute is not found rather than - throwing an exception""" - return self.getValue(name) - - def getValues(self,name): - """Get the list (array) of values for the attribute named name""" - return self.data.get(name) - - def getValue(self,name): - """Get the first value for the attribute named name""" - return self.data.get(name,[None])[0] - - def setValue(self,name,*value): - """Value passed in may be a single value, several values, or a single sequence. - For example: - ent.setValue('name', 'value') - ent.setValue('name', 'value1', 'value2', ..., 'valueN') - ent.setValue('name', ['value1', 'value2', ..., 'valueN']) - ent.setValue('name', ('value1', 'value2', ..., 'valueN')) - Since *value is a tuple, we may have to extract a list or tuple from that - tuple as in the last two examples above""" - if isinstance(value[0],list) or isinstance(value[0],tuple): - self.data[name] = value[0] - else: - self.data[name] = value - - setValues = setValue - - def toTupleList(self): - """Convert the attrs and values to a list of 2-tuples. The first element - of the tuple is the attribute name. The second element is either a - single value or a list of values.""" - return self.data.items() - - def __str__(self): - """Convert the Entry to its LDIF representation""" - return self.__repr__() - - # the ldif class base64 encodes some attrs which I would rather see in raw form - to - # encode specific attrs as base64, add them to the list below - ldif.safe_string_re = re.compile('^$') - base64_attrs = ['nsstate', 'krbprincipalkey', 'krbExtraData'] - - def __repr__(self): - """Convert the Entry to its LDIF representation""" - sio = cStringIO.StringIO() - # what's all this then? the unparse method will currently only accept - # a list or a dict, not a class derived from them. self.data is a - # cidict, so unparse barfs on it. I've filed a bug against python-ldap, - # but in the meantime, we have to convert to a plain old dict for printing - # I also don't want to see wrapping, so set the line width really high (1000) - newdata = {} - newdata.update(self.data) - ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(self.dn,newdata) - return sio.getvalue() - -def wrapper(f,name): - """This is the method that wraps all of the methods of the superclass. This seems - to need to be an unbound method, that's why it's outside of IPAdmin. Perhaps there - is some way to do this with the new classmethod or staticmethod of 2.4. - Basically, we replace every call to a method in SimpleLDAPObject (the superclass - of IPAdmin) with a call to inner. The f argument to wrapper is the bound method - of IPAdmin (which is inherited from the superclass). Bound means that it will implicitly - be called with the self argument, it is not in the args list. name is the name of - the method to call. If name is a method that returns entry objects (e.g. result), - we wrap the data returned by an Entry class. If name is a method that takes an entry - argument, we extract the raw data from the entry object to pass in.""" - def inner(*args, **kargs): - if name == 'result': - type, data = f(*args, **kargs) - # data is either a 2-tuple or a list of 2-tuples - # print data - if data: - if isinstance(data,tuple): - return type, Entry(data) - elif isinstance(data,list): - return type, [Entry(x) for x in data] - else: - raise TypeError, "unknown data type %s returned by result" % type(data) - else: - return type, data - elif name.startswith('add'): - # the first arg is self - # the second and third arg are the dn and the data to send - # We need to convert the Entry into the format used by - # python-ldap - ent = args[0] - if isinstance(ent,Entry): - return f(ent.dn, ent.toTupleList(), *args[2:]) - else: - return f(*args, **kargs) - else: - return f(*args, **kargs) - return inner - -class LDIFConn(ldif.LDIFParser): - def __init__( - self, - input_file, - ignored_attr_types=None,max_entries=0,process_url_schemes=None - ): - """ - See LDIFParser.__init__() - - Additional Parameters: - all_records - List instance for storing parsed records - """ - self.dndict = {} # maps dn to Entry - self.dnlist = [] # contains entries in order read - myfile = input_file - if isinstance(input_file,str) or isinstance(input_file,unicode): - myfile = open(input_file, "r") - ldif.LDIFParser.__init__(self,myfile,ignored_attr_types,max_entries,process_url_schemes) - self.parse() - if isinstance(input_file,str) or isinstance(input_file,unicode): - myfile.close() - - def handle(self,dn,entry): - """ - Append single record to dictionary of all records. - """ - if not dn: - dn = '' - newentry = Entry((dn, entry)) - self.dndict[IPAdmin.normalizeDN(dn)] = newentry - self.dnlist.append(newentry) - - def get(self,dn): - ndn = IPAdmin.normalizeDN(dn) - return self.dndict.get(ndn, Entry(None)) - -class IPAdmin(SimpleLDAPObject): - CFGSUFFIX = "o=NetscapeRoot" - DEFAULT_USER_ID = "nobody" - - def getDseAttr(self,attrname): - conffile = self.confdir + '/dse.ldif' - dseldif = LDIFConn(conffile) - cnconfig = dseldif.get("cn=config") - if cnconfig: - return cnconfig.getValue(attrname) - return None - - def __initPart2(self): - if self.binddn and len(self.binddn) and not hasattr(self,'sroot'): - try: - ent = self.getEntry('cn=config', ldap.SCOPE_BASE, '(objectclass=*)', - [ 'nsslapd-instancedir', 'nsslapd-errorlog', - 'nsslapd-certdir', 'nsslapd-schemadir' ]) - self.errlog = ent.getValue('nsslapd-errorlog') - self.confdir = ent.getValue('nsslapd-certdir') - if not self.confdir: - self.confdir = ent.getValue('nsslapd-schemadir') - if self.confdir: - self.confdir = os.path.dirname(self.confdir) - instdir = ent.getValue('nsslapd-instancedir') - ent = self.getEntry('cn=config,cn=ldbm database,cn=plugins,cn=config', - ldap.SCOPE_BASE, '(objectclass=*)', - [ 'nsslapd-directory' ]) - self.dbdir = os.path.dirname(ent.getValue('nsslapd-directory')) - except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR): - pass # usually means - except ldap.OPERATIONS_ERROR, e: - pass # usually means this is Active Directory - except ldap.LDAPError, e: - print "caught exception ", e - raise - - def __localinit__(self): - """If a CA certificate is provided then it is assumed that we are - doing SSL client authentication with proxy auth. - - If a CA certificate is not present then it is assumed that we are - using a forwarded kerberos ticket for SASL auth. SASL provides - its own encryption. - """ - if self.cacert is not None: - SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port)) - else: - SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port)) - - def __init__(self,host,port=389,cacert=None,bindcert=None,bindkey=None,proxydn=None,debug=None): - """We just set our instance variables and wrap the methods - the real - work is done in __localinit__ and __initPart2 - these are separated - out this way so that we can call them from places other than - instance creation e.g. when we just need to reconnect, not create a - new instance""" - if debug and debug.lower() == "on": - ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) - if cacert is not None: - ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert) - if bindcert is not None: - ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert) - if bindkey is not None: - ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey) - - self.__wrapmethods() - self.port = port - self.host = host - self.cacert = cacert - self.bindcert = bindcert - self.bindkey = bindkey - self.proxydn = proxydn - self.suffixes = {} - self.__localinit__() - - def __str__(self): - return self.host + ":" + str(self.port) - - def __get_server_controls__(self): - """Create the proxy user server control. The control has the form - 0x04 = Octet String - 4|0x80 sets the length of the string length field at 4 bytes - the struct() gets us the length in bytes of string self.proxydn - self.proxydn is the proxy dn to send""" - - import sys - - if self.proxydn is not None: - proxydn = chr(0x04) + chr(4|0x80) + struct.pack('l', socket.htonl(len(self.proxydn))) + self.proxydn; - - # Create the proxy control - sctrl=[] - sctrl.append(LDAPControl('2.16.840.1.113730.3.4.18',True,proxydn)) - else: - sctrl=None - - return sctrl - - def toLDAPURL(self): - return "ldap://%s:%d/" % (self.host,self.port) - - def set_proxydn(self, proxydn): - self.proxydn = proxydn - - def set_krbccache(self, krbccache, principal): - if krbccache is not None: - os.environ["KRB5CCNAME"] = krbccache - self.sasl_interactive_bind_s("", sasl_auth) - self.principal = principal - self.proxydn = None - - def do_simple_bind(self, binddn="cn=directory manager", bindpw=""): - self.binddn = binddn - self.bindpwd = bindpw - self.simple_bind_s(binddn, bindpw) - self.__initPart2() - - def getEntry(self,*args): - """This wraps the search function. It is common to just get one entry""" - - sctrl = self.__get_server_controls__() - - if sctrl is not None: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) - - try: - res = self.search(*args) - type, obj = self.result(res) - except ldap.NO_SUCH_OBJECT: - raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, - notfound(args)) - except ldap.LDAPError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - - if not obj: - raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, - notfound(args)) - elif isinstance(obj,Entry): - return obj - else: # assume list/tuple - return obj[0] - - def getList(self,*args): - """This wraps the search function to find all users.""" - - sctrl = self.__get_server_controls__() - if sctrl is not None: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) - - try: - res = self.search(*args) - type, obj = self.result(res) - except (ldap.ADMINLIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED), e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, - "Too many results returned by search", e) - except ldap.LDAPError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - - if not obj: - raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, - notfound(args)) - - all_users = [] - for s in obj: - all_users.append(s) - - return all_users - - def getListAsync(self,*args): - """This version performs an asynchronous search, to allow - results even if we hit a limit. - - It returns a list: counter followed by the results. - If the results are truncated, counter will be set to -1. - """ - - sctrl = self.__get_server_controls__() - if sctrl is not None: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) - - entries = [] - partial = 0 - - try: - msgid = self.search_ext(*args) - type, result_list = self.result(msgid, 0) - while result_list: - for result in result_list: - entries.append(result) - type, result_list = self.result(msgid, 0) - except (ldap.ADMINLIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED, - ldap.TIMELIMIT_EXCEEDED), e: - partial = 1 - except ldap.LDAPError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - - if not entries: - raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, - notfound(args)) - - if partial == 1: - counter = -1 - else: - counter = len(entries) - - return [counter] + entries - - def addEntry(self,*args): - """This wraps the add function. It assumes that the entry is already - populated with all of the desired objectclasses and attributes""" - - sctrl = self.__get_server_controls__() - - try: - if sctrl is not None: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) - self.add_s(*args) - except ldap.ALREADY_EXISTS: - raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) - except ldap.LDAPError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - return "Success" - - def updateRDN(self, dn, newrdn): - """Wrap the modrdn function.""" - - sctrl = self.__get_server_controls__() - - if dn == newrdn: - # no need to report an error - return "Success" - - try: - if sctrl is not None: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) - self.modrdn_s(dn, newrdn, delold=1) - except ldap.LDAPError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - return "Success" - - def updateEntry(self,dn,olduser,newuser): - """This wraps the mod function. It assumes that the entry is already - populated with all of the desired objectclasses and attributes""" - - sctrl = self.__get_server_controls__() - - modlist = self.generateModList(olduser, newuser) - - if len(modlist) == 0: - raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST) - - try: - if sctrl is not None: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) - self.modify_s(dn, modlist) - # this is raised when a 'delete' attribute isn't found. - # it indicates the previous attribute was removed by another - # update, making the olduser stale. - except ldap.NO_SUCH_ATTRIBUTE: - raise ipaerror.gen_exception(ipaerror.LDAP_MIDAIR_COLLISION) - except ldap.LDAPError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - return "Success" - - def generateModList(self, old_entry, new_entry): - """A mod list generator that computes more precise modification lists - than the python-ldap version. This version purposely generates no - REPLACE operations, to deal with multi-user updates more properly.""" - modlist = [] - - old_entry = ipautil.CIDict(old_entry) - new_entry = ipautil.CIDict(new_entry) - - keys = set(map(string.lower, old_entry.keys())) - keys.update(map(string.lower, new_entry.keys())) - - for key in keys: - new_values = new_entry.get(key, []) - if not(isinstance(new_values,list) or isinstance(new_values,tuple)): - new_values = [new_values] - new_values = filter(lambda value:value!=None, new_values) - new_values = set(new_values) - - old_values = old_entry.get(key, []) - if not(isinstance(old_values,list) or isinstance(old_values,tuple)): - old_values = [old_values] - old_values = filter(lambda value:value!=None, old_values) - old_values = set(old_values) - - adds = list(new_values.difference(old_values)) - removes = list(old_values.difference(new_values)) - - if len(removes) > 0: - modlist.append((ldap.MOD_DELETE, key, removes)) - if len(adds) > 0: - modlist.append((ldap.MOD_ADD, key, adds)) - - return modlist - - def inactivateEntry(self,dn,has_key): - """Rather than deleting entries we mark them as inactive. - has_key defines whether the entry already has nsAccountlock - set so we can determine which type of mod operation to run.""" - - sctrl = self.__get_server_controls__() - modlist=[] - - if has_key == True: - operation = ldap.MOD_REPLACE - else: - operation = ldap.MOD_ADD - - modlist.append((operation, "nsAccountlock", "true")) - - try: - if sctrl is not None: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) - self.modify_s(dn, modlist) - except ldap.LDAPError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - return "Success" - - def deleteEntry(self,*args): - """This wraps the delete function. Use with caution.""" - - sctrl = self.__get_server_controls__() - - try: - if sctrl is not None: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) - self.delete_s(*args) - except ldap.LDAPError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - return "Success" - - def modifyPassword(self,dn,oldpass,newpass): - """Set the user password using RFC 3062, LDAP Password Modify Extended - Operation. This ends up calling the IPA password slapi plugin - handler so the Kerberos password gets set properly. - - oldpass is not mandatory - """ - - sctrl = self.__get_server_controls__() - - try: - if sctrl is not None: - self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) - self.passwd_s(dn, oldpass, newpass) - except ldap.LDAPError, e: - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - return "Success" - - def __wrapmethods(self): - """This wraps all methods of SimpleLDAPObject, so that we can intercept - the methods that deal with entries. Instead of using a raw list of tuples - of lists of hashes of arrays as the entry object, we want to wrap entries - in an Entry class that provides some useful methods""" - for name in dir(self.__class__.__bases__[0]): - attr = getattr(self, name) - if callable(attr): - setattr(self, name, wrapper(attr, name)) - - def exportLDIF(self, file, suffix, forrepl=False, verbose=False): - cn = "export" + str(int(time.time())) - dn = "cn=%s, cn=export, cn=tasks, cn=config" % cn - entry = Entry(dn) - entry.setValues('objectclass', 'top', 'extensibleObject') - entry.setValues('cn', cn) - entry.setValues('nsFilename', file) - entry.setValues('nsIncludeSuffix', suffix) - if forrepl: - entry.setValues('nsExportReplica', 'true') - - rc = self.startTaskAndWait(entry, verbose) - - if rc: - if verbose: - print "Error: export task %s for file %s exited with %d" % (cn,file,rc) - else: - if verbose: - print "Export task %s for file %s completed successfully" % (cn,file) - return rc - - def waitForEntry(self, dn, timeout=7200, attr='', quiet=True): - scope = ldap.SCOPE_BASE - filter = "(objectclass=*)" - attrlist = [] - if attr: - filter = "(%s=*)" % attr - attrlist.append(attr) - timeout += int(time.time()) - - if isinstance(dn,Entry): - dn = dn.dn - - # wait for entry and/or attr to show up - if not quiet: - sys.stdout.write("Waiting for %s %s:%s " % (self,dn,attr)) - sys.stdout.flush() - entry = None - while not entry and int(time.time()) < timeout: - try: - entry = self.getEntry(dn, scope, filter, attrlist) - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - pass # found entry, but no attr - except ldap.NO_SUCH_OBJECT: - pass # no entry yet - except ldap.LDAPError, e: # badness - print "\nError reading entry", dn, e - break - if not entry: - if not quiet: - sys.stdout.write(".") - sys.stdout.flush() - time.sleep(1) - - if not entry and int(time.time()) > timeout: - print "\nwaitForEntry timeout for %s for %s" % (self,dn) - elif entry and not quiet: - print "\nThe waited for entry is:", entry - elif not entry: - print "\nError: could not read entry %s from %s" % (dn,self) - - return entry - - def addSchema(self, attr, val): - dn = "cn=schema" - self.modify_s(dn, [(ldap.MOD_ADD, attr, val)]) - - def addAttr(self, *args): - return self.addSchema('attributeTypes', args) - - def addObjClass(self, *args): - return self.addSchema('objectClasses', args) - - ########################### - # Static methods start here - ########################### - def normalizeDN(dn): - # not great, but will do until we use a newer version of python-ldap - # that has DN utilities - ary = ldap.explode_dn(dn.lower()) - return ",".join(ary) - normalizeDN = staticmethod(normalizeDN) - - def getfqdn(name=''): - return socket.getfqdn(name) - getfqdn = staticmethod(getfqdn) - - def getdomainname(name=''): - fqdn = IPAdmin.getfqdn(name) - index = fqdn.find('.') - if index >= 0: - return fqdn[index+1:] - else: - return fqdn - getdomainname = staticmethod(getdomainname) - - def getdefaultsuffix(name=''): - dm = IPAdmin.getdomainname(name) - if dm: - return "dc=" + dm.replace('.', ', dc=') - else: - return 'dc=localdomain' - getdefaultsuffix = staticmethod(getdefaultsuffix) - - def is_a_dn(dn): - """Returns True if the given string is a DN, False otherwise.""" - return (dn.find("=") > 0) - is_a_dn = staticmethod(is_a_dn) - - -def notfound(args): - """Return a string suitable for displaying as an error when a - search returns no results. - - This just returns whatever is after the equals sign""" - if len(args) > 2: - filter = args[2] - try: - target = re.match(r'\(.*=(.*)\)', filter).group(1) - except: - target = filter - return "%s not found" % str(target) - else: - return args[0] diff --git a/ipa-server/ipaserver/krbinstance.py b/ipa-server/ipaserver/krbinstance.py deleted file mode 100644 index 25284430..00000000 --- a/ipa-server/ipaserver/krbinstance.py +++ /dev/null @@ -1,428 +0,0 @@ -# Authors: Simo Sorce -# -# Copyright (C) 2007 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 -# - -import subprocess -import string -import tempfile -import shutil -import logging -import fileinput -import re -import sys -import os -import pwd -import socket -import shutil - -import service -import installutils -from ipa import sysrestore -from ipa import ipautil -from ipa import ipaerror - -import ipaldap - -import ldap -from ldap import LDAPError -from ldap import ldapobject - -from pyasn1.type import univ, namedtype -import pyasn1.codec.ber.encoder -import pyasn1.codec.ber.decoder -import struct -import base64 - -KRBMKEY_DENY_ACI = """ -(targetattr = "krbMKey")(version 3.0; acl "No external access"; deny (all) userdn != "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) -""" - -def update_key_val_in_file(filename, key, val): - if os.path.exists(filename): - pattern = "^[\s#]*%s\s*=\s*%s\s*" % (re.escape(key), re.escape(val)) - p = re.compile(pattern) - for line in fileinput.input(filename): - if p.search(line): - fileinput.close() - return - fileinput.close() - - pattern = "^[\s#]*%s\s*=" % re.escape(key) - p = re.compile(pattern) - for line in fileinput.input(filename, inplace=1): - if not p.search(line): - sys.stdout.write(line) - fileinput.close() - f = open(filename, "a") - f.write("%s=%s\n" % (key, val)) - f.close() - -class KpasswdInstance(service.SimpleServiceInstance): - def __init__(self): - service.SimpleServiceInstance.__init__(self, "ipa_kpasswd") - -class KrbInstance(service.Service): - def __init__(self, fstore=None): - service.Service.__init__(self, "krb5kdc") - self.ds_user = None - self.fqdn = None - self.realm = None - self.domain = None - self.host = None - self.admin_password = None - self.master_password = None - self.suffix = None - self.kdc_password = None - self.sub_dict = None - - self.kpasswd = KpasswdInstance() - - if fstore: - self.fstore = fstore - else: - self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') - - def __common_setup(self, ds_user, realm_name, host_name, domain_name, admin_password): - self.ds_user = ds_user - self.fqdn = host_name - self.realm = realm_name.upper() - self.host = host_name.split(".")[0] - self.ip = socket.gethostbyname(host_name) - self.domain = domain_name - self.suffix = ipautil.realm_to_suffix(self.realm) - self.kdc_password = ipautil.ipa_generate_password() - self.admin_password = admin_password - - self.__setup_sub_dict() - - # get a connection to the DS - try: - self.conn = ipaldap.IPAdmin(self.fqdn) - self.conn.do_simple_bind(bindpw=self.admin_password) - except Exception, e: - logging.critical("Could not connect to the Directory Server on %s" % self.fqdn) - raise e - - self.backup_state("running", self.is_running()) - try: - self.stop() - except: - # It could have been not running - pass - - def __common_post_setup(self): - self.step("starting the KDC", self.__start_instance) - self.step("configuring KDC to start on boot", self.__enable) - - def create_instance(self, ds_user, realm_name, host_name, domain_name, admin_password, master_password): - self.master_password = master_password - - self.__common_setup(ds_user, realm_name, host_name, domain_name, admin_password) - - self.step("setting KDC account password", self.__configure_kdc_account_password) - self.step("adding sasl mappings to the directory", self.__configure_sasl_mappings) - self.step("adding kerberos entries to the DS", self.__add_krb_entries) - self.step("adding default ACIs", self.__add_default_acis) - self.step("configuring KDC", self.__create_instance) - self.step("adding default keytypes", self.__add_default_keytypes) - self.step("creating a keytab for the directory", self.__create_ds_keytab) - self.step("creating a keytab for the machine", self.__create_host_keytab) - self.step("exporting the kadmin keytab", self.__export_kadmin_changepw_keytab) - self.step("adding the password extension to the directory", self.__add_pwd_extop_module) - self.step("adding the kerberos master key to the directory", self.__add_master_key) - - self.__common_post_setup() - - self.start_creation("Configuring Kerberos KDC") - - self.kpasswd.create_instance() - - def create_replica(self, ds_user, realm_name, host_name, domain_name, admin_password, ldap_passwd_filename, kpasswd_filename): - self.__copy_ldap_passwd(ldap_passwd_filename) - self.__copy_kpasswd_keytab(kpasswd_filename) - - self.__common_setup(ds_user, realm_name, host_name, domain_name, admin_password) - - self.step("adding sasl mappings to the directory", self.__configure_sasl_mappings) - self.step("writing stash file from DS", self.__write_stash_from_ds) - self.step("configuring KDC", self.__create_replica_instance) - self.step("creating a keytab for the directory", self.__create_ds_keytab) - self.step("creating a keytab for the machine", self.__create_host_keytab) - self.step("adding the password extension to the directory", self.__add_pwd_extop_module) - - self.__common_post_setup() - - self.start_creation("Configuring Kerberos KDC") - - self.kpasswd.create_instance() - - def __copy_ldap_passwd(self, filename): - self.fstore.backup_file("/var/kerberos/krb5kdc/ldappwd") - shutil.copy(filename, "/var/kerberos/krb5kdc/ldappwd") - os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600) - - def __copy_kpasswd_keytab(self, filename): - self.fstore.backup_file("/var/kerberos/krb5kdc/kpasswd.keytab") - shutil.copy(filename, "/var/kerberos/krb5kdc/kpasswd.keytab") - os.chmod("/var/kerberos/krb5kdc/kpasswd.keytab", 0600) - - - def __configure_kdc_account_password(self): - hexpwd = '' - for x in self.kdc_password: - hexpwd += (hex(ord(x))[2:]) - self.fstore.backup_file("/var/kerberos/krb5kdc/ldappwd") - pwd_fd = open("/var/kerberos/krb5kdc/ldappwd", "w") - pwd_fd.write("uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix+"#{HEX}"+hexpwd+"\n") - pwd_fd.close() - os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600) - - def __enable(self): - self.backup_state("enabled", self.is_enabled()) - self.chkconfig_on() - - def __start_instance(self): - try: - self.start() - except: - logging.critical("krb5kdc service failed to start") - - def __setup_sub_dict(self): - self.sub_dict = dict(FQDN=self.fqdn, - IP=self.ip, - PASSWORD=self.kdc_password, - SUFFIX=self.suffix, - DOMAIN=self.domain, - HOST=self.host, - REALM=self.realm) - - def __ldap_mod(self, ldif): - txt = ipautil.template_file(ipautil.SHARE_DIR + ldif, self.sub_dict) - fd = ipautil.write_tmp_file(txt) - - [pw_fd, pw_name] = tempfile.mkstemp() - os.write(pw_fd, self.admin_password) - os.close(pw_fd) - - args = ["/usr/bin/ldapmodify", "-h", "127.0.0.1", "-xv", - "-D", "cn=Directory Manager", "-y", pw_name, "-f", fd.name] - - try: - try: - ipautil.run(args) - except ipautil.CalledProcessError, e: - logging.critical("Failed to load %s: %s" % (ldif, str(e))) - finally: - os.remove(pw_name) - - fd.close() - - def __configure_sasl_mappings(self): - # we need to remove any existing SASL mappings in the directory as otherwise they - # they may conflict. There is no way to define the order they are used in atm. - - # FIXME: for some reason IPAdmin dies here, so we switch - # it out for a regular ldapobject. - conn = self.conn - self.conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") - self.conn.bind("cn=directory manager", self.admin_password) - try: - msgid = self.conn.search("cn=mapping,cn=sasl,cn=config", ldap.SCOPE_ONELEVEL, "(objectclass=nsSaslMapping)") - res = self.conn.result(msgid) - for r in res[1]: - mid = self.conn.delete_s(r[0]) - #except LDAPError, e: - # logging.critical("Error during SASL mapping removal: %s" % str(e)) - except Exception, e: - logging.critical("Could not connect to the Directory Server on %s" % self.fqdn) - raise e - print type(e) - print dir(e) - raise e - - self.conn = conn - - entry = ipaldap.Entry("cn=Full Principal,cn=mapping,cn=sasl,cn=config") - entry.setValues("objectclass", "top", "nsSaslMapping") - entry.setValues("cn", "Full Principal") - entry.setValues("nsSaslMapRegexString", '\(.*\)@\(.*\)') - entry.setValues("nsSaslMapBaseDNTemplate", self.suffix) - entry.setValues("nsSaslMapFilterTemplate", '(krbPrincipalName=\\1@\\2)') - - try: - self.conn.add_s(entry) - except ldap.ALREADY_EXISTS: - logging.critical("failed to add Full Principal Sasl mapping") - raise e - - entry = ipaldap.Entry("cn=Name Only,cn=mapping,cn=sasl,cn=config") - entry.setValues("objectclass", "top", "nsSaslMapping") - entry.setValues("cn", "Name Only") - entry.setValues("nsSaslMapRegexString", '\(.*\)') - entry.setValues("nsSaslMapBaseDNTemplate", self.suffix) - entry.setValues("nsSaslMapFilterTemplate", '(krbPrincipalName=\\1@%s)' % self.realm) - - try: - self.conn.add_s(entry) - except ldap.ALREADY_EXISTS: - logging.critical("failed to add Name Only Sasl mapping") - raise e - - def __add_krb_entries(self): - self.__ldap_mod("kerberos.ldif") - - def __add_default_acis(self): - self.__ldap_mod("default-aci.ldif") - - def __add_default_keytypes(self): - self.__ldap_mod("default-keytypes.ldif") - - def __create_replica_instance(self): - self.__create_instance(replica=True) - - def __template_file(self, path): - template = os.path.join(ipautil.SHARE_DIR, os.path.basename(path) + ".template") - conf = ipautil.template_file(template, self.sub_dict) - self.fstore.backup_file(path) - fd = open(path, "w+") - fd.write(conf) - fd.close() - - def __create_instance(self, replica=False): - self.__template_file("/var/kerberos/krb5kdc/kdc.conf") - self.__template_file("/etc/krb5.conf") - self.__template_file("/usr/share/ipa/html/krb5.ini") - self.__template_file("/usr/share/ipa/html/krb.con") - self.__template_file("/usr/share/ipa/html/krbrealm.con") - - if not replica: - #populate the directory with the realm structure - args = ["/usr/kerberos/sbin/kdb5_ldap_util", "-D", "uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix, "-w", self.kdc_password, "create", "-s", "-P", self.master_password, "-r", self.realm, "-subtrees", self.suffix, "-sscope", "sub"] - try: - ipautil.run(args) - except ipautil.CalledProcessError, e: - print "Failed to populate the realm structure in kerberos", e - - def __write_stash_from_ds(self): - try: - entry = self.conn.getEntry("cn=%s, cn=kerberos, %s" % (self.realm, self.suffix), ldap.SCOPE_SUBTREE) - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e: - logging.critical("Could not find master key in DS") - raise e - - krbMKey = pyasn1.codec.ber.decoder.decode(entry.krbmkey) - keytype = int(krbMKey[0][1][0]) - keydata = str(krbMKey[0][1][1]) - - format = '=hi%ss' % len(keydata) - s = struct.pack(format, keytype, len(keydata), keydata) - try: - fd = open("/var/kerberos/krb5kdc/.k5."+self.realm, "w") - fd.write(s) - fd.close() - except os.error, e: - logging.critical("failed to write stash file") - raise e - - #add the password extop module - def __add_pwd_extop_module(self): - self.__ldap_mod("pwd-extop-conf.ldif") - - def __add_master_key(self): - #get the Master Key from the stash file - try: - stash = open("/var/kerberos/krb5kdc/.k5."+self.realm, "r") - keytype = struct.unpack('h', stash.read(2))[0] - keylen = struct.unpack('i', stash.read(4))[0] - keydata = stash.read(keylen) - except os.error: - logging.critical("Failed to retrieve Master Key from Stash file: %s") - #encode it in the asn.1 attribute - MasterKey = univ.Sequence() - MasterKey.setComponentByPosition(0, univ.Integer(keytype)) - MasterKey.setComponentByPosition(1, univ.OctetString(keydata)) - krbMKey = univ.Sequence() - krbMKey.setComponentByPosition(0, univ.Integer(0)) #we have no kvno - krbMKey.setComponentByPosition(1, MasterKey) - asn1key = pyasn1.codec.ber.encoder.encode(krbMKey) - - dn = "cn="+self.realm+",cn=kerberos,"+self.suffix - #protect the master key by adding an appropriate deny rule along with the key - mod = [(ldap.MOD_ADD, 'aci', ipautil.template_str(KRBMKEY_DENY_ACI, self.sub_dict)), - (ldap.MOD_ADD, 'krbMKey', str(asn1key))] - try: - self.conn.modify_s(dn, mod) - except ldap.TYPE_OR_VALUE_EXISTS, e: - logging.critical("failed to add master key to kerberos database\n") - raise e - - def __create_ds_keytab(self): - ldap_principal = "ldap/" + self.fqdn + "@" + self.realm - installutils.kadmin_addprinc(ldap_principal) - - self.fstore.backup_file("/etc/dirsrv/ds.keytab") - installutils.create_keytab("/etc/dirsrv/ds.keytab", ldap_principal) - - self.fstore.backup_file("/etc/sysconfig/dirsrv") - update_key_val_in_file("/etc/sysconfig/dirsrv", "export KRB5_KTNAME", "/etc/dirsrv/ds.keytab") - pent = pwd.getpwnam(self.ds_user) - os.chown("/etc/dirsrv/ds.keytab", pent.pw_uid, pent.pw_gid) - - def __create_host_keytab(self): - host_principal = "host/" + self.fqdn + "@" + self.realm - installutils.kadmin_addprinc(host_principal) - - self.fstore.backup_file("/etc/krb5.keytab") - installutils.create_keytab("/etc/krb5.keytab", host_principal) - - # Make sure access is strictly reserved to root only for now - os.chown("/etc/krb5.keytab", 0, 0) - os.chmod("/etc/krb5.keytab", 0600) - - def __export_kadmin_changepw_keytab(self): - installutils.kadmin_modprinc("kadmin/changepw", "+requires_preauth") - - self.fstore.backup_file("/var/kerberos/krb5kdc/kpasswd.keytab") - installutils.create_keytab("/var/kerberos/krb5kdc/kpasswd.keytab", "kadmin/changepw") - - self.fstore.backup_file("/etc/sysconfig/ipa_kpasswd") - update_key_val_in_file("/etc/sysconfig/ipa_kpasswd", "export KRB5_KTNAME", "/var/kerberos/krb5kdc/kpasswd.keytab") - - def uninstall(self): - self.kpasswd.uninstall() - - running = self.restore_state("running") - enabled = self.restore_state("enabled") - - try: - self.stop() - except: - pass - - for f in ["/var/kerberos/krb5kdc/ldappwd", "/var/kerberos/krb5kdc/kdc.conf", "/etc/krb5.conf"]: - try: - self.fstore.restore_file(f) - except ValueError, error: - logging.debug(error) - pass - - if not enabled is None and not enabled: - self.chkconfig_off() - - if not running is None and running: - self.start() diff --git a/ipa-server/ipaserver/ldapupdate.py b/ipa-server/ipaserver/ldapupdate.py deleted file mode 100755 index cdf23125..00000000 --- a/ipa-server/ipaserver/ldapupdate.py +++ /dev/null @@ -1,593 +0,0 @@ -# Authors: Rob Crittenden -# -# Copyright (C) 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 -# - -# Documentation can be found at http://freeipa.org/page/LdapUpdate - -# TODO -# save undo files? - -UPDATES_DIR="/usr/share/ipa/updates/" - -import sys -from ipaserver import ipaldap, installutils -from ipa import entity, ipaerror, ipautil -import ldap -import logging -import krbV -import platform -import shlex -import time -import random -import os -import fnmatch - -class BadSyntax(Exception): - def __init__(self, value): - self.value = value - def __str__(self): - return repr(self.value) - -class LDAPUpdate: - def __init__(self, dm_password, sub_dict={}, live_run=True): - """dm_password = Directory Manager password - sub_dict = substitution dictionary - live_run = Apply the changes or just test - """ - self.sub_dict = sub_dict - self.live_run = live_run - self.dm_password = dm_password - self.conn = None - self.modified = False - - krbctx = krbV.default_context() - - fqdn = installutils.get_fqdn() - if fqdn is None: - raise RuntimeError("Unable to determine hostname") - - domain = ipautil.get_domain_name() - libarch = self.__identify_arch() - suffix = ipautil.realm_to_suffix(krbctx.default_realm) - - if not self.sub_dict.get("REALM"): - self.sub_dict["REALM"] = krbctx.default_realm - if not self.sub_dict.get("FQDN"): - self.sub_dict["FQDN"] = fqdn - if not self.sub_dict.get("DOMAIN"): - self.sub_dict["DOMAIN"] = domain - if not self.sub_dict.get("SUFFIX"): - self.sub_dict["SUFFIX"] = suffix - if not self.sub_dict.get("LIBARCH"): - self.sub_dict["LIBARCH"] = libarch - if not self.sub_dict.get("TIME"): - self.sub_dict["TIME"] = int(time.time()) - - # Try out the password - try: - conn = ipaldap.IPAdmin(fqdn) - conn.do_simple_bind(bindpw=self.dm_password) - conn.unbind() - except ldap.CONNECT_ERROR, e: - raise RuntimeError("Unable to connect to LDAP server %s" % fqdn) - except ldap.SERVER_DOWN, e: - raise RuntimeError("Unable to connect to LDAP server %s" % fqdn) - except ldap.INVALID_CREDENTIALS, e : - raise RuntimeError("The password provided is incorrect for LDAP server %s" % fqdn) - - def __detail_error(self, detail): - """IPA returns two errors back. One a generic one indicating the broad - problem and a detailed message back as well which should have come - from LDAP. This function will parse that into a human-readable - string. - """ - msg = "" - desc = detail[0].get('desc') - info = detail[0].get('info') - - if desc: - msg = desc - if info: - msg = msg + " " + info - - return msg - - def __identify_arch(self): - """On multi-arch systems some libraries may be in /lib64, /usr/lib64, - etc. Determine if a suffix is needed based on the current - architecture. - """ - bits = platform.architecture()[0] - - if bits == "64bit": - return "64" - else: - return "" - - def __template_str(self, s): - try: - return ipautil.template_str(s, self.sub_dict) - except KeyError, e: - raise BadSyntax("Unknown template keyword %s" % e) - - def __remove_quotes(self, line): - """Remove leading and trailng double or single quotes""" - if line.startswith('"'): - line = line[1:] - if line.endswith('"'): - line = line[:-1] - if line.startswith("'"): - line = line[1:] - if line.endswith("'"): - line = line[:-1] - - return line - - def __parse_values(self, line): - """Parse a comma-separated string into separate values and convert them - into a list. This should handle quoted-strings with embedded commas - """ - lexer = shlex.shlex(line) - lexer.wordchars = lexer.wordchars + ".()-" - l = [] - v = "" - for token in lexer: - if token != ',': - if v: - v = v + " " + token - else: - v = token - else: - l.append(self.__remove_quotes(v)) - v = "" - - l.append(self.__remove_quotes(v)) - - return l - - def read_file(self, filename): - if filename == '-': - fd = sys.stdin - else: - fd = open(filename) - text = fd.readlines() - if fd != sys.stdin: fd.close() - return text - - def __entry_to_entity(self, ent): - """Tne Entry class is a bare LDAP entry. The Entity class has a lot more - helper functions that we need, so convert to dict and then to Entity. - """ - entry = dict(ent.data) - entry['dn'] = ent.dn - for key,value in entry.iteritems(): - if isinstance(value,list) or isinstance(value,tuple): - if len(value) == 0: - entry[key] = '' - elif len(value) == 1: - entry[key] = value[0] - return entity.Entity(entry) - - def __combine_updates(self, dn_list, all_updates, update): - """Combine a new update with the list of total updates - - Updates are stored in 2 lists: - dn_list: contains a unique list of DNs in the updates - all_updates: the actual updates that need to be applied - - We want to apply the updates from the shortest to the longest - path so if new child and parent entries are in different updates - we can be sure the parent gets written first. This also lets - us apply any schema first since it is in the very short cn=schema. - """ - dn = update.get('dn') - dns = ldap.explode_dn(dn.lower()) - l = len(dns) - if dn_list.get(l): - if dn not in dn_list[l]: - dn_list[l].append(dn) - else: - dn_list[l] = [dn] - if not all_updates.get(dn): - all_updates[dn] = update - return all_updates - - e = all_updates[dn] - e['updates'] = e['updates'] + update['updates'] - - all_updates[dn] = e - - return all_updates - - def parse_update_file(self, data, all_updates, dn_list): - """Parse the update file into a dictonary of lists and apply the update - for each DN in the file.""" - valid_keywords = ["default", "add", "remove", "only"] - update = {} - d = "" - index = "" - dn = None - lcount = 0 - for line in data: - # Strip out \n and extra white space - lcount = lcount + 1 - - # skip comments and empty lines - line = line.rstrip() - if line.startswith('#') or line == '': continue - - if line.lower().startswith('dn:'): - if dn is not None: - all_updates = self.__combine_updates(dn_list, all_updates, update) - - update = {} - dn = line[3:].strip() - update['dn'] = self.__template_str(dn) - else: - if dn is None: - raise BadSyntax, "dn is not defined in the update" - - if line.startswith(' '): - v = d[len(d) - 1] - v = v + " " + line.strip() - d[len(d) - 1] = v - update[index] = d - continue - line = line.strip() - values = line.split(':', 2) - if len(values) != 3: - raise BadSyntax, "Bad formatting on line %d: %s" % (lcount,line) - - index = values[0].strip().lower() - - if index not in valid_keywords: - raise BadSyntax, "Unknown keyword %s" % index - - attr = values[1].strip() - value = values[2].strip() - value = self.__template_str(value) - - new_value = "" - if index == "default": - new_value = attr + ":" + value - else: - new_value = index + ":" + attr + ":" + value - index = "updates" - - d = update.get(index, []) - - d.append(new_value) - - update[index] = d - - if dn is not None: - all_updates = self.__combine_updates(dn_list, all_updates, update) - - return (all_updates, dn_list) - - def create_index_task(self, attribute): - """Create a task to update an index for an attribute""" - - r = random.SystemRandom() - - # Refresh the time to make uniqueness more probable. Add on some - # randomness for good measure. - self.sub_dict['TIME'] = int(time.time()) + r.randint(0,10000) - - cn = self.__template_str("indextask_$TIME") - dn = "cn=%s, cn=index, cn=tasks, cn=config" % cn - - e = ipaldap.Entry(dn) - - e.setValues('objectClass', ['top', 'extensibleObject']) - e.setValue('cn', cn) - e.setValue('nsInstance', 'userRoot') - e.setValues('nsIndexAttribute', attribute) - - logging.info("Creating task to index attribute: %s", attribute) - logging.debug("Task id: %s", dn) - - if self.live_run: - self.conn.addEntry(e.dn, e.toTupleList()) - - return dn - - def monitor_index_task(self, dn): - """Give a task DN monitor it and wait until it has completed (or failed) - """ - - if not self.live_run: - # If not doing this live there is nothing to monitor - return - - # Pause for a moment to give the task time to be created - time.sleep(1) - - attrlist = ['nstaskstatus', 'nstaskexitcode'] - entry = None - - while True: - try: - entry = self.conn.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - logging.error("Task not found: %s", dn) - return - except ipaerror.exception_for(ipaerror.LDAP_DATABASE_ERROR), e: - logging.error("Task lookup failure %s: %s", e, self.__detail_error(e.detail)) - return - - status = entry.getValue('nstaskstatus') - if status is None: - # task doesn't have a status yet - time.sleep(1) - continue - - if status.lower().find("finished") > -1: - logging.info("Indexing finished") - break - - logging.debug("Indexing in progress") - time.sleep(1) - - return - - def __create_default_entry(self, dn, default): - """Create the default entry from the values provided. - - The return type is entity.Entity - """ - entry = ipaldap.Entry(dn) - - if not default: - # This means that the entire entry needs to be created with add - return self.__entry_to_entity(entry) - - for line in default: - # We already do syntax-parsing so this is safe - (k, v) = line.split(':',1) - e = entry.getValues(k) - if e: - # multi-valued attribute - e = list(e) - e.append(v) - else: - e = v - entry.setValues(k, e) - - return self.__entry_to_entity(entry) - - def __get_entry(self, dn): - """Retrieve an object from LDAP. - - The return type is ipaldap.Entry - """ - searchfilter="objectclass=*" - sattrs = ["*"] - scope = ldap.SCOPE_BASE - - return self.conn.getList(dn, scope, searchfilter, sattrs) - - def __apply_updates(self, updates, entry): - """updates is a list of changes to apply - entry is the thing to apply them to - - returns the modified entry - """ - if not updates: - return entry - - only = {} - for u in updates: - # We already do syntax-parsing so this is safe - (utype, k, values) = u.split(':',2) - - values = self.__parse_values(values) - - e = entry.getValues(k) - if not isinstance(e, list): - if e is None: - e = [] - else: - e = [e] - - for v in values: - if utype == 'remove': - logging.debug("remove: '%s' from %s, current value %s", v, k, e) - try: - e.remove(v) - except ValueError: - logging.warn("remove: '%s' not in %s", v, k) - pass - entry.setValues(k, e) - logging.debug('remove: updated value %s', e) - elif utype == 'add': - logging.debug("add: '%s' to %s, current value %s", v, k, e) - # Remove it, ignoring errors so we can blindly add it later - try: - e.remove(v) - except ValueError: - pass - e.append(v) - logging.debug('add: updated value %s', e) - entry.setValues(k, e) - elif utype == 'only': - logging.debug("only: set %s to '%s', current value %s", k, v, e) - if only.get(k): - e.append(v) - else: - e = [v] - only[k] = True - entry.setValues(k, e) - logging.debug('only: updated value %s', e) - - self.print_entity(entry) - - return entry - - def print_entity(self, e, message=None): - """The entity object currently lacks a str() method""" - logging.debug("---------------------------------------------") - if message: - logging.debug("%s", message) - logging.debug("dn: " + e.dn) - attr = e.attrList() - for a in attr: - value = e.getValues(a) - if isinstance(value,str): - logging.debug(a + ": " + value) - else: - logging.debug(a + ": ") - for l in value: - logging.debug("\t" + l) - def is_schema_updated(self, s): - """Compare the schema in 's' with the current schema in the DS to - see if anything has changed. This should account for syntax - differences (like added parens that make no difference but are - detected as a change by generateModList()). - - This doesn't handle re-ordering of attributes. They are still - detected as changes, so foo $ bar != bar $ foo. - - return True if the schema has changed - return False if it has not - """ - s = ldap.schema.SubSchema(s) - s = s.ldap_entry() - - # Get a fresh copy and convert into a SubSchema - n = self.__get_entry("cn=schema")[0] - n = dict(n.data) - n = ldap.schema.SubSchema(n) - n = n.ldap_entry() - - if s == n: - return False - else: - return True - - def __update_record(self, update): - found = False - - new_entry = self.__create_default_entry(update.get('dn'), - update.get('default')) - - try: - e = self.__get_entry(new_entry.dn) - if len(e) > 1: - # we should only ever get back one entry - raise BadSyntax, "More than 1 entry returned on a dn search!? %s" % new_entry.dn - entry = self.__entry_to_entity(e[0]) - found = True - logging.info("Updating existing entry: %s", entry.dn) - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - # Doesn't exist, start with the default entry - entry = new_entry - logging.info("New entry: %s", entry.dn) - except ipaerror.exception_for(ipaerror.LDAP_DATABASE_ERROR): - # Doesn't exist, start with the default entry - entry = new_entry - logging.info("New entry, using default value: %s", entry.dn) - - self.print_entity(entry) - - # Bring this entry up to date - entry = self.__apply_updates(update.get('updates'), entry) - - self.print_entity(entry, "Final value") - - if not found: - # New entries get their orig_data set to the entry itself. We want to - # empty that so that everything appears new when generating the - # modlist - # entry.orig_data = {} - try: - if self.live_run: - self.conn.addEntry(entry.dn, entry.toTupleList()) - except Exception, e: - logging.error("Add failure %s: %s", e, self.__detail_error(e.detail)) - else: - # Update LDAP - try: - updated = False - changes = self.conn.generateModList(entry.origDataDict(), entry.toDict()) - if (entry.dn == "cn=schema"): - updated = self.is_schema_updated(entry.toDict()) - else: - if len(changes) > 1: - updated = True - logging.debug("%s" % changes) - if self.live_run and updated: - self.conn.updateEntry(entry.dn, entry.origDataDict(), entry.toDict()) - logging.info("Done") - except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST), e: - logging.info("Entry already up-to-date") - updated = False - except ipaerror.exception_for(ipaerror.LDAP_DATABASE_ERROR), e: - logging.error("Update failed: %s: %s", e, self.__detail_error(e.detail)) - updated = False - - if ("cn=index" in entry.dn and - "cn=userRoot" in entry.dn): - taskid = self.create_index_task(entry.cn) - self.monitor_index_task(taskid) - - if updated: - self.modified = True - return - - def get_all_files(self, root, recursive=False): - """Get all update files""" - f = [] - for path, subdirs, files in os.walk(root): - for name in files: - if fnmatch.fnmatch(name, "*.update"): - f.append(os.path.join(path, name)) - if not recursive: - break - return f - - def update(self, files): - """Execute the update. files is a list of the update files to use. - - returns True if anything was changed, otherwise False - """ - - try: - self.conn = ipaldap.IPAdmin(self.sub_dict['FQDN']) - self.conn.do_simple_bind(bindpw=self.dm_password) - all_updates = {} - dn_list = {} - for f in files: - try: - logging.info("Parsing file %s" % f) - data = self.read_file(f) - except Exception, e: - print e - sys.exit(1) - - (all_updates, dn_list) = self.parse_update_file(data, all_updates, dn_list) - - sortedkeys = dn_list.keys() - sortedkeys.sort() - for k in sortedkeys: - for dn in dn_list[k]: - self.__update_record(all_updates[dn]) - finally: - if self.conn: self.conn.unbind() - - return self.modified diff --git a/ipa-server/ipaserver/ntpinstance.py b/ipa-server/ipaserver/ntpinstance.py deleted file mode 100644 index e2ec6065..00000000 --- a/ipa-server/ipaserver/ntpinstance.py +++ /dev/null @@ -1,107 +0,0 @@ -# Authors: Karl MacMillan -# -# Copyright (C) 2007 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 -# - -import shutil -import logging - -import service -from ipa import sysrestore -from ipa import ipautil - -class NTPInstance(service.Service): - def __init__(self, fstore=None): - service.Service.__init__(self, "ntpd") - - if fstore: - self.fstore = fstore - else: - self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') - - def __write_config(self): - # The template sets the config to point towards ntp.pool.org, but - # they request that software not point towards the default pool. - # We use the OS variable to point it towards either the rhel - # or fedora pools. Other distros should be added in the future - # or we can get our own pool. - os = "" - if ipautil.file_exists("/etc/fedora-release"): - os = "fedora" - elif ipautil.file_exists("/etc/redhat-release"): - os = "rhel" - - sub_dict = { } - sub_dict["SERVERA"] = "0.%s.pool.ntp.org" % os - sub_dict["SERVERB"] = "1.%s.pool.ntp.org" % os - sub_dict["SERVERC"] = "2.%s.pool.ntp.org" % os - - ntp_conf = ipautil.template_file(ipautil.SHARE_DIR + "ntp.conf.server.template", sub_dict) - ntp_sysconf = ipautil.template_file(ipautil.SHARE_DIR + "ntpd.sysconfig.template", {}) - - self.fstore.backup_file("/etc/ntp.conf") - self.fstore.backup_file("/etc/sysconfig/ntpd") - - fd = open("/etc/ntp.conf", "w") - fd.write(ntp_conf) - fd.close() - - fd = open("/etc/sysconfig/ntpd", "w") - fd.write(ntp_sysconf) - fd.close() - - def __stop(self): - self.backup_state("running", self.is_running()) - self.stop() - - def __start(self): - self.start() - - def __enable(self): - self.backup_state("enabled", self.is_enabled()) - self.chkconfig_on() - - def create_instance(self): - - # we might consider setting the date manually using ntpd -qg in case - # the current time is very far off. - - self.step("stopping ntpd", self.__stop) - self.step("writing configuration", self.__write_config) - self.step("configuring ntpd to start on boot", self.__enable) - self.step("starting ntpd", self.__start) - - self.start_creation("Configuring ntpd") - - def uninstall(self): - running = self.restore_state("running") - enabled = self.restore_state("enabled") - - if not running is None: - self.stop() - - try: - self.fstore.restore_file("/etc/ntp.conf") - except ValueError, error: - logging.debug(error) - pass - - if not enabled is None and not enabled: - self.chkconfig_off() - - if not running is None and running: - self.start() diff --git a/ipa-server/ipaserver/replication.py b/ipa-server/ipaserver/replication.py deleted file mode 100644 index 8477bd18..00000000 --- a/ipa-server/ipaserver/replication.py +++ /dev/null @@ -1,532 +0,0 @@ -# Authors: Karl MacMillan -# -# Copyright (C) 2007 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 -# - -import time, logging - -import ipaldap, ldap, dsinstance -from ldap import modlist -from ipa import ipaerror - -DIRMAN_CN = "cn=directory manager" -CACERT="/usr/share/ipa/html/ca.crt" -# the default container used by AD for user entries -WIN_USER_CONTAINER="cn=Users" -# the default container used by IPA for user entries -IPA_USER_CONTAINER="cn=users,cn=accounts" -PORT = 636 -TIMEOUT = 120 - -IPA_REPLICA = 1 -WINSYNC = 2 - -class ReplicationManager: - """Manage replication agreements between DS servers, and sync - agreements with Windows servers""" - def __init__(self, hostname, dirman_passwd): - self.hostname = hostname - self.dirman_passwd = dirman_passwd - - self.conn = ipaldap.IPAdmin(hostname, port=PORT, cacert=CACERT) - self.conn.do_simple_bind(bindpw=dirman_passwd) - - self.repl_man_passwd = dirman_passwd - - # these are likely constant, but you could change them - # at runtime if you really want - self.repl_man_dn = "cn=replication manager,cn=config" - self.repl_man_cn = "replication manager" - self.suffix = "" - - def _get_replica_id(self, conn, master_conn): - """ - Returns the replica ID which is unique for each backend. - - conn is the connection we are trying to get the replica ID for. - master_conn is the master we are going to replicate with. - """ - # First see if there is already one set - dn = self.replica_dn() - try: - replica = conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0] - if replica.getValue('nsDS5ReplicaId'): - return int(replica.getValue('nsDS5ReplicaId')) - except ldap.NO_SUCH_OBJECT: - pass - - # Ok, either the entry doesn't exist or the attribute isn't set - # so get it from the other master - retval = -1 - dn = "cn=replication, cn=etc, %s" % self.suffix - try: - replica = master_conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0] - if not replica.getValue('nsDS5ReplicaId'): - logging.debug("Unable to retrieve nsDS5ReplicaId from remote server") - raise RuntimeError("Unable to retrieve nsDS5ReplicaId from remote server") - except ldap.NO_SUCH_OBJECT: - logging.debug("Unable to retrieve nsDS5ReplicaId from remote server") - raise - - # Now update the value on the master - retval = int(replica.getValue('nsDS5ReplicaId')) - mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaId', str(retval + 1))] - - try: - master_conn.modify_s(dn, mod) - except Exception, e: - logging.debug("Problem updating nsDS5ReplicaID %s" % e) - raise - - return retval - - def find_replication_dns(self, conn): - filt = "(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement))" - try: - ents = conn.search_s("cn=mapping tree,cn=config", ldap.SCOPE_SUBTREE, filt) - except ldap.NO_SUCH_OBJECT: - return [] - return [ent.dn for ent in ents] - - def add_replication_manager(self, conn, passwd=None): - """ - Create a pseudo user to use for replication. If no password - is provided the directory manager password will be used. - """ - - if passwd: - self.repl_man_passwd = passwd - - ent = ipaldap.Entry(self.repl_man_dn) - ent.setValues("objectclass", "top", "person") - ent.setValues("cn", self.repl_man_cn) - ent.setValues("userpassword", self.repl_man_passwd) - ent.setValues("sn", "replication manager pseudo user") - - try: - conn.add_s(ent) - except ldap.ALREADY_EXISTS: - # should we set the password here? - pass - - def delete_replication_manager(self, conn, dn="cn=replication manager,cn=config"): - try: - conn.delete_s(dn) - except ldap.NO_SUCH_OBJECT: - pass - - def get_replica_type(self, master=True): - if master: - return "3" - else: - return "2" - - def replica_dn(self): - return 'cn=replica, cn="%s", cn=mapping tree, cn=config' % self.suffix - - def local_replica_config(self, conn, replica_id): - dn = self.replica_dn() - - try: - conn.getEntry(dn, ldap.SCOPE_BASE) - # replication is already configured - return - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - pass - - replica_type = self.get_replica_type() - - entry = ipaldap.Entry(dn) - entry.setValues('objectclass', "top", "nsds5replica", "extensibleobject") - entry.setValues('cn', "replica") - entry.setValues('nsds5replicaroot', self.suffix) - entry.setValues('nsds5replicaid', str(replica_id)) - entry.setValues('nsds5replicatype', replica_type) - entry.setValues('nsds5flags', "1") - entry.setValues('nsds5replicabinddn', [self.repl_man_dn]) - entry.setValues('nsds5replicalegacyconsumer', "off") - - conn.add_s(entry) - - def setup_changelog(self, conn): - dn = "cn=changelog5, cn=config" - dirpath = conn.dbdir + "/cldb" - entry = ipaldap.Entry(dn) - entry.setValues('objectclass', "top", "extensibleobject") - entry.setValues('cn', "changelog5") - entry.setValues('nsslapd-changelogdir', dirpath) - try: - conn.add_s(entry) - except ldap.ALREADY_EXISTS: - return - - def setup_chaining_backend(self, conn): - chaindn = "cn=chaining database, cn=plugins, cn=config" - benamebase = "chaindb" - urls = [self.to_ldap_url(conn)] - cn = "" - benum = 1 - done = False - while not done: - try: - cn = benamebase + str(benum) # e.g. localdb1 - dn = "cn=" + cn + ", " + chaindn - entry = ipaldap.Entry(dn) - entry.setValues('objectclass', 'top', 'extensibleObject', 'nsBackendInstance') - entry.setValues('cn', cn) - entry.setValues('nsslapd-suffix', self.suffix) - entry.setValues('nsfarmserverurl', urls) - entry.setValues('nsmultiplexorbinddn', self.repl_man_dn) - entry.setValues('nsmultiplexorcredentials', self.repl_man_passwd) - - self.conn.add_s(entry) - done = True - except ldap.ALREADY_EXISTS: - benum += 1 - except ldap.LDAPError, e: - print "Could not add backend entry " + dn, e - raise - - return cn - - def to_ldap_url(self, conn): - return "ldap://%s:%d/" % (conn.host, conn.port) - - def setup_chaining_farm(self, conn): - try: - conn.modify_s(self.suffix, [(ldap.MOD_ADD, 'aci', - [ "(targetattr = \"*\")(version 3.0; acl \"Proxied authorization for database links\"; allow (proxy) userdn = \"ldap:///%s\";)" % self.repl_man_dn ])]) - except ldap.TYPE_OR_VALUE_EXISTS: - logging.debug("proxy aci already exists in suffix %s on %s" % (self.suffix, conn.host)) - - def get_mapping_tree_entry(self): - try: - entry = self.conn.getEntry("cn=mapping tree,cn=config", ldap.SCOPE_ONELEVEL, - "(cn=\"%s\")" % (self.suffix)) - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e: - logging.debug("failed to find mappting tree entry for %s" % self.suffix) - raise e - - return entry - - - def enable_chain_on_update(self, bename): - mtent = self.get_mapping_tree_entry() - dn = mtent.dn - - plgent = self.conn.getEntry("cn=Multimaster Replication Plugin,cn=plugins,cn=config", - ldap.SCOPE_BASE, "(objectclass=*)", ['nsslapd-pluginPath']) - path = plgent.getValue('nsslapd-pluginPath') - - mod = [(ldap.MOD_REPLACE, 'nsslapd-state', 'backend'), - (ldap.MOD_ADD, 'nsslapd-backend', bename), - (ldap.MOD_ADD, 'nsslapd-distribution-plugin', path), - (ldap.MOD_ADD, 'nsslapd-distribution-funct', 'repl_chain_on_update')] - - try: - self.conn.modify_s(dn, mod) - except ldap.TYPE_OR_VALUE_EXISTS: - logging.debug("chainOnUpdate already enabled for %s" % self.suffix) - - def setup_chain_on_update(self, other_conn): - chainbe = self.setup_chaining_backend(other_conn) - self.enable_chain_on_update(chainbe) - - def add_passsync_user(self, conn, password): - pass_dn = "uid=passsync,cn=sysaccounts,cn=etc,%s" % self.suffix - print "The user for the Windows PassSync service is %s" % pass_dn - try: - conn.getEntry(pass_dn, ldap.SCOPE_BASE) - print "Windows PassSync entry exists, not resetting password" - return - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - pass - - # The user doesn't exist, add it - entry = ipaldap.Entry(pass_dn) - entry.setValues("objectclass", ["account", "simplesecurityobject"]) - entry.setValues("uid", "passsync") - entry.setValues("userPassword", password) - conn.add_s(entry) - - # Add it to the list of users allowed to bypass password policy - extop_dn = "cn=ipa_pwd_extop,cn=plugins,cn=config" - entry = conn.getEntry(extop_dn, ldap.SCOPE_BASE) - pass_mgrs = entry.getValues('passSyncManagersDNs') - if not pass_mgrs: - pass_mgrs = [] - if not isinstance(pass_mgrs, list): - pass_mgrs = [pass_mgrs] - pass_mgrs.append(pass_dn) - mod = [(ldap.MOD_REPLACE, 'passSyncManagersDNs', pass_mgrs)] - conn.modify_s(extop_dn, mod) - - # And finally grant it permission to write passwords - mod = [(ldap.MOD_ADD, 'aci', - ['(targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Windows PassSync service can write passwords"; allow (write) userdn="ldap:///%s";)' % pass_dn])] - try: - conn.modify_s(self.suffix, mod) - except ldap.TYPE_OR_VALUE_EXISTS: - logging.debug("passsync aci already exists in suffix %s on %s" % (self.suffix, conn.host)) - - def setup_winsync_agmt(self, entry, **kargs): - entry.setValues("objectclass", "nsDSWindowsReplicationAgreement") - entry.setValues("nsds7WindowsReplicaSubtree", - kargs.get("win_subtree", - WIN_USER_CONTAINER + "," + self.suffix)) - entry.setValues("nsds7DirectoryReplicaSubtree", - kargs.get("ds_subtree", - IPA_USER_CONTAINER + "," + self.suffix)) - # for now, just sync users and ignore groups - entry.setValues("nsds7NewWinUserSyncEnabled", kargs.get('newwinusers', 'true')) - entry.setValues("nsds7NewWinGroupSyncEnabled", kargs.get('newwingroups', 'false')) - windomain = '' - if kargs.has_key('windomain'): - windomain = kargs['windomain'] - else: - windomain = '.'.join(ldap.explode_dn(self.suffix, 1)) - entry.setValues("nsds7WindowsDomain", windomain) - - def agreement_dn(self, hostname, port=PORT): - cn = "meTo%s%d" % (hostname, port) - dn = "cn=%s, %s" % (cn, self.replica_dn()) - - return (cn, dn) - - def setup_agreement(self, a, b, **kargs): - cn, dn = self.agreement_dn(b.host) - try: - a.getEntry(dn, ldap.SCOPE_BASE) - return - except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - pass - - iswinsync = kargs.get("winsync", False) - repl_man_dn = kargs.get("binddn", self.repl_man_dn) - repl_man_passwd = kargs.get("bindpw", self.repl_man_passwd) - port = kargs.get("port", PORT) - - entry = ipaldap.Entry(dn) - entry.setValues('objectclass', "nsds5replicationagreement") - entry.setValues('cn', cn) - entry.setValues('nsds5replicahost', b.host) - entry.setValues('nsds5replicaport', str(port)) - entry.setValues('nsds5replicatimeout', str(TIMEOUT)) - entry.setValues('nsds5replicabinddn', repl_man_dn) - entry.setValues('nsds5replicacredentials', repl_man_passwd) - entry.setValues('nsds5replicabindmethod', 'simple') - entry.setValues('nsds5replicaroot', self.suffix) - entry.setValues('nsds5replicaupdateschedule', '0000-2359 0123456') - entry.setValues('nsds5replicatransportinfo', 'SSL') - entry.setValues('nsDS5ReplicatedAttributeList', '(objectclass=*) $ EXCLUDE memberOf') - entry.setValues('description', "me to %s%d" % (b.host, port)) - if iswinsync: - self.setup_winsync_agmt(entry, **kargs) - - a.add_s(entry) - - entry = a.waitForEntry(entry) - - def delete_agreement(self, hostname): - cn, dn = self.agreement_dn(hostname) - return self.conn.deleteEntry(dn) - - def check_repl_init(self, conn, agmtdn): - done = False - hasError = 0 - attrlist = ['cn', 'nsds5BeginReplicaRefresh', 'nsds5replicaUpdateInProgress', - 'nsds5ReplicaLastInitStatus', 'nsds5ReplicaLastInitStart', - 'nsds5ReplicaLastInitEnd'] - entry = conn.getEntry(agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) - if not entry: - print "Error reading status from agreement", agmtdn - hasError = 1 - else: - refresh = entry.nsds5BeginReplicaRefresh - inprogress = entry.nsds5replicaUpdateInProgress - status = entry.nsds5ReplicaLastInitStatus - if not refresh: # done - check status - if not status: - print "No status yet" - elif status.find("replica busy") > -1: - print "[%s] reports: Replica Busy! Status: [%s]" % (conn.host, status) - done = True - hasError = 2 - elif status.find("Total update succeeded") > -1: - print "Update succeeded" - done = True - elif inprogress.lower() == 'true': - print "Update in progress yet not in progress" - else: - print "[%s] reports: Update failed! Status: [%s]" % (conn.host, status) - hasError = 1 - done = True - else: - print "Update in progress" - - return done, hasError - - def check_repl_update(self, conn, agmtdn): - done = False - hasError = 0 - attrlist = ['cn', 'nsds5replicaUpdateInProgress', - 'nsds5ReplicaLastUpdateStatus', 'nsds5ReplicaLastUpdateStart', - 'nsds5ReplicaLastUpdateEnd'] - entry = conn.getEntry(agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) - if not entry: - print "Error reading status from agreement", agmtdn - hasError = 1 - else: - inprogress = entry.nsds5replicaUpdateInProgress - status = entry.nsds5ReplicaLastUpdateStatus - start = entry.nsds5ReplicaLastUpdateStart - end = entry.nsds5ReplicaLastUpdateEnd - # incremental update is done if inprogress is false and end >= start - done = inprogress and inprogress.lower() == 'false' and start and end and (start <= end) - logging.info("Replication Update in progress: %s: status: %s: start: %s: end: %s" % - (inprogress, status, start, end)) - if not done and status: # check for errors - # status will usually be a number followed by a string - # number != 0 means error - rc, msg = status.split(' ', 1) - if rc != '0': - hasError = 1 - done = True - - return done, hasError - - def wait_for_repl_init(self, conn, agmtdn): - done = False - haserror = 0 - while not done and not haserror: - time.sleep(1) # give it a few seconds to get going - done, haserror = self.check_repl_init(conn, agmtdn) - return haserror - - def wait_for_repl_update(self, conn, agmtdn, maxtries=600): - done = False - haserror = 0 - while not done and not haserror and maxtries > 0: - time.sleep(1) # give it a few seconds to get going - done, haserror = self.check_repl_update(conn, agmtdn) - maxtries -= 1 - if maxtries == 0: # too many tries - print "Error: timeout: could not determine agreement status: please check your directory server logs for possible errors" - haserror = 1 - return haserror - - def start_replication(self, other_conn, conn=None): - print "Starting replication, please wait until this has completed." - if conn == None: - conn = self.conn - cn, dn = self.agreement_dn(conn.host) - - mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')] - other_conn.modify_s(dn, mod) - - return self.wait_for_repl_init(other_conn, dn) - - def basic_replication_setup(self, conn, replica_id): - self.add_replication_manager(conn) - self.local_replica_config(conn, replica_id) - self.setup_changelog(conn) - - def setup_replication(self, other_hostname, realm_name, **kargs): - """ - NOTES: - - the directory manager password needs to be the same on - both directories. Or use the optional binddn and bindpw - """ - iswinsync = kargs.get("winsync", False) - oth_port = kargs.get("port", PORT) - oth_cacert = kargs.get("cacert", CACERT) - oth_binddn = kargs.get("binddn", DIRMAN_CN) - oth_bindpw = kargs.get("bindpw", self.dirman_passwd) - # note - there appears to be a bug in python-ldap - it does not - # allow connections using two different CA certs - other_conn = ipaldap.IPAdmin(other_hostname, port=oth_port, cacert=oth_cacert) - try: - other_conn.do_simple_bind(binddn=oth_binddn, bindpw=oth_bindpw) - except Exception, e: - if iswinsync: - logging.info("Could not validate connection to remote server %s:%d - continuing" % - (other_hostname, oth_port)) - logging.info("The error was: %s" % e) - else: - raise e - - self.suffix = ipaldap.IPAdmin.normalizeDN(dsinstance.realm_to_suffix(realm_name)) - - if not iswinsync: - local_id = self._get_replica_id(self.conn, other_conn) - else: - # there is no other side to get a replica ID from - local_id = self._get_replica_id(self.conn, self.conn) - self.basic_replication_setup(self.conn, local_id) - - if not iswinsync: - other_id = self._get_replica_id(other_conn, other_conn) - self.basic_replication_setup(other_conn, other_id) - self.setup_agreement(other_conn, self.conn) - self.setup_agreement(self.conn, other_conn) - return self.start_replication(other_conn) - else: - self.add_passsync_user(self.conn, kargs.get("passsync")) - self.setup_agreement(self.conn, other_conn, **kargs) - logging.info("Added new sync agreement, waiting for it to become ready . . .") - cn, dn = self.agreement_dn(other_hostname) - self.wait_for_repl_update(self.conn, dn, 30) - logging.info("Agreement is ready, starting replication . . .") - return self.start_replication(self.conn, other_conn) - - def initialize_replication(self, dn, conn): - mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')] - try: - conn.modify_s(dn, mod) - except ldap.ALREADY_EXISTS: - return - - def force_synch(self, dn, schedule, conn): - newschedule = '2358-2359 0' - - # On the remote chance of a match. We force a synch to happen right - # now by changing the schedule to something else and quickly changing - # it back. - if newschedule == schedule: - newschedule = '2358-2359 1' - logging.info("Changing agreement %s schedule to %s to force synch" % - (dn, newschedule)) - mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaUpdateSchedule', [ newschedule ])] - conn.modify_s(dn, mod) - time.sleep(1) - logging.info("Changing agreement %s to restore original schedule %s" % - (dn, schedule)) - mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaUpdateSchedule', [ schedule ])] - conn.modify_s(dn, mod) - - def get_agreement_type(self, hostname): - cn, dn = self.agreement_dn(hostname) - - entry = self.conn.getEntry(dn, ldap.SCOPE_BASE) - - objectclass = entry.getValues("objectclass") - - for o in objectclass: - if o.lower() == "nsdswindowsreplicationagreement": - return WINSYNC - - return IPA_REPLICA diff --git a/ipa-server/ipaserver/service.py b/ipa-server/ipaserver/service.py deleted file mode 100644 index b9f6c505..00000000 --- a/ipa-server/ipaserver/service.py +++ /dev/null @@ -1,169 +0,0 @@ -# Authors: Karl MacMillan -# -# Copyright (C) 2007 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 -# - -import logging, sys -from ipa import sysrestore -from ipa import ipautil - - -def stop(service_name): - ipautil.run(["/sbin/service", service_name, "stop"]) - -def start(service_name): - ipautil.run(["/sbin/service", service_name, "start"]) - -def restart(service_name): - ipautil.run(["/sbin/service", service_name, "restart"]) - -def is_running(service_name): - ret = True - try: - ipautil.run(["/sbin/service", service_name, "status"]) - except ipautil.CalledProcessError: - ret = False - return ret - -def chkconfig_on(service_name): - ipautil.run(["/sbin/chkconfig", service_name, "on"]) - -def chkconfig_off(service_name): - ipautil.run(["/sbin/chkconfig", service_name, "off"]) - -def chkconfig_add(service_name): - ipautil.run(["/sbin/chkconfig", "--add", service_name]) - -def chkconfig_del(service_name): - ipautil.run(["/sbin/chkconfig", "--del", service_name]) - -def is_enabled(service_name): - (stdout, stderr) = ipautil.run(["/sbin/chkconfig", "--list", service_name]) - - runlevels = {} - for runlevel in range(0, 7): - runlevels[runlevel] = False - - for line in stdout.split("\n"): - parts = line.split() - if parts[0] == service_name: - for s in parts[1:]: - (runlevel, status) = s.split(":")[0:2] - try: - runlevels[int(runlevel)] = status == "on" - except ValueError: - pass - break - - return (runlevels[3] and runlevels[4] and runlevels[5]) - -def print_msg(message, output_fd=sys.stdout): - logging.debug(message) - output_fd.write(message) - output_fd.write("\n") - - -class Service: - def __init__(self, service_name, sstore=None): - self.service_name = service_name - self.steps = [] - self.output_fd = sys.stdout - - if sstore: - self.sstore = sstore - else: - self.sstore = sysrestore.StateFile('/var/lib/ipa/sysrestore') - - def set_output(self, fd): - self.output_fd = fd - - def stop(self): - stop(self.service_name) - - def start(self): - start(self.service_name) - - def restart(self): - restart(self.service_name) - - def is_running(self): - return is_running(self.service_name) - - def chkconfig_add(self): - chkconfig_add(self.service_name) - - def chkconfig_del(self): - chkconfig_del(self.service_name) - - def chkconfig_on(self): - chkconfig_on(self.service_name) - - def chkconfig_off(self): - chkconfig_off(self.service_name) - - def is_enabled(self): - return is_enabled(self.service_name) - - def backup_state(self, key, value): - self.sstore.backup_state(self.service_name, key, value) - - def restore_state(self, key): - return self.sstore.restore_state(self.service_name, key) - - def print_msg(self, message): - print_msg(message, self.output_fd) - - def step(self, message, method): - self.steps.append((message, method)) - - def start_creation(self, message): - self.print_msg(message) - - step = 0 - for (message, method) in self.steps: - self.print_msg(" [%d/%d]: %s" % (step+1, len(self.steps), message)) - method() - step += 1 - - self.print_msg("done configuring %s." % self.service_name) - - self.steps = [] - -class SimpleServiceInstance(Service): - def create_instance(self): - self.step("starting %s " % self.service_name, self.__start) - self.step("configuring %s to start on boot" % self.service_name, self.__enable) - self.start_creation("Configuring %s" % self.service_name) - - def __start(self): - self.backup_state("running", self.is_running()) - self.restart() - - def __enable(self): - self.chkconfig_add() - self.backup_state("enabled", self.is_enabled()) - self.chkconfig_on() - - def uninstall(self): - running = self.restore_state("running") - enabled = not self.restore_state("enabled") - - if not running is None and not running: - self.stop() - if not enabled is None and not enabled: - self.chkconfig_off() - self.chkconfig_del() diff --git a/ipa-server/man/Makefile.am b/ipa-server/man/Makefile.am deleted file mode 100644 index 244b06b8..00000000 --- a/ipa-server/man/Makefile.am +++ /dev/null @@ -1,27 +0,0 @@ -# This file will be processed with automake-1.7 to create Makefile.in - -AUTOMAKE_OPTIONS = 1.7 - -NULL= - -man1_MANS = \ - ipa-replica-install.1 \ - ipa-replica-manage.1 \ - ipa-replica-prepare.1 \ - ipa-server-certinstall.1 \ - ipa-server-install.1 \ - ipa-ldap-updater.1 \ - ipa-compat-manage.1 - -man8_MANS = \ - ipactl.8 \ - ipa_kpasswd.8 \ - ipa_webgui.8 - -install-data-hook: - @for i in $(man1_MANS) ; do gzip -f $(DESTDIR)$(man1dir)/$$i ; done - @for i in $(man8_MANS) ; do gzip -f $(DESTDIR)$(man8dir)/$$i ; done - -MAINTAINERCLEANFILES = \ - Makefile.in \ - $(NULL) diff --git a/ipa-server/man/ipa-compat-manage.1 b/ipa-server/man/ipa-compat-manage.1 deleted file mode 100644 index 767384a4..00000000 --- a/ipa-server/man/ipa-compat-manage.1 +++ /dev/null @@ -1,45 +0,0 @@ -.\" A man page for ipa-ldap-updater -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Simo Sorce -.\" -.TH "ipa-compat-manage" "1" "Dec 2 2008" "freeipa" "" -.SH "NAME" -ipa\-compat\-manage \- Enables or disables the schema compatibility plugin -.SH "SYNOPSIS" -ipa\-compat\-manage [options] -.SH "DESCRIPTION" -Run the command with the \fBenable\fR option to enable the compat plugin. - -Run the command with the \fBdisable\fR option to disable the compat plugin. - -In both cases the user will be prompted to provide the Directory Manager's password unless option \fB\-y\fR is used. - -Directory Server will need to be restarted after the schema compatibility plugin has been enabled. - -.SH "OPTIONS" -.TP -\fB\-d\fR, \fB\-\-debug\fR -Enable debug logging when more verbose output is needed -.TP -\fB\-y\fR \fIfile\fR -File containing the Directory Manager password -.SH "EXIT STATUS" -0 if the command was successful - -1 if an error occurred - -2 if the plugin is already in the required status (enabled or disabled) diff --git a/ipa-server/man/ipa-ldap-updater.1 b/ipa-server/man/ipa-ldap-updater.1 deleted file mode 100644 index 453ac758..00000000 --- a/ipa-server/man/ipa-ldap-updater.1 +++ /dev/null @@ -1,78 +0,0 @@ -.\" A man page for ipa-ldap-updater -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Rob Crittenden -.\" -.TH "ipa-ldap-updater" "1" "Sep 12 2008" "freeipa" "" -.SH "NAME" -ipa\-ldap\-updater \- Update the IPA LDAP configuration -.SH "SYNOPSIS" -ipa\-ldap\-updater [options] input_file(s) -ipa\-ldap\-updater [options] -.SH "DESCRIPTION" -Run with no file arguments, ipa\-ldap\-updater will process all files with the extension .update in /usr/share/ipa/updates. - -An update file describes an LDAP entry and a set of operations to be performed on that entry. It can be used to add new entries or modify existing entries. It cannot remove entries, just specific values in a given attribute. - -Blank lines and lines beginning with # are ignored. - -There are 4 keywords: - - * default: the starting value - * add: add a value (or values) to an attribute - * remove: remove a value (or values) from an attribute - * only: set an attribute to this - -Values is a comma\-separated field so multi\-values may be added at one time. Double or single quotes may be put around individual values that contain embedded commas. - -The difference between the default and add keywords is if the DN of the entry exists then default is ignored. So for updating something like schema, which will be under cn=schema, you must always use add (because cn=schema is guaranteed to exist). It will not re\-add the same information again and again. - -It alsos provide some things that can be templated such as architecture (for plugin paths), realm and domain name. - -The available template variables are: - - * $REALM \- the kerberos realm (EXAMPLE.COM) - * $FQDN \- the fully\-qualified domain name of the IPA server being updated (ipa.example.com) - * $DOMAIN \- the domain name (example.com) - * $SUFFIX \- the IPA LDAP suffix (dc=example,dc=com) - * $LIBARCH \- set to 64 on x86_64 systems to be used for plugin paths - * $TIME \- an integer representation of current time - -A few rules: - - 1. Only one rule per line - 2. Each line stands alone (e.g. an only followed by an only results in the last only being used) - 3. adding a value that exists is ok. The request is ignored, duplicate values are not added - 4. removing a value that doesn't exist is ok. It is simply ignored. - 5. If a DN doesn't exist it is created from the 'default' entry and all updates are applied - 6. If a DN does exist the default values are skipped - 7. Only the first rule on a line is respected -.SH "OPTIONS" -.TP -\fB\-d\fR, \fB\-\-debug -Enable debug logging when more verbose output is needed -.TP -\fB\-t\fR, \fB\-\-test\fR -Run through the update without changing anything. If changes are available then the command returns 2. If no updates are available it returns 0. -.TP -\fB\-y\fR -File containing the Directory Manager password -.SH "EXIT STATUS" -0 if the command was successful - -1 if an error occurred - -2 if run with in test mode (\-t) and updates are available diff --git a/ipa-server/man/ipa-replica-install.1 b/ipa-server/man/ipa-replica-install.1 deleted file mode 100644 index 674afd12..00000000 --- a/ipa-server/man/ipa-replica-install.1 +++ /dev/null @@ -1,41 +0,0 @@ -.\" A man page for ipa-replica-install -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Rob Crittenden -.\" -.TH "ipa-replica-install" "1" "Mar 14 2008" "freeipa" "" -.SH "NAME" -ipa\-replica\-install \- Create an IPA replica -.SH "SYNOPSIS" -ipa\-replica\-install [\fIOPTION\fR]... replica_file -.SH "DESCRIPTION" -Configures a new IPA server that is a replica of the server that generated it. Once it has been created it is an exact copy of the original IPA server and is an equal master. Changes made to any master are automatically replicated to other masters. - -The replica_file is created using the ipa\-replica\-prepare utility. -.SH "OPTIONS" -.TP -\fB\-d\fR, \fB\-\-debug -Enable debug logging when more verbose output is needed -.TP -\fB\-n\fR, \fB\-\-no\-ntp\fR -Do not configure NTP -.TP -\fB\-p\fR, \fB\-\-password\fR=\fIDM_PASSWORD\fR -Directory Manager (existing master) password -.SH "EXIT STATUS" -0 if the command was successful - -1 if an error occurred diff --git a/ipa-server/man/ipa-replica-manage.1 b/ipa-server/man/ipa-replica-manage.1 deleted file mode 100644 index 810cf1de..00000000 --- a/ipa-server/man/ipa-replica-manage.1 +++ /dev/null @@ -1,70 +0,0 @@ -.\" A man page for ipa-replica-manage -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Rob Crittenden -.\" -.TH "ipa-replica-manage" "1" "Mar 14 2008" "freeipa" "" -.SH "NAME" -ipa\-replica\-manage \- Manage an IPA replica -.SH "SYNOPSIS" -ipa\-replica\-manage [\fIOPTION\fR]... [add|del|list|init|synch] [SERVER] -.SH "DESCRIPTION" -Manages the replication agreements of an IPA server. -.TP -add \- Adds a new replication agreement between two existing IPA servers -.TP -del \- Removes a replication agreement -.TP -list \- Lists the hostnames that HOST IPA server has agreements with -.TP -init \- Forces a full initialization of the IPA server on SERVER from HOST -.TP -synch \- Immediately flush any data to be replicated to SERVER -.SH "OPTIONS" -.TP -\fB\-H HOST\fR, \fB\-\-host\fR=\fIHOST\fR -The IPA server to manage -.TP -\fB\-p DM_PASSWORD\fR, \fB\-\-password\fR=\fIDM_PASSWORD\fR -The Directory Manager password to use for authentication -.TP -\fB\-v\fR, \fB\-\-verbose\fR -Provide additional information -.TP -\fB\-\-winsync\fR -Specifies to create/use a Windows Sync Agreement -.TP -\fB\-\-port\fR=\fISERVER_PORT\fR -Port number of other server (default is 636, the LDAPS port) -.TP -\fB\-\-binddn\fR=\fIADMIN_DN\fR -Bind DN to use with remote server (default is cn=Directory Manager) - Be careful to quote this value on the command line -.TP -\fB--bindpw\fR=\fIADMIN_PWD\fR -Password for Bind DN to use with remote server (default is the DM_PASSWORD above) -.TP -\fB\-\-cacert\fR=\fI/path/to/cacertfile\fR -Full path and filename of CA certificate to use with TLS/SSL to the remote server - this CA certificate will be installed in the directory server's certificate database -.TP -\fB\-\-win-subtree\fR=\fIcn=Users,dc=example,dc=com\fR -DN of Windows subtree containing the users you want to sync (default cn=Users, - this is typically what Windows AD uses as the default value) - Be careful to quote this value on the command line -.TP -\fB\-\-passsync\fR=\fIPASSSYNC_PWD\fR -Password for the Windows PassSync user. -.SH "EXIT STATUS" -0 if the command was successful - -1 if an error occurred diff --git a/ipa-server/man/ipa-replica-prepare.1 b/ipa-server/man/ipa-replica-prepare.1 deleted file mode 100644 index 8eb49444..00000000 --- a/ipa-server/man/ipa-replica-prepare.1 +++ /dev/null @@ -1,48 +0,0 @@ -.\" A man page for ipa-replica-prepare -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Rob Crittenden -.\" -.TH "ipa-replica-prepare" "1" "Mar 14 2008" "freeipa" "" -.SH "NAME" -ipa\-replica\-prepare \- Create an IPA replica file -.SH "SYNOPSIS" -ipa\-replica\-prepare [\fIOPTION\fR]... hostname -.SH "DESCRIPTION" -Generates a replica file that may be used with ipa\-replica\-install to create a replica of an IPA server. - -A replica can only be created on an IPA server installed with ipa\-server\-install (the first server). - -You must provide the fully\-qualified hostname of the machine you want to install the replica on and a host\-specific replica_file will be created. It is host\-specific because SSL server certificates are generated as part of the process and they are specific to a particular hostname. - -Once the file has been created it will be named replica\-hostname. This file can then be moved across the network to the target machine and a new IPA replica setup by running ipa\-replica\-install replica\-hostname. -.SH "OPTIONS" -.TP -\fB\-\-dirsrv_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Directory Server SSL Certificate -.TP -\fB\-\-http_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Apache Server SSL Certificate -.TP -\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR -The password of the Directory Server PKCS#12 file -.TP -\fB\-\-http_pin\fR=\fIHTTP_PIN\fR -The password of the Apache Server PKCS#12 file -.SH "EXIT STATUS" -0 if the command was successful - -1 if an error occurred diff --git a/ipa-server/man/ipa-server-certinstall.1 b/ipa-server/man/ipa-server-certinstall.1 deleted file mode 100644 index 946ab9f8..00000000 --- a/ipa-server/man/ipa-server-certinstall.1 +++ /dev/null @@ -1,48 +0,0 @@ -.\" A man page for ipa-server-certinstall -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Rob Crittenden -.\" -.TH "ipa-server-certinstall" "1" "Mar 14 2008" "freeipa" "" -.SH "NAME" -ipa\-server\-certinstall \- Install new SSL server certificates -.SH "SYNOPSIS" -ipa\-server\-certinstall [\fIOPTION\fR]... PKCS12_FILE -.SH "DESCRIPTION" -Replace the current SSL Directory and/or Apache server certificate(s) with the certificate in the PKCS#12 file. - -PKCS#12 is a file format used to safely transport SSL certificates and public/private keypairs. - -They may be generated and managed using the NSS pk12util command or the OpenSSL pkcs12 command. - -The service(s) are not automatically restarted. In order to use the newly installed certificate(s) you will need to manually restart the Directory and/or Apache servers. -.SH "OPTIONS" -.TP -\fB\-d\fR, \fB\-\-dirsrv\fR -Install the certificate on the Directory Server -.TP -\fB\-w\fR, \fB\-\-http\fR -Install the certificate in the Apache Web Server -.TP -\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR -The password of the Directory Server PKCS#12 file -.TP -\fB\-\-http_pin\fR=\fIHTTP_PIN\fR -The password of the Apache Server PKCS#12 file -.SH "EXIT STATUS" -0 if the installation was successful - -1 if an error occurred diff --git a/ipa-server/man/ipa-server-install.1 b/ipa-server/man/ipa-server-install.1 deleted file mode 100644 index 8854f4e5..00000000 --- a/ipa-server/man/ipa-server-install.1 +++ /dev/null @@ -1,81 +0,0 @@ -.\" A man page for ipa-server-install -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Rob Crittenden -.\" -.TH "ipa-server-install" "1" "Mar 14 2008" "freeipa" "" -.SH "NAME" -ipa\-server\-install \- Configure an IPA server -.SH "SYNOPSIS" -ipa\-server\-install [\fIOPTION\fR]... -.SH "DESCRIPTION" -Configures the services needed by an IPA server. This includes setting up a Kerberos Key Distribution Center (KDC) with an LDAP back\-end, configuring Apache, configuring NTP and starting some IPA\-provided services: ipa_kpasswd and ipa_webgui. -.SH "OPTIONS" -.TP -\fB\-u\fR, \fB\-\-user\fR=\fIDS_USER\fR -The user that the Directory Server will run as -.TP -\fB\-r\fR, \fB\-\-realm\fR=\fIREALM_NAME\fR -The Kerberos realm name for the IPA server -.TP -\fB\-n\fR, \fB\-\-domain\fR=\fIDOMAIN_NAME\fR -Your DNS domain name -.TP -\fB\-p\fR, \fB\-\-ds\-password\fR=\fIDM_PASSWORD\fR -The password to be used by the Directory Server for the Directory Manager user -.TP -\fB\-P\fR, \fB\-\-master\-password\fR=\fIMASTER_PASSWORD\fR -The kerberos master password (normally autogenerated) -.TP -\fB\-a\fR, \fB\-\-admin\-password\fR=\fIADMIN_PASSWORD\fR -The password for the IPA admin user -.TP -\fB\-d\fR, \fB\-\-debug\fR -Enable debug logging when more verbose output is needed -.TP -\fB\-\-hostname\fR=\fIHOST_NAME\fR -The fully\-qualified DNS name of this server -.TP -\fB\-\-ip\-address\fR=\fIIP_ADDRESS\fR -The IP address of this server -.TP -\fB\-U\fR, \fB\-\-unattended\fR -An unattended installation that will never prompt for user input -.TP -\fB\-\-setup\-bind\fR -Generate a DNS zone file that contains auto\-discovery records for this IPA server -.TP -\fB\-n\fR, \fB\-\-no\-ntp\fR -Do not configure NTP -\fB\-U\fR, \fB\-\-uninstall\fR -Uninstall an existing IPA installation -.TP -\fB\-\-dirsrv_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Directory Server SSL Certificate -.TP -\fB\-\-http_pkcs12\fR=\fIFILE\fR -PKCS#12 file containing the Apache Server SSL Certificate -.TP -\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR -The password of the Directory Server PKCS#12 file -.TP -\fB\-\-http_pin\fR=\fIHTTP_PIN\fR -The password of the Apache Server PKCS#12 file -.PP -.SH "EXIT STATUS" -0 if the installation was successful - -1 if an error occurred diff --git a/ipa-server/man/ipa_kpasswd.8 b/ipa-server/man/ipa_kpasswd.8 deleted file mode 100644 index f2ba3dd9..00000000 --- a/ipa-server/man/ipa_kpasswd.8 +++ /dev/null @@ -1,36 +0,0 @@ -.\" A man page for ipa_kpasswd -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Rob Crittenden -.\" -.TH "ipa_kpasswd" "8" "Mar 14 2008" "freeipa" "" -.SH "NAME" -ipa_kpasswd \- Proxy Kerberos password change requests -.SH "SYNOPSIS" -ipa_kpasswd -.SH "DESCRIPTION" -Implementation of the kpasswd protocol (RFC 3244). - -It is used to proxy password change operations to Directory Server. -.SH "ENVIRONMENT VARIABLES" -.TP -KRB5_KTNAME -Location of the keytab to be used by ipa_kpasswd -.TP -IPA_KPASSWD_DEBUG -Enable additional syslog output from ipa_kpasswd. Setting greater than 0 gets basic output. Setting higher than 100 gets more. -.SH "EXIT STATUS" -\-1 if an error occurred diff --git a/ipa-server/man/ipa_webgui.8 b/ipa-server/man/ipa_webgui.8 deleted file mode 100644 index 20545363..00000000 --- a/ipa-server/man/ipa_webgui.8 +++ /dev/null @@ -1,37 +0,0 @@ -.\" A man page for ipa_webgui -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Rob Crittenden -.\" -.TH "ipa_webgui" "8" "Mar 14 2008" "freeipa" "" -.SH "NAME" -ipa_webgui \- Start the IPA Web User Interface -.SH "SYNOPSIS" -ipa_webgui [\fIOPTION\fR]... - -.SH "DESCRIPTION" -Used to start the TurboGears web user interface for IPA -.SH "OPTIONS" -.TP -\fB\-f\fR, \fB\-\-foreground\fR -Remain in the foreground instead of becoming a daemon. -.TP -\fB\-d\fR, \fB\-\-debug\fR -.TP -Increase the amount of logging and print it to stdout instead of logging to /var/log/ipa_error.log - -.SH "EXIT STATUS" -1 if an error occurred diff --git a/ipa-server/man/ipactl.8 b/ipa-server/man/ipactl.8 deleted file mode 100644 index a4797f96..00000000 --- a/ipa-server/man/ipactl.8 +++ /dev/null @@ -1,37 +0,0 @@ -.\" A man page for ipactl -.\" Copyright (C) 2008 Red Hat, Inc. -.\" -.\" This is free software; you can redistribute it and/or modify it under -.\" the terms of the GNU Library 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 Library General Public -.\" License along with this program; if not, write to the Free Software -.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -.\" -.\" Author: Rob Crittenden -.\" -.TH "ipactl" "8" "Mar 14 2008" "freeipa" "" -.SH "NAME" -ipactl \- IPA Server Control Interface -.SH "SYNOPSIS" -ipactl \fIcommand\fR -.SH "DESCRIPTION" -A tool to help an administer control an IPA environment. - -IPA glues several discrete services together to work in concert and the order that these services are started and stopped is important. ipactl ensures that they are started and stopped in the correct order. -.SH "OPTIONS" -.TP -start -Start all of the services that make up IPA -.TP -stop -Stop all of the services that make up IPA -.TP -restart -Stop then start all of the services that make up IPA diff --git a/ipa-server/selinux/Makefile b/ipa-server/selinux/Makefile deleted file mode 100644 index a662d2fd..00000000 --- a/ipa-server/selinux/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -SUBDIRS = ipa_webgui ipa_kpasswd -POLICY_MAKEFILE = /usr/share/selinux/devel/Makefile -POLICY_DIR = $(DESTDIR)/usr/share/selinux/targeted - -all: - if [ ! -e $(POLICY_MAKEFILE) ]; then echo "You need to install the SELinux development tools (selinux-policy-devel)" && exit 1; fi - - @for subdir in $(SUBDIRS); do \ - (cd $$subdir && $(MAKE) -f $(POLICY_MAKEFILE) $@) || exit 1; \ - done - -clean: - @for subdir in $(SUBDIRS); do \ - (cd $$subdir && $(MAKE) -f $(POLICY_MAKEFILE) $@) || exit 1; \ - done - -distclean: clean - rm -f ipa-server-selinux.spec - -maintainer-clean: distclean - -install: all - install -d $(POLICY_DIR) - install -m 644 ipa_webgui/ipa_webgui.pp $(POLICY_DIR) - install -m 644 ipa_kpasswd/ipa_kpasswd.pp $(POLICY_DIR) - -load: - /usr/sbin/semodule -i ipa_webgui/ipa_webgui.pp ipa_kpasswd/ipa_kpasswd.pp diff --git a/ipa-server/selinux/ipa-server-selinux.spec.in b/ipa-server/selinux/ipa-server-selinux.spec.in deleted file mode 100644 index 3387553a..00000000 --- a/ipa-server/selinux/ipa-server-selinux.spec.in +++ /dev/null @@ -1,86 +0,0 @@ -%define POLICYCOREUTILSVER 1.33.12-1 - -Name: ipa-server-selinux -Version: __VERSION__ -Release: __RELEASE__%{?dist} -Summary: IPA server SELinux policies - -Group: System Environment/Base -License: GPLv2 -URL: http://www.freeipa.org -Source0: ipa-server-%{version}.tgz -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -BuildArch: noarch - -BuildRequires: selinux-policy-devel m4 make policycoreutils >= %{POLICYCOREUTILSVER} -Requires(pre): policycoreutils >= %{POLICYCOREUTILSVER} libsemanage - -%description -SELinux policy for ipa-server - -%prep -%setup -n ipa-server-%{version} -q - -%build -cd selinux -make - -%clean -%{__rm} -fR %{buildroot} - -%install -%{__rm} -fR %{buildroot} -cd selinux -install -d %{buildroot}/%{_usr}/share/selinux/targeted/ -make DESTDIR=%{buildroot} install - -%files -%{_usr}/share/selinux/targeted/ipa_webgui.pp -%{_usr}/share/selinux/targeted/ipa_kpasswd.pp - - -%define saveFileContext() \ -if [ -s /etc/selinux/config ]; then \ - . %{_sysconfdir}/selinux/config; \ - FILE_CONTEXT=%{_sysconfdir}/selinux/%1/contexts/files/file_contexts; \ - if [ "${SELINUXTYPE}" == %1 -a -f ${FILE_CONTEXT} ]; then \ - cp -f ${FILE_CONTEXT} ${FILE_CONTEXT}.%{name}; \ - fi \ -fi; - -%define relabel() \ -. %{_sysconfdir}/selinux/config; \ -FILE_CONTEXT=%{_sysconfdir}/selinux/%1/contexts/files/file_contexts; \ -selinuxenabled; \ -if [ $? == 0 -a "${SELINUXTYPE}" == %1 -a -f ${FILE_CONTEXT}.%{name} ]; then \ - fixfiles -C ${FILE_CONTEXT}.%{name} restore; \ - rm -f ${FILE_CONTEXT}.%name; \ -fi; - -%pre -%saveFileContext targeted - -%post -semodule -s targeted -i /usr/share/selinux/targeted/ipa_webgui.pp /usr/share/selinux/targeted/ipa_kpasswd.pp -%relabel targeted - -%preun -if [ $1 = 0 ]; then -%saveFileContext targeted -fi - -%postun -if [ $1 = 0 ]; then -semodule -s targeted -r ipa_webgui ipa_kpasswd -%relabel targeted -fi - -%changelog -* Thu Apr 3 2008 Rob Crittenden - 1.0.0-1 -- Version bump for release - -* Thu Feb 21 2008 Rob Crittenden - 0.99.0-1 -- Version bump for release - -* Thu Jan 17 2008 Karl MacMillan - 0.6.0-1 -- Initial version diff --git a/ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.fc b/ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.fc deleted file mode 100644 index 2dcf827d..00000000 --- a/ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.fc +++ /dev/null @@ -1,9 +0,0 @@ -# -# /usr -# -/usr/sbin/ipa_kpasswd -- gen_context(system_u:object_r:ipa_kpasswd_exec_t,s0) - -# -# /var -# -/var/cache/ipa/kpasswd(/.*)? gen_context(system_u:object_r:ipa_kpasswd_ccache_t,s0) diff --git a/ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.te b/ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.te deleted file mode 100644 index b5203a4e..00000000 --- a/ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.te +++ /dev/null @@ -1,71 +0,0 @@ -policy_module(ipa_kpasswd, 1.0) - -######################################## -# -# Declarations -# - -type ipa_kpasswd_t; -type ipa_kpasswd_exec_t; -type ipa_kpasswd_var_run_t; -type ipa_kpasswd_ccache_t; -init_daemon_domain(ipa_kpasswd_t, ipa_kpasswd_exec_t) - -######################################## -# -# IPA kpasswd local policy -# - -allow ipa_kpasswd_t self:capability { sys_nice dac_override }; -allow ipa_kpasswd_t self:tcp_socket create_stream_socket_perms; -allow ipa_kpasswd_t self:udp_socket create_socket_perms; - -files_read_etc_files(ipa_kpasswd_t) -files_search_usr(ipa_kpasswd_t) - -files_pid_file(ipa_kpasswd_var_run_t); -allow ipa_kpasswd_t ipa_kpasswd_var_run_t:file manage_file_perms; -files_pid_filetrans(ipa_kpasswd_t,ipa_kpasswd_var_run_t,file) - -auth_use_nsswitch(ipa_kpasswd_t) - -libs_use_ld_so(ipa_kpasswd_t) -libs_use_shared_libs(ipa_kpasswd_t) - -logging_send_syslog_msg(ipa_kpasswd_t) - -miscfiles_read_localization(ipa_kpasswd_t) - -kerberos_use(ipa_kpasswd_t) -kerberos_manage_host_rcache(ipa_kpasswd_t) -kerberos_read_kdc_config(ipa_kpasswd_t) - -kernel_read_system_state(ipa_kpasswd_t) - -# /var/cache/ipa/kpasswd -files_type(ipa_kpasswd_ccache_t) -manage_dirs_pattern(ipa_kpasswd_t, ipa_kpasswd_ccache_t, ipa_kpasswd_ccache_t) -manage_files_pattern(ipa_kpasswd_t, ipa_kpasswd_ccache_t, ipa_kpasswd_ccache_t) -files_var_filetrans(ipa_kpasswd_t, ipa_kpasswd_ccache_t,dir) - -kernel_read_network_state(ipa_kpasswd_t) -kernel_read_network_state_symlinks(ipa_kpasswd_t) - -corenet_tcp_sendrecv_all_if(ipa_kpasswd_t) -corenet_udp_sendrecv_all_if(ipa_kpasswd_t) -corenet_raw_sendrecv_all_if(ipa_kpasswd_t) -corenet_tcp_sendrecv_all_nodes(ipa_kpasswd_t) -corenet_udp_sendrecv_all_nodes(ipa_kpasswd_t) -corenet_raw_sendrecv_all_nodes(ipa_kpasswd_t) -corenet_tcp_sendrecv_all_ports(ipa_kpasswd_t) -corenet_udp_sendrecv_all_ports(ipa_kpasswd_t) -corenet_non_ipsec_sendrecv(ipa_kpasswd_t) -corenet_tcp_bind_all_nodes(ipa_kpasswd_t) -corenet_udp_bind_all_nodes(ipa_kpasswd_t) -corenet_tcp_bind_kerberos_admin_port(ipa_kpasswd_t) -corenet_udp_bind_kerberos_admin_port(ipa_kpasswd_t) -require { - type krb5kdc_conf_t; -}; - -allow ipa_kpasswd_t krb5kdc_conf_t:dir search_dir_perms; diff --git a/ipa-server/selinux/ipa_webgui/ipa_webgui.fc b/ipa-server/selinux/ipa_webgui/ipa_webgui.fc deleted file mode 100644 index c9dfb2b5..00000000 --- a/ipa-server/selinux/ipa_webgui/ipa_webgui.fc +++ /dev/null @@ -1,11 +0,0 @@ -# -# /usr -# -/usr/sbin/ipa_webgui -- gen_context(system_u:object_r:ipa_webgui_exec_t,s0) - - -# -# /var -# -/var/log/ipa_error\.log -- gen_context(system_u:object_r:ipa_webgui_log_t,s0) -/var/cache/ipa/sessions(/.*)? gen_context(system_u:object_r:ipa_cache_t,s0) diff --git a/ipa-server/selinux/ipa_webgui/ipa_webgui.te b/ipa-server/selinux/ipa_webgui/ipa_webgui.te deleted file mode 100644 index a9818d82..00000000 --- a/ipa-server/selinux/ipa_webgui/ipa_webgui.te +++ /dev/null @@ -1,97 +0,0 @@ -policy_module(ipa_webgui, 1.0) - -######################################## -# -# Declarations -# - -require { - type sbin_t; -} -type ipa_webgui_t; -type ipa_webgui_exec_t; -type ipa_webgui_var_run_t; -type ipa_cache_t; -files_type(ipa_cache_t) -init_daemon_domain(ipa_webgui_t, ipa_webgui_exec_t) - -type ipa_webgui_log_t; -logging_log_file(ipa_webgui_log_t) - -######################################## -# -# IPA webgui local policy -# - -allow ipa_webgui_t self:tcp_socket create_stream_socket_perms; -allow ipa_webgui_t self:udp_socket create_socket_perms; -allow ipa_webgui_t self:process setfscreate; - -# This is how the kerberos credential cache is passed to -# the ipa_webgui process. Unfortunately, the kerberos -# libraries seem to insist that it be open rw. To top it -# all off there is no interface for this either. -require { - type httpd_tmp_t; -} -allow ipa_webgui_t httpd_tmp_t:file read_file_perms; -dontaudit ipa_webgui_t httpd_tmp_t:file write; - -apache_search_sys_content(ipa_webgui_t) -apache_read_config(ipa_webgui_t) - -corecmd_list_bin(ipa_webgui_t) - -miscfiles_read_localization(ipa_webgui_t) - -files_list_usr(ipa_webgui_t) -files_read_etc_files(ipa_webgui_t) -files_read_usr_files(ipa_webgui_t) -files_read_usr_symlinks(ipa_webgui_t) -files_search_etc(ipa_webgui_t) -files_search_tmp(ipa_webgui_t) - -files_pid_file(ipa_webgui_var_run_t) -allow ipa_webgui_t ipa_webgui_var_run_t:file manage_file_perms; -files_pid_filetrans(ipa_webgui_t,ipa_webgui_var_run_t,file) - -kerberos_read_config(ipa_webgui_t) - -kernel_read_system_state(ipa_webgui_t) - -auth_use_nsswitch(ipa_webgui_t) - -libs_use_ld_so(ipa_webgui_t) -libs_use_shared_libs(ipa_webgui_t) - -logging_search_logs(ipa_webgui_t) -logging_log_filetrans(ipa_webgui_t,ipa_webgui_log_t,file) -allow ipa_webgui_t ipa_webgui_log_t:file rw_file_perms; - -allow ipa_webgui_t self:capability { setgid setuid }; - -# /var/cache/ipa/sessions -files_type(ipa_cache_t) -manage_dirs_pattern(ipa_webgui_t, ipa_cache_t, ipa_cache_t) -manage_files_pattern(ipa_webgui_t, ipa_cache_t, ipa_cache_t) -files_var_filetrans(ipa_webgui_t, ipa_cache_t,dir) - -userdom_dontaudit_search_sysadm_home_dirs(ipa_webgui_t) - -corenet_tcp_sendrecv_all_if(ipa_webgui_t) -corenet_udp_sendrecv_all_if(ipa_webgui_t) -corenet_raw_sendrecv_all_if(ipa_webgui_t) -corenet_tcp_sendrecv_all_nodes(ipa_webgui_t) -corenet_udp_sendrecv_all_nodes(ipa_webgui_t) -corenet_raw_sendrecv_all_nodes(ipa_webgui_t) -corenet_tcp_sendrecv_all_ports(ipa_webgui_t) -corenet_udp_sendrecv_all_ports(ipa_webgui_t) -corenet_non_ipsec_sendrecv(ipa_webgui_t) -corenet_tcp_bind_all_nodes(ipa_webgui_t) -corenet_udp_bind_all_nodes(ipa_webgui_t) -corenet_tcp_bind_http_cache_port(ipa_webgui_t) -corenet_tcp_connect_http_cache_port(ipa_webgui_t) -corenet_tcp_connect_ldap_port(ipa_webgui_t) - -corecmd_search_sbin(ipa_webgui_t) -allow ipa_webgui_t sbin_t:dir read; diff --git a/ipa-server/version.m4.in b/ipa-server/version.m4.in deleted file mode 100644 index 5ddc8cea..00000000 --- a/ipa-server/version.m4.in +++ /dev/null @@ -1 +0,0 @@ -define([IPA_VERSION], [__VERSION__]) diff --git a/ipa-server/xmlrpc-server/ipa-rewrite.conf b/ipa-server/xmlrpc-server/ipa-rewrite.conf deleted file mode 100644 index ef494300..00000000 --- a/ipa-server/xmlrpc-server/ipa-rewrite.conf +++ /dev/null @@ -1,19 +0,0 @@ -# VERSION 2 - DO NOT REMOVE THIS LINE - -RewriteEngine on - -# By default forward all requests to /ipa. If you don't want IPA -# to be the default on your web server comment this line out. You will -# need to modify ipa_webgui.cfg as well. -RewriteRule ^/$$ https://$FQDN/ipa/ui [L,NC,R=301] - -# Redirect to the fully-qualified hostname. Not redirecting to secure -# port so configuration files can be retrieved without requiring SSL. -RewriteCond %{HTTP_HOST} !^$FQDN$$ [NC] -RewriteRule ^/ipa/(.*) http://$FQDN/ipa/$$1 [L,R=301] - -# Redirect to the secure port if not displaying an error or retrieving -# configuration. -RewriteCond %{SERVER_PORT} !^443$$ -RewriteCond %{REQUEST_URI} !^/ipa/(errors|config) -RewriteRule ^/ipa/(.*) https://$FQDN/ipa/$$1 [L,R=301,NC] diff --git a/ipa-server/xmlrpc-server/ipa.conf b/ipa-server/xmlrpc-server/ipa.conf deleted file mode 100644 index 85b4543a..00000000 --- a/ipa-server/xmlrpc-server/ipa.conf +++ /dev/null @@ -1,109 +0,0 @@ -# -# VERSION 2 - DO NOT REMOVE THIS LINE -# -# LoadModule auth_kerb_module modules/mod_auth_kerb.so - -ProxyRequests Off - -# ipa-rewrite.conf is loaded separately - -# This is required so the auto-configuration works with Firefox 2+ -AddType application/java-archive jar - - - AuthType Kerberos - AuthName "Kerberos Login" - KrbMethodNegotiate on - KrbMethodK5Passwd off - KrbServiceName HTTP - KrbAuthRealms $REALM - Krb5KeyTab /etc/httpd/conf/ipa.keytab - KrbSaveCredentials on - Require valid-user - ErrorDocument 401 /ipa/errors/unauthorized.html - RewriteEngine on - Order deny,allow - Allow from all - - RequestHeader set X-Forwarded-Keytab %{KRB5CCNAME}e - - # RequestHeader unset Authorization - - -# The URI's with a trailing ! are those that aren't handled by the proxy -ProxyPass /ipa/ui http://localhost:8080/ipa/ui -ProxyPassReverse /ipa/ui http://localhost:8080/ipa/ui - -# Configure the XML-RPC service -Alias /ipa/xml "/usr/share/ipa/ipaserver/XMLRPC" - -# This is where we redirect on failed auth -Alias /ipa/errors "/usr/share/ipa/html" - -# For the MIT Windows config files -Alias /ipa/config "/usr/share/ipa/html" - - - AuthType Kerberos - AuthName "Kerberos Login" - KrbMethodNegotiate on - KrbMethodK5Passwd off - KrbServiceName HTTP - KrbAuthRealms $REALM - Krb5KeyTab /etc/httpd/conf/ipa.keytab - KrbSaveCredentials on - Require valid-user - ErrorDocument 401 /ipa/errors/unauthorized.html - - SetHandler mod_python - PythonHandler ipaxmlrpc - - PythonDebug Off - - PythonOption IPADebug Off - - # this is pointless to use since it would just reload ipaxmlrpc.py - PythonAutoReload Off - - -# Do no authentication on the directory that contains error messages - - AllowOverride None - Satisfy Any - Allow from all - - -# Protect our CGIs - - AuthType Kerberos - AuthName "Kerberos Login" - KrbMethodNegotiate on - KrbMethodK5Passwd off - KrbServiceName HTTP - KrbAuthRealms $REALM - Krb5KeyTab /etc/httpd/conf/ipa.keytab - KrbSaveCredentials on - Require valid-user - ErrorDocument 401 /ipa/errors/unauthorized.html - - -#Alias /ipatest "/usr/share/ipa/ipatest" - -# -# AuthType Kerberos -# AuthName "Kerberos Login" -# KrbMethodNegotiate on -# KrbMethodK5Passwd off -# KrbServiceName HTTP -# KrbAuthRealms $REALM -# Krb5KeyTab /etc/httpd/conf/ipa.keytab -# KrbSaveCredentials on -# Require valid-user -# ErrorDocument 401 /ipa/errors/unauthorized.html -# -# SetHandler mod_python -# PythonHandler test_mod_python -# -# PythonDebug Off -# -# diff --git a/ipa-server/xmlrpc-server/ssbrowser.html b/ipa-server/xmlrpc-server/ssbrowser.html deleted file mode 100644 index 37dbcb40..00000000 --- a/ipa-server/xmlrpc-server/ssbrowser.html +++ /dev/null @@ -1,68 +0,0 @@ - - - -Browser Kerberos Setup - - -

Browser Kerberos Setup

-

Internet Explorer Configuration

-

Once you are able to log into the workstation with your kerberos key you should be able to use that ticket in Internet Explorer. For illustration purposes his page will use EXAMPLE.COM as the sample realm and example.com for the domain. -

-
  • Login to the Windows machine using an account of domain EXAMPLE.COM - -
  • In Internet Explorer, click Tools, and then click Internet Options. -
-
  1. Click the Security tab. -
  2. Click Local intranet. -
  3. Click Sites -
  4. Click Advanced -
  5. Add *.example.com to the list - -
-
  • In Internet Explorer, click Tools, and then click Internet Options. -
-
  1. Click the Security tab. -
  2. Click Local intranet. -
  3. Click Custom Level -
  4. Select Automatic logon only in Intranet zone. -
-
  • Visit a kerberized web site using IE. You must use the fully-qualified DN in the URL. -
  • If all went right, it should work. - -
-

Firefox Configuration

-

-You can configure Firefox to use Kerberos for Single Sign-on. In order for this functionality to work correctly, you need to configure your web browser to send your Kerberos credentials to the appropriate KDC.The following section describes the configuration changes and other requirements to achieve this. -

-
    -
  1. -

    -In the address bar of Firefox, type about:config to display the list of current configuration options. -

    -
  2. - -
  3. -

    -In the Filter field, type negotiate to restrict the list of options. -

    -
  4. -
  5. -

    -Double-click the network.negotiate-auth.trusted-uris entry to display the Enter string value dialog box. - -

    -
  6. -
  7. -

    -Enter the name of the domain against which you want to authenticate, for example, .example.com. -

    -
  8. -
  9. -

    -Repeat the above procedure for the network.negotiate-auth.delegation-uris entry, using the same domain. -

    -
  10. - -
- - diff --git a/ipa-server/xmlrpc-server/unauthorized.html b/ipa-server/xmlrpc-server/unauthorized.html deleted file mode 100644 index 6ba8a99e..00000000 --- a/ipa-server/xmlrpc-server/unauthorized.html +++ /dev/null @@ -1,28 +0,0 @@ - -Kerberos Authentication Failed</h2> -<body> -<h2>Kerberos Authentication Failed</h2> -<p> -Unable to verify your Kerberos credentials. Please make sure -that you have valid Kerberos tickets (obtainable via kinit), and that you -have <a href="/ipa/errors/ssbrowser.html">configured your -browser correctly</a>. If you are still unable to access -the IPA Web interface, please contact the helpdesk on for additional assistance. -</p> -<p> -Import the <a href="/ipa/errors/ca.crt">IPA Certificate Authority</a>. -</p> -<p> -<script type="text/javascript"> - if (navigator.userAgent.indexOf("Firefox") != -1 || - navigator.userAgent.indexOf("SeaMonkey") != -1) - { - document.write("<p>You can automatically configure your browser to work with Kerberos by importing the Certificate Authority above and clicking on the Configure Browser button.</p>"); - document.write("<p>You <strong>must</strong> reload this page after importing the Certificate Authority for the automatic settings to work</p>"); - document.write("<object data=\"jar:/ipa/errors/configure.jar!/preferences.html\" type=\"text/html\"><\/object"); - } -</script> -</p> -</ul> -</body> -</html> diff --git a/ipaserver/install/Makefile.am b/ipaserver/install/Makefile.am new file mode 100644 index 00000000..999dcf24 --- /dev/null +++ b/ipaserver/install/Makefile.am @@ -0,0 +1,24 @@ +NULL = + +appdir = $(pythondir)/ipaserver +app_PYTHON = \ + __init__.py \ + bindinstance.py \ + dsinstance.py \ + ipaldap.py \ + krbinstance.py \ + httpinstance.py \ + ntpinstance.py \ + service.py \ + installutils.py \ + replication.py \ + certs.py \ + ldapupdate.py \ + $(NULL) + +EXTRA_DIST = \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/ipaserver/install/__init__.py b/ipaserver/install/__init__.py new file mode 100644 index 00000000..ef86f9ec --- /dev/null +++ b/ipaserver/install/__init__.py @@ -0,0 +1,21 @@ +# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com> +# see inline +# +# Copyright (C) 2007 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 or later +# +# 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 +# + +__all__ = ["dsinstance", "krbinstance"] diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py new file mode 100644 index 00000000..5badf860 --- /dev/null +++ b/ipaserver/install/bindinstance.py @@ -0,0 +1,156 @@ +# Authors: Simo Sorce <ssorce@redhat.com> +# +# Copyright (C) 2007 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 +# + +import string +import tempfile +import shutil +import os +import socket +import logging + +import service +from ipa import sysrestore +from ipa import ipautil + +def check_inst(): + # So far this file is always present in both RHEL5 and Fedora if all the necessary + # bind packages are installed (RHEL5 requires also the pkg: caching-nameserver) + if not os.path.exists('/etc/named.rfc1912.zones'): + return False + + return True + +class BindInstance(service.Service): + def __init__(self, fstore=None): + service.Service.__init__(self, "named") + self.fqdn = None + self.domain = None + self.host = None + self.ip_address = None + self.realm = None + self.sub_dict = None + + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + + def setup(self, fqdn, ip_address, realm_name, domain_name): + self.fqdn = fqdn + self.ip_address = ip_address + self.realm = realm_name + self.domain = domain_name + self.host = fqdn.split(".")[0] + + self.__setup_sub_dict() + + def create_sample_bind_zone(self): + bind_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.zone.db.template", self.sub_dict) + [bind_fd, bind_name] = tempfile.mkstemp(".db","sample.zone.") + os.write(bind_fd, bind_txt) + os.close(bind_fd) + print "Sample zone file for bind has been created in "+bind_name + + def create_instance(self): + + try: + self.stop() + except: + pass + + self.step("Setting up our zone", self.__setup_zone) + self.step("Setting up named.conf", self.__setup_named_conf) + + self.step("restarting named", self.__start) + self.step("configuring named to start on boot", self.__enable) + + self.step("Changing resolv.conf to point to ourselves", self.__setup_resolv_conf) + self.start_creation("Configuring bind:") + + def __start(self): + try: + self.backup_state("running", self.is_running()) + self.restart() + except: + print "named service failed to start" + + def __enable(self): + self.backup_state("enabled", self.is_running()) + self.chkconfig_on() + + def __setup_sub_dict(self): + self.sub_dict = dict(FQDN=self.fqdn, + IP=self.ip_address, + DOMAIN=self.domain, + HOST=self.host, + REALM=self.realm) + + def __setup_zone(self): + self.backup_state("domain", self.domain) + zone_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.zone.db.template", self.sub_dict) + self.fstore.backup_file('/var/named/'+self.domain+'.zone.db') + zone_fd = open('/var/named/'+self.domain+'.zone.db', 'w') + zone_fd.write(zone_txt) + zone_fd.close() + + def __setup_named_conf(self): + self.fstore.backup_file('/etc/named.conf') + named_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.named.conf.template", self.sub_dict) + named_fd = open('/etc/named.conf', 'w') + named_fd.seek(0) + named_fd.truncate(0) + named_fd.write(named_txt) + named_fd.close() + + def __setup_resolv_conf(self): + self.fstore.backup_file('/etc/resolv.conf') + resolv_txt = "search "+self.domain+"\nnameserver "+self.ip_address+"\n" + resolv_fd = open('/etc/resolv.conf', 'w') + resolv_fd.seek(0) + resolv_fd.truncate(0) + resolv_fd.write(resolv_txt) + resolv_fd.close() + + def uninstall(self): + running = self.restore_state("running") + enabled = self.restore_state("enabled") + domain = self.restore_state("domain") + + if not running is None: + self.stop() + + if not domain is None: + try: + self.fstore.restore_file(os.path.join ("/var/named/", domain + ".zone.db")) + except ValueError, error: + logging.debug(error) + pass + + for f in ["/etc/named.conf", "/etc/resolv.conf"]: + try: + self.fstore.restore_file(f) + except ValueError, error: + logging.debug(error) + pass + + if not enabled is None and not enabled: + self.chkconfig_off() + + if not running is None and running: + self.start() diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py new file mode 100644 index 00000000..8cb1d088 --- /dev/null +++ b/ipaserver/install/certs.py @@ -0,0 +1,424 @@ +# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com> +# +# Copyright (C) 2007 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 +# + +import os, stat, subprocess, re +import sha +import errno +import tempfile +import shutil + +from ipa import sysrestore +from ipa import ipautil + +CA_SERIALNO="/var/lib/ipa/ca_serialno" + +class CertDB(object): + def __init__(self, dir, fstore=None): + self.secdir = dir + + self.noise_fname = self.secdir + "/noise.txt" + self.passwd_fname = self.secdir + "/pwdfile.txt" + self.certdb_fname = self.secdir + "/cert8.db" + self.keydb_fname = self.secdir + "/key3.db" + self.secmod_fname = self.secdir + "/secmod.db" + self.cacert_fname = self.secdir + "/cacert.asc" + self.pk12_fname = self.secdir + "/cacert.p12" + self.pin_fname = self.secdir + "/pin.txt" + self.reqdir = tempfile.mkdtemp('', 'ipa-', '/var/lib/ipa') + self.certreq_fname = self.reqdir + "/tmpcertreq" + self.certder_fname = self.reqdir + "/tmpcert.der" + + # Making this a starting value that will generate + # unique values for the current DB is the + # responsibility of the caller for now. In the + # future we might automatically determine this + # for a given db. + self.cur_serial = -1 + + self.cacert_name = "CA certificate" + self.valid_months = "120" + self.keysize = "1024" + + # We are going to set the owner of all of the cert + # files to the owner of the containing directory + # instead of that of the process. This works when + # this is called by root for a daemon that runs as + # a normal user + mode = os.stat(self.secdir) + self.uid = mode[stat.ST_UID] + self.gid = mode[stat.ST_GID] + + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + + def __del__(self): + shutil.rmtree(self.reqdir, ignore_errors=True) + + def set_serial_from_pkcs12(self): + """A CA cert was loaded from a PKCS#12 file. Set up our serial file""" + + self.cur_serial = self.find_cacert_serial() + try: + f=open(CA_SERIALNO,"w") + f.write(str(self.cur_serial)) + f.close() + except IOError, e: + raise RuntimeError("Unable to increment serial number: %s" % str(e)) + + def next_serial(self): + try: + f=open(CA_SERIALNO,"r") + r = f.readline() + try: + self.cur_serial = int(r) + 1 + except ValueError: + raise RuntimeError("The value in %s is not an integer" % CA_SERIALNO) + f.close() + except IOError, e: + if e.errno == errno.ENOENT: + self.cur_serial = 1000 + f=open(CA_SERIALNO,"w") + f.write(str(self.cur_serial)) + f.close() + else: + raise RuntimeError("Unable to determine serial number: %s" % str(e)) + + try: + f=open(CA_SERIALNO,"w") + f.write(str(self.cur_serial)) + f.close() + except IOError, e: + raise RuntimeError("Unable to increment serial number: %s" % str(e)) + + return str(self.cur_serial) + + def set_perms(self, fname, write=False): + os.chown(fname, self.uid, self.gid) + perms = stat.S_IRUSR + if write: + perms |= stat.S_IWUSR + os.chmod(fname, perms) + + def gen_password(self): + return sha.sha(ipautil.ipa_generate_password()).hexdigest() + + def run_certutil(self, args, stdin=None): + new_args = ["/usr/bin/certutil", "-d", self.secdir] + new_args = new_args + args + return ipautil.run(new_args, stdin) + + def run_signtool(self, args, stdin=None): + new_args = ["/usr/bin/signtool", "-d", self.secdir] + new_args = new_args + args + ipautil.run(new_args, stdin) + + def create_noise_file(self): + ipautil.backup_file(self.noise_fname) + f = open(self.noise_fname, "w") + f.write(self.gen_password()) + self.set_perms(self.noise_fname) + + def create_passwd_file(self, passwd=None): + ipautil.backup_file(self.passwd_fname) + f = open(self.passwd_fname, "w") + if passwd is not None: + f.write("%s\n" % passwd) + else: + f.write(self.gen_password()) + f.close() + self.set_perms(self.passwd_fname) + + def create_certdbs(self): + ipautil.backup_file(self.certdb_fname) + ipautil.backup_file(self.keydb_fname) + ipautil.backup_file(self.secmod_fname) + self.run_certutil(["-N", + "-f", self.passwd_fname]) + self.set_perms(self.passwd_fname, write=True) + + def create_ca_cert(self): + # Generate the encryption key + self.run_certutil(["-G", "-z", self.noise_fname, "-f", self.passwd_fname]) + # Generate the self-signed cert + self.run_certutil(["-S", "-n", self.cacert_name, + "-s", "cn=IPA Test Certificate Authority", + "-x", + "-t", "CT,,C", + "-m", self.next_serial(), + "-v", self.valid_months, + "-z", self.noise_fname, + "-f", self.passwd_fname]) + + def export_ca_cert(self, nickname, create_pkcs12=False): + """create_pkcs12 tells us whether we should create a PKCS#12 file + of the CA or not. If we are running on a replica then we won't + have the private key to make a PKCS#12 file so we don't need to + do that step.""" + # export the CA cert for use with other apps + ipautil.backup_file(self.cacert_fname) + self.run_certutil(["-L", "-n", nickname, + "-a", + "-o", self.cacert_fname]) + self.set_perms(self.cacert_fname) + if create_pkcs12: + ipautil.backup_file(self.pk12_fname) + ipautil.run(["/usr/bin/pk12util", "-d", self.secdir, + "-o", self.pk12_fname, + "-n", self.cacert_name, + "-w", self.passwd_fname, + "-k", self.passwd_fname]) + self.set_perms(self.pk12_fname) + + def load_cacert(self, cacert_fname): + self.run_certutil(["-A", "-n", self.cacert_name, + "-t", "CT,,C", + "-a", + "-i", cacert_fname]) + + def find_cacert_serial(self): + (out,err) = self.run_certutil(["-L", "-n", self.cacert_name]) + data = out.split('\n') + for line in data: + x = re.match(r'\s+Serial Number: (\d+) .*', line) + if x is not None: + return x.group(1) + + raise RuntimeError("Unable to find serial number") + + def create_server_cert(self, nickname, name, other_certdb=None): + cdb = other_certdb + if not cdb: + cdb = self + self.request_cert(name) + cdb.issue_server_cert(self.certreq_fname, self.certder_fname) + self.add_cert(self.certder_fname, nickname) + os.unlink(self.certreq_fname) + os.unlink(self.certder_fname) + + def create_signing_cert(self, nickname, name, other_certdb=None): + cdb = other_certdb + if not cdb: + cdb = self + self.request_cert(name) + cdb.issue_signing_cert(self.certreq_fname, self.certder_fname) + self.add_cert(self.certder_fname, nickname) + os.unlink(self.certreq_fname) + os.unlink(self.certder_fname) + + def request_cert(self, name): + self.run_certutil(["-R", "-s", name, + "-o", self.certreq_fname, + "-g", self.keysize, + "-z", self.noise_fname, + "-f", self.passwd_fname]) + + def issue_server_cert(self, certreq_fname, cert_fname): + p = subprocess.Popen(["/usr/bin/certutil", + "-d", self.secdir, + "-C", "-c", self.cacert_name, + "-i", certreq_fname, + "-o", cert_fname, + "-m", self.next_serial(), + "-v", self.valid_months, + "-f", self.passwd_fname, + "-1", "-5"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + + # Bah - this sucks, but I guess it isn't possible to fully + # control this with command line arguments. + # + # What this is requesting is: + # -1 (Create key usage extension) + # 2 - Key encipherment + # 9 - done + # n - not critical + # + # -5 (Create netscape cert type extension) + # 1 - SSL Server + # 9 - done + # n - not critical + p.stdin.write("2\n9\nn\n1\n9\nn\n") + p.wait() + + def issue_signing_cert(self, certreq_fname, cert_fname): + p = subprocess.Popen(["/usr/bin/certutil", + "-d", self.secdir, + "-C", "-c", self.cacert_name, + "-i", certreq_fname, + "-o", cert_fname, + "-m", self.next_serial(), + "-v", self.valid_months, + "-f", self.passwd_fname, + "-1", "-5"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + + # Bah - this sucks, but I guess it isn't possible to fully + # control this with command line arguments. + # + # What this is requesting is: + # -1 (Create key usage extension) + # 0 - Digital Signature + # 5 - Cert signing key + # 9 - done + # n - not critical + # + # -5 (Create netscape cert type extension) + # 3 - Object Signing + # 9 - done + # n - not critical + p.stdin.write("0\n5\n9\nn\n3\n9\nn\n") + p.wait() + + def add_cert(self, cert_fname, nickname): + self.run_certutil(["-A", "-n", nickname, + "-t", "u,u,u", + "-i", cert_fname, + "-f", cert_fname]) + + def create_pin_file(self): + ipautil.backup_file(self.pin_fname) + f = open(self.pin_fname, "w") + f.write("Internal (Software) Token:") + pwd = open(self.passwd_fname) + f.write(pwd.read()) + f.close() + self.set_perms(self.pin_fname) + + def find_root_cert(self, nickname): + p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir, + "-O", "-n", nickname], stdout=subprocess.PIPE) + + chain = p.stdout.read() + chain = chain.split("\n") + + root_nickname = re.match('\ *"(.*)".*', chain[0]).groups()[0] + + return root_nickname + + def trust_root_cert(self, nickname): + root_nickname = self.find_root_cert(nickname) + + self.run_certutil(["-M", "-n", root_nickname, + "-t", "CT,CT,"]) + + def find_server_certs(self): + p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir, + "-L"], stdout=subprocess.PIPE) + + certs = p.stdout.read() + + certs = certs.split("\n") + + server_certs = [] + + for cert in certs: + fields = cert.split() + if not len(fields): + continue + flags = fields[-1] + if 'u' in flags: + name = " ".join(fields[0:-1]) + # NSS 3.12 added a header to the certutil output + if name == "Certificate Nickname Trust": + continue + server_certs.append((name, flags)) + + return server_certs + + def import_pkcs12(self, pkcs12_fname, passwd_fname=None): + args = ["/usr/bin/pk12util", "-d", self.secdir, + "-i", pkcs12_fname, + "-k", self.passwd_fname] + if passwd_fname: + args = args + ["-w", passwd_fname] + try: + ipautil.run(args) + except ipautil.CalledProcessError, e: + if e.returncode == 17: + raise RuntimeError("incorrect password") + else: + raise RuntimeError("unknown error import pkcs#12 file") + + def export_pkcs12(self, pkcs12_fname, pkcs12_pwd_fname, nickname="CA certificate"): + ipautil.run(["/usr/bin/pk12util", "-d", self.secdir, + "-o", pkcs12_fname, + "-n", nickname, + "-k", self.passwd_fname, + "-w", pkcs12_pwd_fname]) + + def create_self_signed(self, passwd=None): + self.create_noise_file() + self.create_passwd_file(passwd) + self.create_certdbs() + self.create_ca_cert() + self.export_ca_cert(self.cacert_name, True) + self.create_pin_file() + + def create_from_cacert(self, cacert_fname, passwd=""): + self.create_noise_file() + self.create_passwd_file(passwd) + self.create_certdbs() + self.load_cacert(cacert_fname) + + def create_from_pkcs12(self, pkcs12_fname, pkcs12_pwd_fname, passwd=None): + """Create a new NSS database using the certificates in a PKCS#12 file. + + pkcs12_fname: the filename of the PKCS#12 file + pkcs12_pwd_fname: the file containing the pin for the PKCS#12 file + nickname: the nickname/friendly-name of the cert we are loading + passwd: The password to use for the new NSS database we are creating + """ + self.create_noise_file() + self.create_passwd_file(passwd) + self.create_certdbs() + self.import_pkcs12(pkcs12_fname, pkcs12_pwd_fname) + server_certs = self.find_server_certs() + if len(server_certs) == 0: + raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_fname) + + # We only handle one server cert + nickname = server_certs[0][0] + + self.cacert_name = self.find_root_cert(nickname) + self.trust_root_cert(nickname) + self.create_pin_file() + self.export_ca_cert(self.cacert_name, False) + + # This file implies that we have our own self-signed CA. Ensure + # that it no longer exists (from previous installs, for example). + try: + os.remove(CA_SERIALNO) + except: + pass + + def backup_files(self): + self.fstore.backup_file(self.noise_fname) + self.fstore.backup_file(self.passwd_fname) + self.fstore.backup_file(self.certdb_fname) + self.fstore.backup_file(self.keydb_fname) + self.fstore.backup_file(self.secmod_fname) + self.fstore.backup_file(self.cacert_fname) + self.fstore.backup_file(self.pk12_fname) + self.fstore.backup_file(self.pin_fname) + self.fstore.backup_file(self.certreq_fname) + self.fstore.backup_file(self.certder_fname) diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py new file mode 100644 index 00000000..e9826bf6 --- /dev/null +++ b/ipaserver/install/dsinstance.py @@ -0,0 +1,479 @@ +# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com> +# Simo Sorce <ssorce@redhat.com> +# +# Copyright (C) 2007 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 +# + +import shutil +import logging +import pwd +import glob +import sys +import os +import re +import time +import tempfile +import stat + +from ipa import ipautil + +import service +import installutils +import certs +import ipaldap, ldap +from ipaserver import ldapupdate + +SERVER_ROOT_64 = "/usr/lib64/dirsrv" +SERVER_ROOT_32 = "/usr/lib/dirsrv" + +def realm_to_suffix(realm_name): + s = realm_name.split(".") + terms = ["dc=" + x.lower() for x in s] + return ",".join(terms) + +def find_server_root(): + if ipautil.dir_exists(SERVER_ROOT_64): + return SERVER_ROOT_64 + else: + return SERVER_ROOT_32 + +def realm_to_serverid(realm_name): + return "-".join(realm_name.split(".")) + +def config_dirname(serverid): + return "/etc/dirsrv/slapd-" + serverid + "/" + +def schema_dirname(serverid): + return config_dirname(serverid) + "/schema/" + +def erase_ds_instance_data(serverid): + try: + shutil.rmtree("/etc/dirsrv/slapd-%s" % serverid) + except: + pass + try: + shutil.rmtree("/usr/lib/dirsrv/slapd-%s" % serverid) + except: + pass + try: + shutil.rmtree("/usr/lib64/dirsrv/slapd-%s" % serverid) + except: + pass + try: + shutil.rmtree("/var/lib/dirsrv/slapd-%s" % serverid) + except: + pass + try: + shutil.rmtree("/var/lock/dirsrv/slapd-%s" % serverid) + except: + pass +# try: +# shutil.rmtree("/var/log/dirsrv/slapd-%s" % serverid) +# except: +# pass + +def check_existing_installation(): + dirs = glob.glob("/etc/dirsrv/slapd-*") + if not dirs: + return [] + + serverids = [] + for d in dirs: + serverids.append(os.path.basename(d).split("slapd-", 1)[1]) + + return serverids + +def check_ports(): + ds_unsecure = installutils.port_available(389) + ds_secure = installutils.port_available(636) + return (ds_unsecure, ds_secure) + +def is_ds_running(): + """The DS init script always returns 0 when requesting status so it cannot + be used to determine if the server is running. We have to look at the + output. + """ + ret = True + try: + (sout, serr) = ipautil.run(["/sbin/service", "dirsrv", "status"]) + if sout.find("is stopped") >= 0: + ret = False + except ipautil.CalledProcessError: + ret = False + return ret + + +INF_TEMPLATE = """ +[General] +FullMachineName= $FQHN +SuiteSpotUserID= $USER +ServerRoot= $SERVER_ROOT +[slapd] +ServerPort= 389 +ServerIdentifier= $SERVERID +Suffix= $SUFFIX +RootDN= cn=Directory Manager +RootDNPwd= $PASSWORD +InstallLdifFile= /var/lib/dirsrv/boot.ldif +""" + +BASE_TEMPLATE = """ +dn: $SUFFIX +objectClass: top +objectClass: domain +objectClass: pilotObject +dc: $BASEDC +info: IPA V1.0 +""" + +class DsInstance(service.Service): + def __init__(self, realm_name=None, domain_name=None, dm_password=None): + service.Service.__init__(self, "dirsrv") + self.realm_name = realm_name + self.dm_password = dm_password + self.sub_dict = None + self.domain = domain_name + self.serverid = None + self.host_name = None + self.pkcs12_info = None + self.ds_user = None + if realm_name: + self.suffix = realm_to_suffix(self.realm_name) + self.__setup_sub_dict() + else: + self.suffix = None + + def create_instance(self, ds_user, realm_name, host_name, domain_name, dm_password, pkcs12_info=None): + self.ds_user = ds_user + self.realm_name = realm_name.upper() + self.serverid = realm_to_serverid(self.realm_name) + self.suffix = realm_to_suffix(self.realm_name) + self.host_name = host_name + self.dm_password = dm_password + self.domain = domain_name + self.pkcs12_info = pkcs12_info + self.__setup_sub_dict() + + self.step("creating directory server user", self.__create_ds_user) + self.step("creating directory server instance", self.__create_instance) + self.step("adding default schema", self.__add_default_schemas) + self.step("enabling memberof plugin", self.__add_memberof_module) + self.step("enabling referential integrity plugin", self.__add_referint_module) + self.step("enabling distributed numeric assignment plugin", self.__add_dna_module) + self.step("enabling winsync plugin", self.__add_winsync_module) + self.step("configuring uniqueness plugin", self.__set_unique_attrs) + self.step("creating indices", self.__create_indices) + self.step("configuring ssl for ds instance", self.__enable_ssl) + self.step("configuring certmap.conf", self.__certmap_conf) + self.step("restarting directory server", self.__restart_instance) + self.step("adding default layout", self.__add_default_layout) + self.step("configuring Posix uid/gid generation as first master", + self.__config_uidgid_gen_first_master) + self.step("adding master entry as first master", + self.__add_master_entry_first_master) + self.step("initializing group membership", + self.init_memberof) + + self.step("configuring directory to start on boot", self.__enable) + + self.start_creation("Configuring directory server:") + + def __enable(self): + self.backup_state("enabled", self.is_enabled()) + self.chkconfig_on() + + def __setup_sub_dict(self): + server_root = find_server_root() + self.sub_dict = dict(FQHN=self.host_name, SERVERID=self.serverid, + PASSWORD=self.dm_password, SUFFIX=self.suffix.lower(), + REALM=self.realm_name, USER=self.ds_user, + SERVER_ROOT=server_root, DOMAIN=self.domain, + TIME=int(time.time())) + + def __create_ds_user(self): + user_exists = True + try: + pwd.getpwnam(self.ds_user) + logging.debug("ds user %s exists" % self.ds_user) + except KeyError: + user_exists = False + logging.debug("adding ds user %s" % self.ds_user) + args = ["/usr/sbin/useradd", "-c", "DS System User", "-d", "/var/lib/dirsrv", "-M", "-r", "-s", "/sbin/nologin", self.ds_user] + try: + ipautil.run(args) + logging.debug("done adding user") + except ipautil.CalledProcessError, e: + logging.critical("failed to add user %s" % e) + + self.backup_state("user", self.ds_user) + self.backup_state("user_exists", user_exists) + + def __create_instance(self): + self.backup_state("running", is_ds_running()) + self.backup_state("serverid", self.serverid) + + self.sub_dict['BASEDC'] = self.realm_name.split('.')[0].lower() + base_txt = ipautil.template_str(BASE_TEMPLATE, self.sub_dict) + logging.debug(base_txt) + base_fd = file("/var/lib/dirsrv/boot.ldif", "w") + base_fd.write(base_txt) + base_fd.flush() + base_fd.close() + + inf_txt = ipautil.template_str(INF_TEMPLATE, self.sub_dict) + logging.debug("writing inf template") + inf_fd = ipautil.write_tmp_file(inf_txt) + inf_txt = re.sub(r"RootDNPwd=.*\n", "", inf_txt) + logging.debug(inf_txt) + if ipautil.file_exists("/usr/sbin/setup-ds.pl"): + args = ["/usr/sbin/setup-ds.pl", "--silent", "--logfile", "-", "-f", inf_fd.name] + logging.debug("calling setup-ds.pl") + else: + args = ["/usr/bin/ds_newinst.pl", inf_fd.name] + logging.debug("calling ds_newinst.pl") + try: + ipautil.run(args) + logging.debug("completed creating ds instance") + except ipautil.CalledProcessError, e: + logging.critical("failed to restart ds instance %s" % e) + logging.debug("restarting ds instance") + try: + self.restart() + logging.debug("done restarting ds instance") + except ipautil.CalledProcessError, e: + print "failed to restart ds instance", e + logging.debug("failed to restart ds instance %s" % e) + inf_fd.close() + os.remove("/var/lib/dirsrv/boot.ldif") + + def __add_default_schemas(self): + shutil.copyfile(ipautil.SHARE_DIR + "60kerberos.ldif", + schema_dirname(self.serverid) + "60kerberos.ldif") + shutil.copyfile(ipautil.SHARE_DIR + "60samba.ldif", + schema_dirname(self.serverid) + "60samba.ldif") + shutil.copyfile(ipautil.SHARE_DIR + "60radius.ldif", + schema_dirname(self.serverid) + "60radius.ldif") + shutil.copyfile(ipautil.SHARE_DIR + "60ipaconfig.ldif", + schema_dirname(self.serverid) + "60ipaconfig.ldif") + + def __restart_instance(self): + try: + self.restart() + if not is_ds_running(): + logging.critical("Failed to restart the directory server. See the installation log for details.") + sys.exit(1) + except SystemExit, e: + raise e + except Exception, e: + # TODO: roll back here? + logging.critical("Failed to restart the directory server. See the installation log for details.") + + def __ldap_mod(self, ldif, sub_dict = None): + fd = None + path = ipautil.SHARE_DIR + ldif + + if not sub_dict is None: + txt = ipautil.template_file(path, sub_dict) + fd = ipautil.write_tmp_file(txt) + path = fd.name + + [pw_fd, pw_name] = tempfile.mkstemp() + os.write(pw_fd, self.dm_password) + os.close(pw_fd) + + args = ["/usr/bin/ldapmodify", "-h", "127.0.0.1", "-xv", + "-D", "cn=Directory Manager", "-y", pw_name, "-f", path] + + try: + try: + ipautil.run(args) + except ipautil.CalledProcessError, e: + logging.critical("Failed to load %s: %s" % (ldif, str(e))) + finally: + os.remove(pw_name) + + if not fd is None: + fd.close() + + def __add_memberof_module(self): + self.__ldap_mod("memberof-conf.ldif") + + def init_memberof(self): + self.__ldap_mod("memberof-task.ldif", self.sub_dict) + + def apply_updates(self): + ld = ldapupdate.LDAPUpdate(dm_password=self.dm_password) + files = ld.get_all_files(ldapupdate.UPDATES_DIR) + ld.update(files) + + def __add_referint_module(self): + self.__ldap_mod("referint-conf.ldif") + + def __add_dna_module(self): + self.__ldap_mod("dna-conf.ldif") + + def __set_unique_attrs(self): + self.__ldap_mod("unique-attributes.ldif", self.sub_dict) + + def __config_uidgid_gen_first_master(self): + self.__ldap_mod("dna-posix.ldif", self.sub_dict) + + def __add_master_entry_first_master(self): + self.__ldap_mod("master-entry.ldif", self.sub_dict) + + def __add_winsync_module(self): + self.__ldap_mod("ipa-winsync-conf.ldif") + + def __enable_ssl(self): + dirname = config_dirname(self.serverid) + ca = certs.CertDB(dirname) + if self.pkcs12_info: + ca.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1]) + server_certs = ca.find_server_certs() + if len(server_certs) == 0: + raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_info[0]) + + # We only handle one server cert + nickname = server_certs[0][0] + else: + ca.create_self_signed() + ca.create_server_cert("Server-Cert", "cn=%s,ou=Fedora Directory Server" % self.host_name) + nickname = "Server-Cert" + + conn = ipaldap.IPAdmin("127.0.0.1") + conn.simple_bind_s("cn=directory manager", self.dm_password) + + mod = [(ldap.MOD_REPLACE, "nsSSLClientAuth", "allowed"), + (ldap.MOD_REPLACE, "nsSSL3Ciphers", + "-rsa_null_md5,+rsa_rc4_128_md5,+rsa_rc4_40_md5,+rsa_rc2_40_md5,\ ++rsa_des_sha,+rsa_fips_des_sha,+rsa_3des_sha,+rsa_fips_3des_sha,+fortezza,\ ++fortezza_rc4_128_sha,+fortezza_null,+tls_rsa_export1024_with_rc4_56_sha,\ ++tls_rsa_export1024_with_des_cbc_sha")] + conn.modify_s("cn=encryption,cn=config", mod) + + mod = [(ldap.MOD_ADD, "nsslapd-security", "on"), + (ldap.MOD_REPLACE, "nsslapd-ssl-check-hostname", "off")] + conn.modify_s("cn=config", mod) + + entry = ipaldap.Entry("cn=RSA,cn=encryption,cn=config") + + entry.setValues("objectclass", "top", "nsEncryptionModule") + entry.setValues("cn", "RSA") + entry.setValues("nsSSLPersonalitySSL", nickname) + entry.setValues("nsSSLToken", "internal (software)") + entry.setValues("nsSSLActivation", "on") + + conn.addEntry(entry) + + conn.unbind() + + def __add_default_layout(self): + self.__ldap_mod("bootstrap-template.ldif", self.sub_dict) + + def __create_indices(self): + self.__ldap_mod("indices.ldif") + + def __certmap_conf(self): + shutil.copyfile(ipautil.SHARE_DIR + "certmap.conf.template", + config_dirname(self.serverid) + "certmap.conf") + + def change_admin_password(self, password): + logging.debug("Changing admin password") + dirname = config_dirname(self.serverid) + if ipautil.dir_exists("/usr/lib64/mozldap"): + app = "/usr/lib64/mozldap/ldappasswd" + else: + app = "/usr/lib/mozldap/ldappasswd" + args = [app, + "-D", "cn=Directory Manager", "-w", self.dm_password, + "-P", dirname+"/cert8.db", "-ZZZ", "-s", password, + "uid=admin,cn=users,cn=accounts,"+self.suffix] + try: + ipautil.run(args) + logging.debug("ldappasswd done") + except ipautil.CalledProcessError, e: + print "Unable to set admin password", e + logging.debug("Unable to set admin password %s" % e) + + def uninstall(self): + running = self.restore_state("running") + enabled = self.restore_state("enabled") + + if not running is None: + self.stop() + + if not enabled is None and not enabled: + self.chkconfig_off() + + serverid = self.restore_state("serverid") + if not serverid is None: + erase_ds_instance_data(serverid) + + ds_user = self.restore_state("user") + user_exists = self.restore_state("user_exists") + + if not ds_user is None and not user_exists is None and not user_exists: + try: + ipautil.run(["/usr/sbin/userdel", ds_user]) + except ipautil.CalledProcessError, e: + logging.critical("failed to delete user %s" % e) + + if self.restore_state("running"): + self.start() + + # we could probably move this function into the service.Service + # class - it's very generic - all we need is a way to get an + # instance of a particular Service + def add_ca_cert(self, cacert_fname, cacert_name=''): + """Add a CA certificate to the directory server cert db. We + first have to shut down the directory server in case it has + opened the cert db read-only. Then we use the CertDB class + to add the CA cert. We have to provide a nickname, and we + do not use 'CA certificate' since that's the default, so + we use 'Imported CA' if none specified. Then we restart + the server.""" + # first make sure we have a valid cacert_fname + try: + if not os.access(cacert_fname, os.R_OK): + logging.critical("The given CA cert file named [%s] could not be read" % + cacert_fname) + return False + except OSError, e: + logging.critical("The given CA cert file named [%s] could not be read: %s" % + (cacert_fname, str(e))) + return False + # ok - ca cert file can be read + # shutdown the server + self.stop() + + dirname = config_dirname(realm_to_serverid(self.realm_name)) + certdb = certs.CertDB(dirname) + if not cacert_name or len(cacert_name) == 0: + cacert_name = "Imported CA" + # we can't pass in the nickname, so we set the instance variable + certdb.cacert_name = cacert_name + status = True + try: + certdb.load_cacert(cacert_fname) + except ipalib.CalledProcessError, e: + logging.critical("Error importing CA cert file named [%s]: %s" % + (cacert_fname, str(e))) + status = False + # restart the directory server + self.start() + + return status diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py new file mode 100644 index 00000000..f5a903b3 --- /dev/null +++ b/ipaserver/install/httpinstance.py @@ -0,0 +1,231 @@ +# Authors: Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2007 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 +# + +import os +import os.path +import subprocess +import string +import tempfile +import logging +import pwd +import fileinput +import sys +import shutil + +import service +import certs +import dsinstance +import installutils +from ipa import sysrestore +from ipa import ipautil + +HTTPD_DIR = "/etc/httpd" +SSL_CONF = HTTPD_DIR + "/conf.d/ssl.conf" +NSS_CONF = HTTPD_DIR + "/conf.d/nss.conf" +NSS_DIR = HTTPD_DIR + "/alias" + +selinux_warning = """WARNING: could not set selinux boolean httpd_can_network_connect to true. +The web interface may not function correctly until this boolean is +successfully change with the command: + /usr/sbin/setsebool -P httpd_can_network_connect true +Try updating the policycoreutils and selinux-policy packages. +""" + +class WebGuiInstance(service.SimpleServiceInstance): + def __init__(self): + service.SimpleServiceInstance.__init__(self, "ipa_webgui") + +class HTTPInstance(service.Service): + def __init__(self, fstore = None): + service.Service.__init__(self, "httpd") + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + + def create_instance(self, realm, fqdn, domain_name, autoconfig=True, pkcs12_info=None): + self.fqdn = fqdn + self.realm = realm + self.domain = domain_name + self.pkcs12_info = pkcs12_info + self.sub_dict = { "REALM" : realm, "FQDN": fqdn, "DOMAIN" : self.domain } + + self.step("disabling mod_ssl in httpd", self.__disable_mod_ssl) + self.step("Setting mod_nss port to 443", self.__set_mod_nss_port) + self.step("Adding URL rewriting rules", self.__add_include) + self.step("configuring httpd", self.__configure_http) + self.step("creating a keytab for httpd", self.__create_http_keytab) + self.step("Setting up ssl", self.__setup_ssl) + if autoconfig: + self.step("Setting up browser autoconfig", self.__setup_autoconfig) + self.step("configuring SELinux for httpd", self.__selinux_config) + self.step("restarting httpd", self.__start) + self.step("configuring httpd to start on boot", self.__enable) + + self.start_creation("Configuring the web interface") + + def __start(self): + self.backup_state("running", self.is_running()) + self.restart() + + def __enable(self): + self.backup_state("enabled", self.is_running()) + self.chkconfig_on() + + def __selinux_config(self): + selinux=0 + try: + if (os.path.exists('/usr/sbin/selinuxenabled')): + ipautil.run(["/usr/sbin/selinuxenabled"]) + selinux=1 + except ipautil.CalledProcessError: + # selinuxenabled returns 1 if not enabled + pass + + if selinux: + try: + # returns e.g. "httpd_can_network_connect --> off" + (stdout, stderr) = ipautils.run(["/usr/sbin/getsebool", + "httpd_can_network_connect"]) + self.backup_state("httpd_can_network_connect", stdout.split()[2]) + except: + pass + + # Allow apache to connect to the turbogears web gui + # This can still fail even if selinux is enabled + try: + ipautil.run(["/usr/sbin/setsebool", "-P", "httpd_can_network_connect", "true"]) + except: + self.print_msg(selinux_warning) + + def __create_http_keytab(self): + http_principal = "HTTP/" + self.fqdn + "@" + self.realm + installutils.kadmin_addprinc(http_principal) + installutils.create_keytab("/etc/httpd/conf/ipa.keytab", http_principal) + + pent = pwd.getpwnam("apache") + os.chown("/etc/httpd/conf/ipa.keytab", pent.pw_uid, pent.pw_gid) + + def __configure_http(self): + http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa.conf", self.sub_dict) + self.fstore.backup_file("/etc/httpd/conf.d/ipa.conf") + http_fd = open("/etc/httpd/conf.d/ipa.conf", "w") + http_fd.write(http_txt) + http_fd.close() + + http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa-rewrite.conf", self.sub_dict) + self.fstore.backup_file("/etc/httpd/conf.d/ipa-rewrite.conf") + http_fd = open("/etc/httpd/conf.d/ipa-rewrite.conf", "w") + http_fd.write(http_txt) + http_fd.close() + + def __disable_mod_ssl(self): + if os.path.exists(SSL_CONF): + self.fstore.backup_file(SSL_CONF) + os.unlink(SSL_CONF) + + def __set_mod_nss_port(self): + self.fstore.backup_file(NSS_CONF) + if installutils.update_file(NSS_CONF, '8443', '443') != 0: + print "Updating port in %s failed." % NSS_CONF + + def __set_mod_nss_nickname(self, nickname): + installutils.set_directive(NSS_CONF, 'NSSNickname', nickname) + + def __add_include(self): + """This should run after __set_mod_nss_port so is already backed up""" + if installutils.update_file(NSS_CONF, '</VirtualHost>', 'Include conf.d/ipa-rewrite.conf\n</VirtualHost>') != 0: + print "Adding Include conf.d/ipa-rewrite to %s failed." % NSS_CONF + + def __setup_ssl(self): + ds_ca = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(self.realm))) + ca = certs.CertDB(NSS_DIR) + if self.pkcs12_info: + ca.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1], passwd="") + server_certs = ca.find_server_certs() + if len(server_certs) == 0: + raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_info[0]) + + # We only handle one server cert + nickname = server_certs[0][0] + + self.__set_mod_nss_nickname(nickname) + else: + ca.create_from_cacert(ds_ca.cacert_fname) + ca.create_server_cert("Server-Cert", "cn=%s,ou=Apache Web Server" % self.fqdn, ds_ca) + ca.create_signing_cert("Signing-Cert", "cn=%s,ou=Signing Certificate,o=Identity Policy Audit" % self.fqdn, ds_ca) + + # Fix the database permissions + os.chmod(NSS_DIR + "/cert8.db", 0640) + os.chmod(NSS_DIR + "/key3.db", 0640) + os.chmod(NSS_DIR + "/secmod.db", 0640) + + pent = pwd.getpwnam("apache") + os.chown(NSS_DIR + "/cert8.db", 0, pent.pw_gid ) + os.chown(NSS_DIR + "/key3.db", 0, pent.pw_gid ) + os.chown(NSS_DIR + "/secmod.db", 0, pent.pw_gid ) + + def __setup_autoconfig(self): + prefs_txt = ipautil.template_file(ipautil.SHARE_DIR + "preferences.html.template", self.sub_dict) + prefs_fd = open("/usr/share/ipa/html/preferences.html", "w") + prefs_fd.write(prefs_txt) + prefs_fd.close() + + # The signing cert is generated in __setup_ssl + ds_ca = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(self.realm))) + ca = certs.CertDB(NSS_DIR) + + # Publish the CA certificate + shutil.copy(ds_ca.cacert_fname, "/usr/share/ipa/html/ca.crt") + os.chmod("/usr/share/ipa/html/ca.crt", 0444) + + tmpdir = tempfile.mkdtemp(prefix = "tmp-") + shutil.copy("/usr/share/ipa/html/preferences.html", tmpdir) + ca.run_signtool(["-k", "Signing-Cert", + "-Z", "/usr/share/ipa/html/configure.jar", + "-e", ".html", + tmpdir]) + shutil.rmtree(tmpdir) + + def uninstall(self): + running = self.restore_state("running") + enabled = self.restore_state("enabled") + + if not running is None: + self.stop() + + if not enabled is None and not enabled: + self.chkconfig_off() + + for f in ["/etc/httpd/conf.d/ipa.conf", SSL_CONF, NSS_CONF]: + try: + self.fstore.restore_file(f) + except ValueError, error: + logging.debug(error) + pass + + sebool_state = self.restore_state("httpd_can_network_connect") + if not sebool_state is None: + try: + ipautil.run(["/usr/sbin/setsebool", "-P", "httpd_can_network_connect", sebool_state]) + except: + self.print_msg(selinux_warning) + + if not running is None and running: + self.start() diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py new file mode 100644 index 00000000..563b168e --- /dev/null +++ b/ipaserver/install/installutils.py @@ -0,0 +1,248 @@ +# Authors: Simo Sorce <ssorce@redhat.com> +# +# Copyright (C) 2007 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 +# + +import logging +import socket +import errno +import getpass +import os +import re +import fileinput +import sys +import time +import struct +import fcntl + +from ipa import ipautil +from ipa import dnsclient + +def get_fqdn(): + fqdn = "" + try: + fqdn = socket.getfqdn() + except: + try: + fqdn = socket.gethostname() + except: + fqdn = "" + return fqdn + +def verify_fqdn(host_name,no_host_dns=False): + + if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain": + raise RuntimeError("Invalid hostname: " + host_name) + + try: + hostaddr = socket.getaddrinfo(host_name, None) + except: + raise RuntimeError("Unable to resolve host name, check /etc/hosts or DNS name resolution") + + if len(hostaddr) == 0: + raise RuntimeError("Unable to resolve host name, check /etc/hosts or DNS name resolution") + + for a in hostaddr: + if a[4][0] == '127.0.0.1' or a[4][0] == '::1': + raise RuntimeError("The IPA Server hostname cannot resolve to localhost (%s). A routable IP address must be used. Check /etc/hosts to see if %s is an alias for %s" % (a[4][0], host_name, a[4][0])) + try: + revname = socket.gethostbyaddr(a[4][0])[0] + except: + raise RuntimeError("Unable to resolve the reverse ip address, check /etc/hosts or DNS name resolution") + if revname != host_name: + raise RuntimeError("The host name %s does not match the reverse lookup %s" % (host_name, revname)) + + if no_host_dns: + print "Warning: skipping DNS resolution of host", host_name + return + + # Verify this is NOT a CNAME + rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_CNAME) + if len(rs) != 0: + for rsn in rs: + if rsn.dns_type == dnsclient.DNS_T_CNAME: + raise RuntimeError("The IPA Server Hostname cannot be a CNAME, only A names are allowed.") + + # Verify that it is a DNS A record + rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_A) + if len(rs) == 0: + print "Warning: Hostname (%s) not found in DNS" % host_name + return + + rec = None + for rsn in rs: + if rsn.dns_type == dnsclient.DNS_T_A: + rec = rsn + break + + if rec == None: + print "Warning: Hostname (%s) not found in DNS" % host_name + return + + # Compare the forward and reverse + forward = rec.dns_name + + addr = socket.inet_ntoa(struct.pack('<L',rec.rdata.address)) + ipaddr = socket.inet_ntoa(struct.pack('!L',rec.rdata.address)) + + addr = addr + ".in-addr.arpa." + rs = dnsclient.query(addr, dnsclient.DNS_C_IN, dnsclient.DNS_T_PTR) + if len(rs) == 0: + raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, addr)) + + rev = None + for rsn in rs: + if rsn.dns_type == dnsclient.DNS_T_PTR: + rev = rsn + break + + if rev == None: + raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, addr)) + + reverse = rev.rdata.ptrdname + + if forward != reverse: + raise RuntimeError("The DNS forward record %s does not match the reverse address %s" % (forward, reverse)) + +def port_available(port): + """Try to bind to a port on the wildcard host + Return 1 if the port is available + Return 0 if the port is in use + """ + rv = 1 + + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + fcntl.fcntl(s, fcntl.F_SETFD, fcntl.FD_CLOEXEC) + s.bind(('', port)) + s.close() + except socket.error, e: + if e[0] == errno.EADDRINUSE: + rv = 0 + + if rv: + try: + s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + fcntl.fcntl(s, fcntl.F_SETFD, fcntl.FD_CLOEXEC) + s.bind(('', port)) + s.close() + except socket.error, e: + if e[0] == errno.EADDRINUSE: + rv = 0 + + return rv + +def standard_logging_setup(log_filename, debug=False): + old_umask = os.umask(077) + # Always log everything (i.e., DEBUG) to the log + # file. + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(levelname)s %(message)s', + filename=log_filename, + filemode='w') + os.umask(old_umask) + + console = logging.StreamHandler() + # If the debug option is set, also log debug messages to the console + if debug: + console.setLevel(logging.DEBUG) + else: + # Otherwise, log critical and error messages + console.setLevel(logging.ERROR) + formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') + console.setFormatter(formatter) + logging.getLogger('').addHandler(console) + +def get_password(prompt): + if os.isatty(sys.stdin.fileno()): + return getpass.getpass(prompt) + else: + return sys.stdin.readline().rstrip() + +def read_password(user, confirm=True, validate=True): + correct = False + pwd = "" + while not correct: + pwd = get_password(user + " password: ") + if not pwd: + continue + if validate and len(pwd) < 8: + print "Password must be at least 8 characters long" + continue + if not confirm: + correct = True + continue + pwd_confirm = get_password("Password (confirm): ") + if pwd != pwd_confirm: + print "Password mismatch!" + print "" + else: + correct = True + print "" + return pwd + +def update_file(filename, orig, subst): + if os.path.exists(filename): + pattern = "%s" % re.escape(orig) + p = re.compile(pattern) + for line in fileinput.input(filename, inplace=1): + if not p.search(line): + sys.stdout.write(line) + else: + sys.stdout.write(p.sub(subst, line)) + fileinput.close() + return 0 + else: + print "File %s doesn't exist." % filename + return 1 + +def set_directive(filename, directive, value): + """Set a name/value pair directive in a configuration file. + + This has only been tested with nss.conf + """ + fd = open(filename) + file = [] + for line in fd: + if directive in line: + file.append('%s "%s"\n' % (directive, value)) + else: + file.append(line) + fd.close() + + fd = open(filename, "w") + fd.write("".join(file)) + fd.close() + +def kadmin(command): + ipautil.run(["/usr/kerberos/sbin/kadmin.local", "-q", command]) + +def kadmin_addprinc(principal): + kadmin("addprinc -randkey " + principal) + +def kadmin_modprinc(principal, options): + kadmin("modprinc " + options + " " + principal) + +def create_keytab(path, principal): + try: + if ipautil.file_exists(path): + os.remove(path) + except os.error: + logging.critical("Failed to remove %s." % path) + + kadmin("ktadd -k " + path + " " + principal) + diff --git a/ipaserver/install/ipaldap.py b/ipaserver/install/ipaldap.py new file mode 100644 index 00000000..c2dbe4e2 --- /dev/null +++ b/ipaserver/install/ipaldap.py @@ -0,0 +1,701 @@ +# Authors: Rich Megginson <richm@redhat.com> +# Rob Crittenden <rcritten@redhat.com +# +# Copyright (C) 2007 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 +# + +import sys +import os +import os.path +import popen2 +import base64 +import urllib +import urllib2 +import socket +import ldif +import re +import string +import ldap +import cStringIO +import time +import operator +import struct +import ldap.sasl +from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples +from ldap.ldapobject import SimpleLDAPObject +from ipa import ipaerror, ipautil + +# Global variable to define SASL auth +sasl_auth = ldap.sasl.sasl({},'GSSAPI') + +class Entry: + """This class represents an LDAP Entry object. An LDAP entry consists of a DN + and a list of attributes. Each attribute consists of a name and a list of + values. In python-ldap, entries are returned as a list of 2-tuples. + Instance variables: + dn - string - the string DN of the entry + data - CIDict - case insensitive dict of the attributes and values""" + + def __init__(self,entrydata): + """data is the raw data returned from the python-ldap result method, which is + a search result entry or a reference or None. + If creating a new empty entry, data is the string DN.""" + if entrydata: + if isinstance(entrydata,tuple): + self.dn = entrydata[0] + self.data = ipautil.CIDict(entrydata[1]) + elif isinstance(entrydata,str) or isinstance(entrydata,unicode): + self.dn = entrydata + self.data = ipautil.CIDict() + else: + self.dn = '' + self.data = ipautil.CIDict() + + def __nonzero__(self): + """This allows us to do tests like if entry: returns false if there is no data, + true otherwise""" + return self.data != None and len(self.data) > 0 + + def hasAttr(self,name): + """Return True if this entry has an attribute named name, False otherwise""" + return self.data and self.data.has_key(name) + + def __getattr__(self,name): + """If name is the name of an LDAP attribute, return the first value for that + attribute - equivalent to getValue - this allows the use of + entry.cn + instead of + entry.getValue('cn') + This also allows us to return None if an attribute is not found rather than + throwing an exception""" + return self.getValue(name) + + def getValues(self,name): + """Get the list (array) of values for the attribute named name""" + return self.data.get(name) + + def getValue(self,name): + """Get the first value for the attribute named name""" + return self.data.get(name,[None])[0] + + def setValue(self,name,*value): + """Value passed in may be a single value, several values, or a single sequence. + For example: + ent.setValue('name', 'value') + ent.setValue('name', 'value1', 'value2', ..., 'valueN') + ent.setValue('name', ['value1', 'value2', ..., 'valueN']) + ent.setValue('name', ('value1', 'value2', ..., 'valueN')) + Since *value is a tuple, we may have to extract a list or tuple from that + tuple as in the last two examples above""" + if isinstance(value[0],list) or isinstance(value[0],tuple): + self.data[name] = value[0] + else: + self.data[name] = value + + setValues = setValue + + def toTupleList(self): + """Convert the attrs and values to a list of 2-tuples. The first element + of the tuple is the attribute name. The second element is either a + single value or a list of values.""" + return self.data.items() + + def __str__(self): + """Convert the Entry to its LDIF representation""" + return self.__repr__() + + # the ldif class base64 encodes some attrs which I would rather see in raw form - to + # encode specific attrs as base64, add them to the list below + ldif.safe_string_re = re.compile('^$') + base64_attrs = ['nsstate', 'krbprincipalkey', 'krbExtraData'] + + def __repr__(self): + """Convert the Entry to its LDIF representation""" + sio = cStringIO.StringIO() + # what's all this then? the unparse method will currently only accept + # a list or a dict, not a class derived from them. self.data is a + # cidict, so unparse barfs on it. I've filed a bug against python-ldap, + # but in the meantime, we have to convert to a plain old dict for printing + # I also don't want to see wrapping, so set the line width really high (1000) + newdata = {} + newdata.update(self.data) + ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(self.dn,newdata) + return sio.getvalue() + +def wrapper(f,name): + """This is the method that wraps all of the methods of the superclass. This seems + to need to be an unbound method, that's why it's outside of IPAdmin. Perhaps there + is some way to do this with the new classmethod or staticmethod of 2.4. + Basically, we replace every call to a method in SimpleLDAPObject (the superclass + of IPAdmin) with a call to inner. The f argument to wrapper is the bound method + of IPAdmin (which is inherited from the superclass). Bound means that it will implicitly + be called with the self argument, it is not in the args list. name is the name of + the method to call. If name is a method that returns entry objects (e.g. result), + we wrap the data returned by an Entry class. If name is a method that takes an entry + argument, we extract the raw data from the entry object to pass in.""" + def inner(*args, **kargs): + if name == 'result': + type, data = f(*args, **kargs) + # data is either a 2-tuple or a list of 2-tuples + # print data + if data: + if isinstance(data,tuple): + return type, Entry(data) + elif isinstance(data,list): + return type, [Entry(x) for x in data] + else: + raise TypeError, "unknown data type %s returned by result" % type(data) + else: + return type, data + elif name.startswith('add'): + # the first arg is self + # the second and third arg are the dn and the data to send + # We need to convert the Entry into the format used by + # python-ldap + ent = args[0] + if isinstance(ent,Entry): + return f(ent.dn, ent.toTupleList(), *args[2:]) + else: + return f(*args, **kargs) + else: + return f(*args, **kargs) + return inner + +class LDIFConn(ldif.LDIFParser): + def __init__( + self, + input_file, + ignored_attr_types=None,max_entries=0,process_url_schemes=None + ): + """ + See LDIFParser.__init__() + + Additional Parameters: + all_records + List instance for storing parsed records + """ + self.dndict = {} # maps dn to Entry + self.dnlist = [] # contains entries in order read + myfile = input_file + if isinstance(input_file,str) or isinstance(input_file,unicode): + myfile = open(input_file, "r") + ldif.LDIFParser.__init__(self,myfile,ignored_attr_types,max_entries,process_url_schemes) + self.parse() + if isinstance(input_file,str) or isinstance(input_file,unicode): + myfile.close() + + def handle(self,dn,entry): + """ + Append single record to dictionary of all records. + """ + if not dn: + dn = '' + newentry = Entry((dn, entry)) + self.dndict[IPAdmin.normalizeDN(dn)] = newentry + self.dnlist.append(newentry) + + def get(self,dn): + ndn = IPAdmin.normalizeDN(dn) + return self.dndict.get(ndn, Entry(None)) + +class IPAdmin(SimpleLDAPObject): + CFGSUFFIX = "o=NetscapeRoot" + DEFAULT_USER_ID = "nobody" + + def getDseAttr(self,attrname): + conffile = self.confdir + '/dse.ldif' + dseldif = LDIFConn(conffile) + cnconfig = dseldif.get("cn=config") + if cnconfig: + return cnconfig.getValue(attrname) + return None + + def __initPart2(self): + if self.binddn and len(self.binddn) and not hasattr(self,'sroot'): + try: + ent = self.getEntry('cn=config', ldap.SCOPE_BASE, '(objectclass=*)', + [ 'nsslapd-instancedir', 'nsslapd-errorlog', + 'nsslapd-certdir', 'nsslapd-schemadir' ]) + self.errlog = ent.getValue('nsslapd-errorlog') + self.confdir = ent.getValue('nsslapd-certdir') + if not self.confdir: + self.confdir = ent.getValue('nsslapd-schemadir') + if self.confdir: + self.confdir = os.path.dirname(self.confdir) + instdir = ent.getValue('nsslapd-instancedir') + ent = self.getEntry('cn=config,cn=ldbm database,cn=plugins,cn=config', + ldap.SCOPE_BASE, '(objectclass=*)', + [ 'nsslapd-directory' ]) + self.dbdir = os.path.dirname(ent.getValue('nsslapd-directory')) + except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR): + pass # usually means + except ldap.OPERATIONS_ERROR, e: + pass # usually means this is Active Directory + except ldap.LDAPError, e: + print "caught exception ", e + raise + + def __localinit__(self): + """If a CA certificate is provided then it is assumed that we are + doing SSL client authentication with proxy auth. + + If a CA certificate is not present then it is assumed that we are + using a forwarded kerberos ticket for SASL auth. SASL provides + its own encryption. + """ + if self.cacert is not None: + SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port)) + else: + SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port)) + + def __init__(self,host,port=389,cacert=None,bindcert=None,bindkey=None,proxydn=None,debug=None): + """We just set our instance variables and wrap the methods - the real + work is done in __localinit__ and __initPart2 - these are separated + out this way so that we can call them from places other than + instance creation e.g. when we just need to reconnect, not create a + new instance""" + if debug and debug.lower() == "on": + ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) + if cacert is not None: + ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert) + if bindcert is not None: + ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert) + if bindkey is not None: + ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey) + + self.__wrapmethods() + self.port = port + self.host = host + self.cacert = cacert + self.bindcert = bindcert + self.bindkey = bindkey + self.proxydn = proxydn + self.suffixes = {} + self.__localinit__() + + def __str__(self): + return self.host + ":" + str(self.port) + + def __get_server_controls__(self): + """Create the proxy user server control. The control has the form + 0x04 = Octet String + 4|0x80 sets the length of the string length field at 4 bytes + the struct() gets us the length in bytes of string self.proxydn + self.proxydn is the proxy dn to send""" + + import sys + + if self.proxydn is not None: + proxydn = chr(0x04) + chr(4|0x80) + struct.pack('l', socket.htonl(len(self.proxydn))) + self.proxydn; + + # Create the proxy control + sctrl=[] + sctrl.append(LDAPControl('2.16.840.1.113730.3.4.18',True,proxydn)) + else: + sctrl=None + + return sctrl + + def toLDAPURL(self): + return "ldap://%s:%d/" % (self.host,self.port) + + def set_proxydn(self, proxydn): + self.proxydn = proxydn + + def set_krbccache(self, krbccache, principal): + if krbccache is not None: + os.environ["KRB5CCNAME"] = krbccache + self.sasl_interactive_bind_s("", sasl_auth) + self.principal = principal + self.proxydn = None + + def do_simple_bind(self, binddn="cn=directory manager", bindpw=""): + self.binddn = binddn + self.bindpwd = bindpw + self.simple_bind_s(binddn, bindpw) + self.__initPart2() + + def getEntry(self,*args): + """This wraps the search function. It is common to just get one entry""" + + sctrl = self.__get_server_controls__() + + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + + try: + res = self.search(*args) + type, obj = self.result(res) + except ldap.NO_SUCH_OBJECT: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, + notfound(args)) + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + + if not obj: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, + notfound(args)) + elif isinstance(obj,Entry): + return obj + else: # assume list/tuple + return obj[0] + + def getList(self,*args): + """This wraps the search function to find all users.""" + + sctrl = self.__get_server_controls__() + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + + try: + res = self.search(*args) + type, obj = self.result(res) + except (ldap.ADMINLIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED), e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, + "Too many results returned by search", e) + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + + if not obj: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, + notfound(args)) + + all_users = [] + for s in obj: + all_users.append(s) + + return all_users + + def getListAsync(self,*args): + """This version performs an asynchronous search, to allow + results even if we hit a limit. + + It returns a list: counter followed by the results. + If the results are truncated, counter will be set to -1. + """ + + sctrl = self.__get_server_controls__() + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + + entries = [] + partial = 0 + + try: + msgid = self.search_ext(*args) + type, result_list = self.result(msgid, 0) + while result_list: + for result in result_list: + entries.append(result) + type, result_list = self.result(msgid, 0) + except (ldap.ADMINLIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED, + ldap.TIMELIMIT_EXCEEDED), e: + partial = 1 + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + + if not entries: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, + notfound(args)) + + if partial == 1: + counter = -1 + else: + counter = len(entries) + + return [counter] + entries + + def addEntry(self,*args): + """This wraps the add function. It assumes that the entry is already + populated with all of the desired objectclasses and attributes""" + + sctrl = self.__get_server_controls__() + + try: + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + self.add_s(*args) + except ldap.ALREADY_EXISTS: + raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + return "Success" + + def updateRDN(self, dn, newrdn): + """Wrap the modrdn function.""" + + sctrl = self.__get_server_controls__() + + if dn == newrdn: + # no need to report an error + return "Success" + + try: + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + self.modrdn_s(dn, newrdn, delold=1) + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + return "Success" + + def updateEntry(self,dn,olduser,newuser): + """This wraps the mod function. It assumes that the entry is already + populated with all of the desired objectclasses and attributes""" + + sctrl = self.__get_server_controls__() + + modlist = self.generateModList(olduser, newuser) + + if len(modlist) == 0: + raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST) + + try: + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + self.modify_s(dn, modlist) + # this is raised when a 'delete' attribute isn't found. + # it indicates the previous attribute was removed by another + # update, making the olduser stale. + except ldap.NO_SUCH_ATTRIBUTE: + raise ipaerror.gen_exception(ipaerror.LDAP_MIDAIR_COLLISION) + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + return "Success" + + def generateModList(self, old_entry, new_entry): + """A mod list generator that computes more precise modification lists + than the python-ldap version. This version purposely generates no + REPLACE operations, to deal with multi-user updates more properly.""" + modlist = [] + + old_entry = ipautil.CIDict(old_entry) + new_entry = ipautil.CIDict(new_entry) + + keys = set(map(string.lower, old_entry.keys())) + keys.update(map(string.lower, new_entry.keys())) + + for key in keys: + new_values = new_entry.get(key, []) + if not(isinstance(new_values,list) or isinstance(new_values,tuple)): + new_values = [new_values] + new_values = filter(lambda value:value!=None, new_values) + new_values = set(new_values) + + old_values = old_entry.get(key, []) + if not(isinstance(old_values,list) or isinstance(old_values,tuple)): + old_values = [old_values] + old_values = filter(lambda value:value!=None, old_values) + old_values = set(old_values) + + adds = list(new_values.difference(old_values)) + removes = list(old_values.difference(new_values)) + + if len(removes) > 0: + modlist.append((ldap.MOD_DELETE, key, removes)) + if len(adds) > 0: + modlist.append((ldap.MOD_ADD, key, adds)) + + return modlist + + def inactivateEntry(self,dn,has_key): + """Rather than deleting entries we mark them as inactive. + has_key defines whether the entry already has nsAccountlock + set so we can determine which type of mod operation to run.""" + + sctrl = self.__get_server_controls__() + modlist=[] + + if has_key == True: + operation = ldap.MOD_REPLACE + else: + operation = ldap.MOD_ADD + + modlist.append((operation, "nsAccountlock", "true")) + + try: + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + self.modify_s(dn, modlist) + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + return "Success" + + def deleteEntry(self,*args): + """This wraps the delete function. Use with caution.""" + + sctrl = self.__get_server_controls__() + + try: + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + self.delete_s(*args) + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + return "Success" + + def modifyPassword(self,dn,oldpass,newpass): + """Set the user password using RFC 3062, LDAP Password Modify Extended + Operation. This ends up calling the IPA password slapi plugin + handler so the Kerberos password gets set properly. + + oldpass is not mandatory + """ + + sctrl = self.__get_server_controls__() + + try: + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + self.passwd_s(dn, oldpass, newpass) + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + return "Success" + + def __wrapmethods(self): + """This wraps all methods of SimpleLDAPObject, so that we can intercept + the methods that deal with entries. Instead of using a raw list of tuples + of lists of hashes of arrays as the entry object, we want to wrap entries + in an Entry class that provides some useful methods""" + for name in dir(self.__class__.__bases__[0]): + attr = getattr(self, name) + if callable(attr): + setattr(self, name, wrapper(attr, name)) + + def exportLDIF(self, file, suffix, forrepl=False, verbose=False): + cn = "export" + str(int(time.time())) + dn = "cn=%s, cn=export, cn=tasks, cn=config" % cn + entry = Entry(dn) + entry.setValues('objectclass', 'top', 'extensibleObject') + entry.setValues('cn', cn) + entry.setValues('nsFilename', file) + entry.setValues('nsIncludeSuffix', suffix) + if forrepl: + entry.setValues('nsExportReplica', 'true') + + rc = self.startTaskAndWait(entry, verbose) + + if rc: + if verbose: + print "Error: export task %s for file %s exited with %d" % (cn,file,rc) + else: + if verbose: + print "Export task %s for file %s completed successfully" % (cn,file) + return rc + + def waitForEntry(self, dn, timeout=7200, attr='', quiet=True): + scope = ldap.SCOPE_BASE + filter = "(objectclass=*)" + attrlist = [] + if attr: + filter = "(%s=*)" % attr + attrlist.append(attr) + timeout += int(time.time()) + + if isinstance(dn,Entry): + dn = dn.dn + + # wait for entry and/or attr to show up + if not quiet: + sys.stdout.write("Waiting for %s %s:%s " % (self,dn,attr)) + sys.stdout.flush() + entry = None + while not entry and int(time.time()) < timeout: + try: + entry = self.getEntry(dn, scope, filter, attrlist) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + pass # found entry, but no attr + except ldap.NO_SUCH_OBJECT: + pass # no entry yet + except ldap.LDAPError, e: # badness + print "\nError reading entry", dn, e + break + if not entry: + if not quiet: + sys.stdout.write(".") + sys.stdout.flush() + time.sleep(1) + + if not entry and int(time.time()) > timeout: + print "\nwaitForEntry timeout for %s for %s" % (self,dn) + elif entry and not quiet: + print "\nThe waited for entry is:", entry + elif not entry: + print "\nError: could not read entry %s from %s" % (dn,self) + + return entry + + def addSchema(self, attr, val): + dn = "cn=schema" + self.modify_s(dn, [(ldap.MOD_ADD, attr, val)]) + + def addAttr(self, *args): + return self.addSchema('attributeTypes', args) + + def addObjClass(self, *args): + return self.addSchema('objectClasses', args) + + ########################### + # Static methods start here + ########################### + def normalizeDN(dn): + # not great, but will do until we use a newer version of python-ldap + # that has DN utilities + ary = ldap.explode_dn(dn.lower()) + return ",".join(ary) + normalizeDN = staticmethod(normalizeDN) + + def getfqdn(name=''): + return socket.getfqdn(name) + getfqdn = staticmethod(getfqdn) + + def getdomainname(name=''): + fqdn = IPAdmin.getfqdn(name) + index = fqdn.find('.') + if index >= 0: + return fqdn[index+1:] + else: + return fqdn + getdomainname = staticmethod(getdomainname) + + def getdefaultsuffix(name=''): + dm = IPAdmin.getdomainname(name) + if dm: + return "dc=" + dm.replace('.', ', dc=') + else: + return 'dc=localdomain' + getdefaultsuffix = staticmethod(getdefaultsuffix) + + def is_a_dn(dn): + """Returns True if the given string is a DN, False otherwise.""" + return (dn.find("=") > 0) + is_a_dn = staticmethod(is_a_dn) + + +def notfound(args): + """Return a string suitable for displaying as an error when a + search returns no results. + + This just returns whatever is after the equals sign""" + if len(args) > 2: + filter = args[2] + try: + target = re.match(r'\(.*=(.*)\)', filter).group(1) + except: + target = filter + return "%s not found" % str(target) + else: + return args[0] diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py new file mode 100644 index 00000000..25284430 --- /dev/null +++ b/ipaserver/install/krbinstance.py @@ -0,0 +1,428 @@ +# Authors: Simo Sorce <ssorce@redhat.com> +# +# Copyright (C) 2007 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 +# + +import subprocess +import string +import tempfile +import shutil +import logging +import fileinput +import re +import sys +import os +import pwd +import socket +import shutil + +import service +import installutils +from ipa import sysrestore +from ipa import ipautil +from ipa import ipaerror + +import ipaldap + +import ldap +from ldap import LDAPError +from ldap import ldapobject + +from pyasn1.type import univ, namedtype +import pyasn1.codec.ber.encoder +import pyasn1.codec.ber.decoder +import struct +import base64 + +KRBMKEY_DENY_ACI = """ +(targetattr = "krbMKey")(version 3.0; acl "No external access"; deny (all) userdn != "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) +""" + +def update_key_val_in_file(filename, key, val): + if os.path.exists(filename): + pattern = "^[\s#]*%s\s*=\s*%s\s*" % (re.escape(key), re.escape(val)) + p = re.compile(pattern) + for line in fileinput.input(filename): + if p.search(line): + fileinput.close() + return + fileinput.close() + + pattern = "^[\s#]*%s\s*=" % re.escape(key) + p = re.compile(pattern) + for line in fileinput.input(filename, inplace=1): + if not p.search(line): + sys.stdout.write(line) + fileinput.close() + f = open(filename, "a") + f.write("%s=%s\n" % (key, val)) + f.close() + +class KpasswdInstance(service.SimpleServiceInstance): + def __init__(self): + service.SimpleServiceInstance.__init__(self, "ipa_kpasswd") + +class KrbInstance(service.Service): + def __init__(self, fstore=None): + service.Service.__init__(self, "krb5kdc") + self.ds_user = None + self.fqdn = None + self.realm = None + self.domain = None + self.host = None + self.admin_password = None + self.master_password = None + self.suffix = None + self.kdc_password = None + self.sub_dict = None + + self.kpasswd = KpasswdInstance() + + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + + def __common_setup(self, ds_user, realm_name, host_name, domain_name, admin_password): + self.ds_user = ds_user + self.fqdn = host_name + self.realm = realm_name.upper() + self.host = host_name.split(".")[0] + self.ip = socket.gethostbyname(host_name) + self.domain = domain_name + self.suffix = ipautil.realm_to_suffix(self.realm) + self.kdc_password = ipautil.ipa_generate_password() + self.admin_password = admin_password + + self.__setup_sub_dict() + + # get a connection to the DS + try: + self.conn = ipaldap.IPAdmin(self.fqdn) + self.conn.do_simple_bind(bindpw=self.admin_password) + except Exception, e: + logging.critical("Could not connect to the Directory Server on %s" % self.fqdn) + raise e + + self.backup_state("running", self.is_running()) + try: + self.stop() + except: + # It could have been not running + pass + + def __common_post_setup(self): + self.step("starting the KDC", self.__start_instance) + self.step("configuring KDC to start on boot", self.__enable) + + def create_instance(self, ds_user, realm_name, host_name, domain_name, admin_password, master_password): + self.master_password = master_password + + self.__common_setup(ds_user, realm_name, host_name, domain_name, admin_password) + + self.step("setting KDC account password", self.__configure_kdc_account_password) + self.step("adding sasl mappings to the directory", self.__configure_sasl_mappings) + self.step("adding kerberos entries to the DS", self.__add_krb_entries) + self.step("adding default ACIs", self.__add_default_acis) + self.step("configuring KDC", self.__create_instance) + self.step("adding default keytypes", self.__add_default_keytypes) + self.step("creating a keytab for the directory", self.__create_ds_keytab) + self.step("creating a keytab for the machine", self.__create_host_keytab) + self.step("exporting the kadmin keytab", self.__export_kadmin_changepw_keytab) + self.step("adding the password extension to the directory", self.__add_pwd_extop_module) + self.step("adding the kerberos master key to the directory", self.__add_master_key) + + self.__common_post_setup() + + self.start_creation("Configuring Kerberos KDC") + + self.kpasswd.create_instance() + + def create_replica(self, ds_user, realm_name, host_name, domain_name, admin_password, ldap_passwd_filename, kpasswd_filename): + self.__copy_ldap_passwd(ldap_passwd_filename) + self.__copy_kpasswd_keytab(kpasswd_filename) + + self.__common_setup(ds_user, realm_name, host_name, domain_name, admin_password) + + self.step("adding sasl mappings to the directory", self.__configure_sasl_mappings) + self.step("writing stash file from DS", self.__write_stash_from_ds) + self.step("configuring KDC", self.__create_replica_instance) + self.step("creating a keytab for the directory", self.__create_ds_keytab) + self.step("creating a keytab for the machine", self.__create_host_keytab) + self.step("adding the password extension to the directory", self.__add_pwd_extop_module) + + self.__common_post_setup() + + self.start_creation("Configuring Kerberos KDC") + + self.kpasswd.create_instance() + + def __copy_ldap_passwd(self, filename): + self.fstore.backup_file("/var/kerberos/krb5kdc/ldappwd") + shutil.copy(filename, "/var/kerberos/krb5kdc/ldappwd") + os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600) + + def __copy_kpasswd_keytab(self, filename): + self.fstore.backup_file("/var/kerberos/krb5kdc/kpasswd.keytab") + shutil.copy(filename, "/var/kerberos/krb5kdc/kpasswd.keytab") + os.chmod("/var/kerberos/krb5kdc/kpasswd.keytab", 0600) + + + def __configure_kdc_account_password(self): + hexpwd = '' + for x in self.kdc_password: + hexpwd += (hex(ord(x))[2:]) + self.fstore.backup_file("/var/kerberos/krb5kdc/ldappwd") + pwd_fd = open("/var/kerberos/krb5kdc/ldappwd", "w") + pwd_fd.write("uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix+"#{HEX}"+hexpwd+"\n") + pwd_fd.close() + os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600) + + def __enable(self): + self.backup_state("enabled", self.is_enabled()) + self.chkconfig_on() + + def __start_instance(self): + try: + self.start() + except: + logging.critical("krb5kdc service failed to start") + + def __setup_sub_dict(self): + self.sub_dict = dict(FQDN=self.fqdn, + IP=self.ip, + PASSWORD=self.kdc_password, + SUFFIX=self.suffix, + DOMAIN=self.domain, + HOST=self.host, + REALM=self.realm) + + def __ldap_mod(self, ldif): + txt = ipautil.template_file(ipautil.SHARE_DIR + ldif, self.sub_dict) + fd = ipautil.write_tmp_file(txt) + + [pw_fd, pw_name] = tempfile.mkstemp() + os.write(pw_fd, self.admin_password) + os.close(pw_fd) + + args = ["/usr/bin/ldapmodify", "-h", "127.0.0.1", "-xv", + "-D", "cn=Directory Manager", "-y", pw_name, "-f", fd.name] + + try: + try: + ipautil.run(args) + except ipautil.CalledProcessError, e: + logging.critical("Failed to load %s: %s" % (ldif, str(e))) + finally: + os.remove(pw_name) + + fd.close() + + def __configure_sasl_mappings(self): + # we need to remove any existing SASL mappings in the directory as otherwise they + # they may conflict. There is no way to define the order they are used in atm. + + # FIXME: for some reason IPAdmin dies here, so we switch + # it out for a regular ldapobject. + conn = self.conn + self.conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") + self.conn.bind("cn=directory manager", self.admin_password) + try: + msgid = self.conn.search("cn=mapping,cn=sasl,cn=config", ldap.SCOPE_ONELEVEL, "(objectclass=nsSaslMapping)") + res = self.conn.result(msgid) + for r in res[1]: + mid = self.conn.delete_s(r[0]) + #except LDAPError, e: + # logging.critical("Error during SASL mapping removal: %s" % str(e)) + except Exception, e: + logging.critical("Could not connect to the Directory Server on %s" % self.fqdn) + raise e + print type(e) + print dir(e) + raise e + + self.conn = conn + + entry = ipaldap.Entry("cn=Full Principal,cn=mapping,cn=sasl,cn=config") + entry.setValues("objectclass", "top", "nsSaslMapping") + entry.setValues("cn", "Full Principal") + entry.setValues("nsSaslMapRegexString", '\(.*\)@\(.*\)') + entry.setValues("nsSaslMapBaseDNTemplate", self.suffix) + entry.setValues("nsSaslMapFilterTemplate", '(krbPrincipalName=\\1@\\2)') + + try: + self.conn.add_s(entry) + except ldap.ALREADY_EXISTS: + logging.critical("failed to add Full Principal Sasl mapping") + raise e + + entry = ipaldap.Entry("cn=Name Only,cn=mapping,cn=sasl,cn=config") + entry.setValues("objectclass", "top", "nsSaslMapping") + entry.setValues("cn", "Name Only") + entry.setValues("nsSaslMapRegexString", '\(.*\)') + entry.setValues("nsSaslMapBaseDNTemplate", self.suffix) + entry.setValues("nsSaslMapFilterTemplate", '(krbPrincipalName=\\1@%s)' % self.realm) + + try: + self.conn.add_s(entry) + except ldap.ALREADY_EXISTS: + logging.critical("failed to add Name Only Sasl mapping") + raise e + + def __add_krb_entries(self): + self.__ldap_mod("kerberos.ldif") + + def __add_default_acis(self): + self.__ldap_mod("default-aci.ldif") + + def __add_default_keytypes(self): + self.__ldap_mod("default-keytypes.ldif") + + def __create_replica_instance(self): + self.__create_instance(replica=True) + + def __template_file(self, path): + template = os.path.join(ipautil.SHARE_DIR, os.path.basename(path) + ".template") + conf = ipautil.template_file(template, self.sub_dict) + self.fstore.backup_file(path) + fd = open(path, "w+") + fd.write(conf) + fd.close() + + def __create_instance(self, replica=False): + self.__template_file("/var/kerberos/krb5kdc/kdc.conf") + self.__template_file("/etc/krb5.conf") + self.__template_file("/usr/share/ipa/html/krb5.ini") + self.__template_file("/usr/share/ipa/html/krb.con") + self.__template_file("/usr/share/ipa/html/krbrealm.con") + + if not replica: + #populate the directory with the realm structure + args = ["/usr/kerberos/sbin/kdb5_ldap_util", "-D", "uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix, "-w", self.kdc_password, "create", "-s", "-P", self.master_password, "-r", self.realm, "-subtrees", self.suffix, "-sscope", "sub"] + try: + ipautil.run(args) + except ipautil.CalledProcessError, e: + print "Failed to populate the realm structure in kerberos", e + + def __write_stash_from_ds(self): + try: + entry = self.conn.getEntry("cn=%s, cn=kerberos, %s" % (self.realm, self.suffix), ldap.SCOPE_SUBTREE) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e: + logging.critical("Could not find master key in DS") + raise e + + krbMKey = pyasn1.codec.ber.decoder.decode(entry.krbmkey) + keytype = int(krbMKey[0][1][0]) + keydata = str(krbMKey[0][1][1]) + + format = '=hi%ss' % len(keydata) + s = struct.pack(format, keytype, len(keydata), keydata) + try: + fd = open("/var/kerberos/krb5kdc/.k5."+self.realm, "w") + fd.write(s) + fd.close() + except os.error, e: + logging.critical("failed to write stash file") + raise e + + #add the password extop module + def __add_pwd_extop_module(self): + self.__ldap_mod("pwd-extop-conf.ldif") + + def __add_master_key(self): + #get the Master Key from the stash file + try: + stash = open("/var/kerberos/krb5kdc/.k5."+self.realm, "r") + keytype = struct.unpack('h', stash.read(2))[0] + keylen = struct.unpack('i', stash.read(4))[0] + keydata = stash.read(keylen) + except os.error: + logging.critical("Failed to retrieve Master Key from Stash file: %s") + #encode it in the asn.1 attribute + MasterKey = univ.Sequence() + MasterKey.setComponentByPosition(0, univ.Integer(keytype)) + MasterKey.setComponentByPosition(1, univ.OctetString(keydata)) + krbMKey = univ.Sequence() + krbMKey.setComponentByPosition(0, univ.Integer(0)) #we have no kvno + krbMKey.setComponentByPosition(1, MasterKey) + asn1key = pyasn1.codec.ber.encoder.encode(krbMKey) + + dn = "cn="+self.realm+",cn=kerberos,"+self.suffix + #protect the master key by adding an appropriate deny rule along with the key + mod = [(ldap.MOD_ADD, 'aci', ipautil.template_str(KRBMKEY_DENY_ACI, self.sub_dict)), + (ldap.MOD_ADD, 'krbMKey', str(asn1key))] + try: + self.conn.modify_s(dn, mod) + except ldap.TYPE_OR_VALUE_EXISTS, e: + logging.critical("failed to add master key to kerberos database\n") + raise e + + def __create_ds_keytab(self): + ldap_principal = "ldap/" + self.fqdn + "@" + self.realm + installutils.kadmin_addprinc(ldap_principal) + + self.fstore.backup_file("/etc/dirsrv/ds.keytab") + installutils.create_keytab("/etc/dirsrv/ds.keytab", ldap_principal) + + self.fstore.backup_file("/etc/sysconfig/dirsrv") + update_key_val_in_file("/etc/sysconfig/dirsrv", "export KRB5_KTNAME", "/etc/dirsrv/ds.keytab") + pent = pwd.getpwnam(self.ds_user) + os.chown("/etc/dirsrv/ds.keytab", pent.pw_uid, pent.pw_gid) + + def __create_host_keytab(self): + host_principal = "host/" + self.fqdn + "@" + self.realm + installutils.kadmin_addprinc(host_principal) + + self.fstore.backup_file("/etc/krb5.keytab") + installutils.create_keytab("/etc/krb5.keytab", host_principal) + + # Make sure access is strictly reserved to root only for now + os.chown("/etc/krb5.keytab", 0, 0) + os.chmod("/etc/krb5.keytab", 0600) + + def __export_kadmin_changepw_keytab(self): + installutils.kadmin_modprinc("kadmin/changepw", "+requires_preauth") + + self.fstore.backup_file("/var/kerberos/krb5kdc/kpasswd.keytab") + installutils.create_keytab("/var/kerberos/krb5kdc/kpasswd.keytab", "kadmin/changepw") + + self.fstore.backup_file("/etc/sysconfig/ipa_kpasswd") + update_key_val_in_file("/etc/sysconfig/ipa_kpasswd", "export KRB5_KTNAME", "/var/kerberos/krb5kdc/kpasswd.keytab") + + def uninstall(self): + self.kpasswd.uninstall() + + running = self.restore_state("running") + enabled = self.restore_state("enabled") + + try: + self.stop() + except: + pass + + for f in ["/var/kerberos/krb5kdc/ldappwd", "/var/kerberos/krb5kdc/kdc.conf", "/etc/krb5.conf"]: + try: + self.fstore.restore_file(f) + except ValueError, error: + logging.debug(error) + pass + + if not enabled is None and not enabled: + self.chkconfig_off() + + if not running is None and running: + self.start() diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py new file mode 100755 index 00000000..cdf23125 --- /dev/null +++ b/ipaserver/install/ldapupdate.py @@ -0,0 +1,593 @@ +# Authors: Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 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 +# + +# Documentation can be found at http://freeipa.org/page/LdapUpdate + +# TODO +# save undo files? + +UPDATES_DIR="/usr/share/ipa/updates/" + +import sys +from ipaserver import ipaldap, installutils +from ipa import entity, ipaerror, ipautil +import ldap +import logging +import krbV +import platform +import shlex +import time +import random +import os +import fnmatch + +class BadSyntax(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class LDAPUpdate: + def __init__(self, dm_password, sub_dict={}, live_run=True): + """dm_password = Directory Manager password + sub_dict = substitution dictionary + live_run = Apply the changes or just test + """ + self.sub_dict = sub_dict + self.live_run = live_run + self.dm_password = dm_password + self.conn = None + self.modified = False + + krbctx = krbV.default_context() + + fqdn = installutils.get_fqdn() + if fqdn is None: + raise RuntimeError("Unable to determine hostname") + + domain = ipautil.get_domain_name() + libarch = self.__identify_arch() + suffix = ipautil.realm_to_suffix(krbctx.default_realm) + + if not self.sub_dict.get("REALM"): + self.sub_dict["REALM"] = krbctx.default_realm + if not self.sub_dict.get("FQDN"): + self.sub_dict["FQDN"] = fqdn + if not self.sub_dict.get("DOMAIN"): + self.sub_dict["DOMAIN"] = domain + if not self.sub_dict.get("SUFFIX"): + self.sub_dict["SUFFIX"] = suffix + if not self.sub_dict.get("LIBARCH"): + self.sub_dict["LIBARCH"] = libarch + if not self.sub_dict.get("TIME"): + self.sub_dict["TIME"] = int(time.time()) + + # Try out the password + try: + conn = ipaldap.IPAdmin(fqdn) + conn.do_simple_bind(bindpw=self.dm_password) + conn.unbind() + except ldap.CONNECT_ERROR, e: + raise RuntimeError("Unable to connect to LDAP server %s" % fqdn) + except ldap.SERVER_DOWN, e: + raise RuntimeError("Unable to connect to LDAP server %s" % fqdn) + except ldap.INVALID_CREDENTIALS, e : + raise RuntimeError("The password provided is incorrect for LDAP server %s" % fqdn) + + def __detail_error(self, detail): + """IPA returns two errors back. One a generic one indicating the broad + problem and a detailed message back as well which should have come + from LDAP. This function will parse that into a human-readable + string. + """ + msg = "" + desc = detail[0].get('desc') + info = detail[0].get('info') + + if desc: + msg = desc + if info: + msg = msg + " " + info + + return msg + + def __identify_arch(self): + """On multi-arch systems some libraries may be in /lib64, /usr/lib64, + etc. Determine if a suffix is needed based on the current + architecture. + """ + bits = platform.architecture()[0] + + if bits == "64bit": + return "64" + else: + return "" + + def __template_str(self, s): + try: + return ipautil.template_str(s, self.sub_dict) + except KeyError, e: + raise BadSyntax("Unknown template keyword %s" % e) + + def __remove_quotes(self, line): + """Remove leading and trailng double or single quotes""" + if line.startswith('"'): + line = line[1:] + if line.endswith('"'): + line = line[:-1] + if line.startswith("'"): + line = line[1:] + if line.endswith("'"): + line = line[:-1] + + return line + + def __parse_values(self, line): + """Parse a comma-separated string into separate values and convert them + into a list. This should handle quoted-strings with embedded commas + """ + lexer = shlex.shlex(line) + lexer.wordchars = lexer.wordchars + ".()-" + l = [] + v = "" + for token in lexer: + if token != ',': + if v: + v = v + " " + token + else: + v = token + else: + l.append(self.__remove_quotes(v)) + v = "" + + l.append(self.__remove_quotes(v)) + + return l + + def read_file(self, filename): + if filename == '-': + fd = sys.stdin + else: + fd = open(filename) + text = fd.readlines() + if fd != sys.stdin: fd.close() + return text + + def __entry_to_entity(self, ent): + """Tne Entry class is a bare LDAP entry. The Entity class has a lot more + helper functions that we need, so convert to dict and then to Entity. + """ + entry = dict(ent.data) + entry['dn'] = ent.dn + for key,value in entry.iteritems(): + if isinstance(value,list) or isinstance(value,tuple): + if len(value) == 0: + entry[key] = '' + elif len(value) == 1: + entry[key] = value[0] + return entity.Entity(entry) + + def __combine_updates(self, dn_list, all_updates, update): + """Combine a new update with the list of total updates + + Updates are stored in 2 lists: + dn_list: contains a unique list of DNs in the updates + all_updates: the actual updates that need to be applied + + We want to apply the updates from the shortest to the longest + path so if new child and parent entries are in different updates + we can be sure the parent gets written first. This also lets + us apply any schema first since it is in the very short cn=schema. + """ + dn = update.get('dn') + dns = ldap.explode_dn(dn.lower()) + l = len(dns) + if dn_list.get(l): + if dn not in dn_list[l]: + dn_list[l].append(dn) + else: + dn_list[l] = [dn] + if not all_updates.get(dn): + all_updates[dn] = update + return all_updates + + e = all_updates[dn] + e['updates'] = e['updates'] + update['updates'] + + all_updates[dn] = e + + return all_updates + + def parse_update_file(self, data, all_updates, dn_list): + """Parse the update file into a dictonary of lists and apply the update + for each DN in the file.""" + valid_keywords = ["default", "add", "remove", "only"] + update = {} + d = "" + index = "" + dn = None + lcount = 0 + for line in data: + # Strip out \n and extra white space + lcount = lcount + 1 + + # skip comments and empty lines + line = line.rstrip() + if line.startswith('#') or line == '': continue + + if line.lower().startswith('dn:'): + if dn is not None: + all_updates = self.__combine_updates(dn_list, all_updates, update) + + update = {} + dn = line[3:].strip() + update['dn'] = self.__template_str(dn) + else: + if dn is None: + raise BadSyntax, "dn is not defined in the update" + + if line.startswith(' '): + v = d[len(d) - 1] + v = v + " " + line.strip() + d[len(d) - 1] = v + update[index] = d + continue + line = line.strip() + values = line.split(':', 2) + if len(values) != 3: + raise BadSyntax, "Bad formatting on line %d: %s" % (lcount,line) + + index = values[0].strip().lower() + + if index not in valid_keywords: + raise BadSyntax, "Unknown keyword %s" % index + + attr = values[1].strip() + value = values[2].strip() + value = self.__template_str(value) + + new_value = "" + if index == "default": + new_value = attr + ":" + value + else: + new_value = index + ":" + attr + ":" + value + index = "updates" + + d = update.get(index, []) + + d.append(new_value) + + update[index] = d + + if dn is not None: + all_updates = self.__combine_updates(dn_list, all_updates, update) + + return (all_updates, dn_list) + + def create_index_task(self, attribute): + """Create a task to update an index for an attribute""" + + r = random.SystemRandom() + + # Refresh the time to make uniqueness more probable. Add on some + # randomness for good measure. + self.sub_dict['TIME'] = int(time.time()) + r.randint(0,10000) + + cn = self.__template_str("indextask_$TIME") + dn = "cn=%s, cn=index, cn=tasks, cn=config" % cn + + e = ipaldap.Entry(dn) + + e.setValues('objectClass', ['top', 'extensibleObject']) + e.setValue('cn', cn) + e.setValue('nsInstance', 'userRoot') + e.setValues('nsIndexAttribute', attribute) + + logging.info("Creating task to index attribute: %s", attribute) + logging.debug("Task id: %s", dn) + + if self.live_run: + self.conn.addEntry(e.dn, e.toTupleList()) + + return dn + + def monitor_index_task(self, dn): + """Give a task DN monitor it and wait until it has completed (or failed) + """ + + if not self.live_run: + # If not doing this live there is nothing to monitor + return + + # Pause for a moment to give the task time to be created + time.sleep(1) + + attrlist = ['nstaskstatus', 'nstaskexitcode'] + entry = None + + while True: + try: + entry = self.conn.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + logging.error("Task not found: %s", dn) + return + except ipaerror.exception_for(ipaerror.LDAP_DATABASE_ERROR), e: + logging.error("Task lookup failure %s: %s", e, self.__detail_error(e.detail)) + return + + status = entry.getValue('nstaskstatus') + if status is None: + # task doesn't have a status yet + time.sleep(1) + continue + + if status.lower().find("finished") > -1: + logging.info("Indexing finished") + break + + logging.debug("Indexing in progress") + time.sleep(1) + + return + + def __create_default_entry(self, dn, default): + """Create the default entry from the values provided. + + The return type is entity.Entity + """ + entry = ipaldap.Entry(dn) + + if not default: + # This means that the entire entry needs to be created with add + return self.__entry_to_entity(entry) + + for line in default: + # We already do syntax-parsing so this is safe + (k, v) = line.split(':',1) + e = entry.getValues(k) + if e: + # multi-valued attribute + e = list(e) + e.append(v) + else: + e = v + entry.setValues(k, e) + + return self.__entry_to_entity(entry) + + def __get_entry(self, dn): + """Retrieve an object from LDAP. + + The return type is ipaldap.Entry + """ + searchfilter="objectclass=*" + sattrs = ["*"] + scope = ldap.SCOPE_BASE + + return self.conn.getList(dn, scope, searchfilter, sattrs) + + def __apply_updates(self, updates, entry): + """updates is a list of changes to apply + entry is the thing to apply them to + + returns the modified entry + """ + if not updates: + return entry + + only = {} + for u in updates: + # We already do syntax-parsing so this is safe + (utype, k, values) = u.split(':',2) + + values = self.__parse_values(values) + + e = entry.getValues(k) + if not isinstance(e, list): + if e is None: + e = [] + else: + e = [e] + + for v in values: + if utype == 'remove': + logging.debug("remove: '%s' from %s, current value %s", v, k, e) + try: + e.remove(v) + except ValueError: + logging.warn("remove: '%s' not in %s", v, k) + pass + entry.setValues(k, e) + logging.debug('remove: updated value %s', e) + elif utype == 'add': + logging.debug("add: '%s' to %s, current value %s", v, k, e) + # Remove it, ignoring errors so we can blindly add it later + try: + e.remove(v) + except ValueError: + pass + e.append(v) + logging.debug('add: updated value %s', e) + entry.setValues(k, e) + elif utype == 'only': + logging.debug("only: set %s to '%s', current value %s", k, v, e) + if only.get(k): + e.append(v) + else: + e = [v] + only[k] = True + entry.setValues(k, e) + logging.debug('only: updated value %s', e) + + self.print_entity(entry) + + return entry + + def print_entity(self, e, message=None): + """The entity object currently lacks a str() method""" + logging.debug("---------------------------------------------") + if message: + logging.debug("%s", message) + logging.debug("dn: " + e.dn) + attr = e.attrList() + for a in attr: + value = e.getValues(a) + if isinstance(value,str): + logging.debug(a + ": " + value) + else: + logging.debug(a + ": ") + for l in value: + logging.debug("\t" + l) + def is_schema_updated(self, s): + """Compare the schema in 's' with the current schema in the DS to + see if anything has changed. This should account for syntax + differences (like added parens that make no difference but are + detected as a change by generateModList()). + + This doesn't handle re-ordering of attributes. They are still + detected as changes, so foo $ bar != bar $ foo. + + return True if the schema has changed + return False if it has not + """ + s = ldap.schema.SubSchema(s) + s = s.ldap_entry() + + # Get a fresh copy and convert into a SubSchema + n = self.__get_entry("cn=schema")[0] + n = dict(n.data) + n = ldap.schema.SubSchema(n) + n = n.ldap_entry() + + if s == n: + return False + else: + return True + + def __update_record(self, update): + found = False + + new_entry = self.__create_default_entry(update.get('dn'), + update.get('default')) + + try: + e = self.__get_entry(new_entry.dn) + if len(e) > 1: + # we should only ever get back one entry + raise BadSyntax, "More than 1 entry returned on a dn search!? %s" % new_entry.dn + entry = self.__entry_to_entity(e[0]) + found = True + logging.info("Updating existing entry: %s", entry.dn) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + # Doesn't exist, start with the default entry + entry = new_entry + logging.info("New entry: %s", entry.dn) + except ipaerror.exception_for(ipaerror.LDAP_DATABASE_ERROR): + # Doesn't exist, start with the default entry + entry = new_entry + logging.info("New entry, using default value: %s", entry.dn) + + self.print_entity(entry) + + # Bring this entry up to date + entry = self.__apply_updates(update.get('updates'), entry) + + self.print_entity(entry, "Final value") + + if not found: + # New entries get their orig_data set to the entry itself. We want to + # empty that so that everything appears new when generating the + # modlist + # entry.orig_data = {} + try: + if self.live_run: + self.conn.addEntry(entry.dn, entry.toTupleList()) + except Exception, e: + logging.error("Add failure %s: %s", e, self.__detail_error(e.detail)) + else: + # Update LDAP + try: + updated = False + changes = self.conn.generateModList(entry.origDataDict(), entry.toDict()) + if (entry.dn == "cn=schema"): + updated = self.is_schema_updated(entry.toDict()) + else: + if len(changes) > 1: + updated = True + logging.debug("%s" % changes) + if self.live_run and updated: + self.conn.updateEntry(entry.dn, entry.origDataDict(), entry.toDict()) + logging.info("Done") + except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST), e: + logging.info("Entry already up-to-date") + updated = False + except ipaerror.exception_for(ipaerror.LDAP_DATABASE_ERROR), e: + logging.error("Update failed: %s: %s", e, self.__detail_error(e.detail)) + updated = False + + if ("cn=index" in entry.dn and + "cn=userRoot" in entry.dn): + taskid = self.create_index_task(entry.cn) + self.monitor_index_task(taskid) + + if updated: + self.modified = True + return + + def get_all_files(self, root, recursive=False): + """Get all update files""" + f = [] + for path, subdirs, files in os.walk(root): + for name in files: + if fnmatch.fnmatch(name, "*.update"): + f.append(os.path.join(path, name)) + if not recursive: + break + return f + + def update(self, files): + """Execute the update. files is a list of the update files to use. + + returns True if anything was changed, otherwise False + """ + + try: + self.conn = ipaldap.IPAdmin(self.sub_dict['FQDN']) + self.conn.do_simple_bind(bindpw=self.dm_password) + all_updates = {} + dn_list = {} + for f in files: + try: + logging.info("Parsing file %s" % f) + data = self.read_file(f) + except Exception, e: + print e + sys.exit(1) + + (all_updates, dn_list) = self.parse_update_file(data, all_updates, dn_list) + + sortedkeys = dn_list.keys() + sortedkeys.sort() + for k in sortedkeys: + for dn in dn_list[k]: + self.__update_record(all_updates[dn]) + finally: + if self.conn: self.conn.unbind() + + return self.modified diff --git a/ipaserver/install/ntpinstance.py b/ipaserver/install/ntpinstance.py new file mode 100644 index 00000000..e2ec6065 --- /dev/null +++ b/ipaserver/install/ntpinstance.py @@ -0,0 +1,107 @@ +# Authors: Karl MacMillan <kmacmillan@redhat.com> +# +# Copyright (C) 2007 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 +# + +import shutil +import logging + +import service +from ipa import sysrestore +from ipa import ipautil + +class NTPInstance(service.Service): + def __init__(self, fstore=None): + service.Service.__init__(self, "ntpd") + + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + + def __write_config(self): + # The template sets the config to point towards ntp.pool.org, but + # they request that software not point towards the default pool. + # We use the OS variable to point it towards either the rhel + # or fedora pools. Other distros should be added in the future + # or we can get our own pool. + os = "" + if ipautil.file_exists("/etc/fedora-release"): + os = "fedora" + elif ipautil.file_exists("/etc/redhat-release"): + os = "rhel" + + sub_dict = { } + sub_dict["SERVERA"] = "0.%s.pool.ntp.org" % os + sub_dict["SERVERB"] = "1.%s.pool.ntp.org" % os + sub_dict["SERVERC"] = "2.%s.pool.ntp.org" % os + + ntp_conf = ipautil.template_file(ipautil.SHARE_DIR + "ntp.conf.server.template", sub_dict) + ntp_sysconf = ipautil.template_file(ipautil.SHARE_DIR + "ntpd.sysconfig.template", {}) + + self.fstore.backup_file("/etc/ntp.conf") + self.fstore.backup_file("/etc/sysconfig/ntpd") + + fd = open("/etc/ntp.conf", "w") + fd.write(ntp_conf) + fd.close() + + fd = open("/etc/sysconfig/ntpd", "w") + fd.write(ntp_sysconf) + fd.close() + + def __stop(self): + self.backup_state("running", self.is_running()) + self.stop() + + def __start(self): + self.start() + + def __enable(self): + self.backup_state("enabled", self.is_enabled()) + self.chkconfig_on() + + def create_instance(self): + + # we might consider setting the date manually using ntpd -qg in case + # the current time is very far off. + + self.step("stopping ntpd", self.__stop) + self.step("writing configuration", self.__write_config) + self.step("configuring ntpd to start on boot", self.__enable) + self.step("starting ntpd", self.__start) + + self.start_creation("Configuring ntpd") + + def uninstall(self): + running = self.restore_state("running") + enabled = self.restore_state("enabled") + + if not running is None: + self.stop() + + try: + self.fstore.restore_file("/etc/ntp.conf") + except ValueError, error: + logging.debug(error) + pass + + if not enabled is None and not enabled: + self.chkconfig_off() + + if not running is None and running: + self.start() diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py new file mode 100644 index 00000000..8477bd18 --- /dev/null +++ b/ipaserver/install/replication.py @@ -0,0 +1,532 @@ +# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com> +# +# Copyright (C) 2007 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 +# + +import time, logging + +import ipaldap, ldap, dsinstance +from ldap import modlist +from ipa import ipaerror + +DIRMAN_CN = "cn=directory manager" +CACERT="/usr/share/ipa/html/ca.crt" +# the default container used by AD for user entries +WIN_USER_CONTAINER="cn=Users" +# the default container used by IPA for user entries +IPA_USER_CONTAINER="cn=users,cn=accounts" +PORT = 636 +TIMEOUT = 120 + +IPA_REPLICA = 1 +WINSYNC = 2 + +class ReplicationManager: + """Manage replication agreements between DS servers, and sync + agreements with Windows servers""" + def __init__(self, hostname, dirman_passwd): + self.hostname = hostname + self.dirman_passwd = dirman_passwd + + self.conn = ipaldap.IPAdmin(hostname, port=PORT, cacert=CACERT) + self.conn.do_simple_bind(bindpw=dirman_passwd) + + self.repl_man_passwd = dirman_passwd + + # these are likely constant, but you could change them + # at runtime if you really want + self.repl_man_dn = "cn=replication manager,cn=config" + self.repl_man_cn = "replication manager" + self.suffix = "" + + def _get_replica_id(self, conn, master_conn): + """ + Returns the replica ID which is unique for each backend. + + conn is the connection we are trying to get the replica ID for. + master_conn is the master we are going to replicate with. + """ + # First see if there is already one set + dn = self.replica_dn() + try: + replica = conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0] + if replica.getValue('nsDS5ReplicaId'): + return int(replica.getValue('nsDS5ReplicaId')) + except ldap.NO_SUCH_OBJECT: + pass + + # Ok, either the entry doesn't exist or the attribute isn't set + # so get it from the other master + retval = -1 + dn = "cn=replication, cn=etc, %s" % self.suffix + try: + replica = master_conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0] + if not replica.getValue('nsDS5ReplicaId'): + logging.debug("Unable to retrieve nsDS5ReplicaId from remote server") + raise RuntimeError("Unable to retrieve nsDS5ReplicaId from remote server") + except ldap.NO_SUCH_OBJECT: + logging.debug("Unable to retrieve nsDS5ReplicaId from remote server") + raise + + # Now update the value on the master + retval = int(replica.getValue('nsDS5ReplicaId')) + mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaId', str(retval + 1))] + + try: + master_conn.modify_s(dn, mod) + except Exception, e: + logging.debug("Problem updating nsDS5ReplicaID %s" % e) + raise + + return retval + + def find_replication_dns(self, conn): + filt = "(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement))" + try: + ents = conn.search_s("cn=mapping tree,cn=config", ldap.SCOPE_SUBTREE, filt) + except ldap.NO_SUCH_OBJECT: + return [] + return [ent.dn for ent in ents] + + def add_replication_manager(self, conn, passwd=None): + """ + Create a pseudo user to use for replication. If no password + is provided the directory manager password will be used. + """ + + if passwd: + self.repl_man_passwd = passwd + + ent = ipaldap.Entry(self.repl_man_dn) + ent.setValues("objectclass", "top", "person") + ent.setValues("cn", self.repl_man_cn) + ent.setValues("userpassword", self.repl_man_passwd) + ent.setValues("sn", "replication manager pseudo user") + + try: + conn.add_s(ent) + except ldap.ALREADY_EXISTS: + # should we set the password here? + pass + + def delete_replication_manager(self, conn, dn="cn=replication manager,cn=config"): + try: + conn.delete_s(dn) + except ldap.NO_SUCH_OBJECT: + pass + + def get_replica_type(self, master=True): + if master: + return "3" + else: + return "2" + + def replica_dn(self): + return 'cn=replica, cn="%s", cn=mapping tree, cn=config' % self.suffix + + def local_replica_config(self, conn, replica_id): + dn = self.replica_dn() + + try: + conn.getEntry(dn, ldap.SCOPE_BASE) + # replication is already configured + return + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + pass + + replica_type = self.get_replica_type() + + entry = ipaldap.Entry(dn) + entry.setValues('objectclass', "top", "nsds5replica", "extensibleobject") + entry.setValues('cn', "replica") + entry.setValues('nsds5replicaroot', self.suffix) + entry.setValues('nsds5replicaid', str(replica_id)) + entry.setValues('nsds5replicatype', replica_type) + entry.setValues('nsds5flags', "1") + entry.setValues('nsds5replicabinddn', [self.repl_man_dn]) + entry.setValues('nsds5replicalegacyconsumer', "off") + + conn.add_s(entry) + + def setup_changelog(self, conn): + dn = "cn=changelog5, cn=config" + dirpath = conn.dbdir + "/cldb" + entry = ipaldap.Entry(dn) + entry.setValues('objectclass', "top", "extensibleobject") + entry.setValues('cn', "changelog5") + entry.setValues('nsslapd-changelogdir', dirpath) + try: + conn.add_s(entry) + except ldap.ALREADY_EXISTS: + return + + def setup_chaining_backend(self, conn): + chaindn = "cn=chaining database, cn=plugins, cn=config" + benamebase = "chaindb" + urls = [self.to_ldap_url(conn)] + cn = "" + benum = 1 + done = False + while not done: + try: + cn = benamebase + str(benum) # e.g. localdb1 + dn = "cn=" + cn + ", " + chaindn + entry = ipaldap.Entry(dn) + entry.setValues('objectclass', 'top', 'extensibleObject', 'nsBackendInstance') + entry.setValues('cn', cn) + entry.setValues('nsslapd-suffix', self.suffix) + entry.setValues('nsfarmserverurl', urls) + entry.setValues('nsmultiplexorbinddn', self.repl_man_dn) + entry.setValues('nsmultiplexorcredentials', self.repl_man_passwd) + + self.conn.add_s(entry) + done = True + except ldap.ALREADY_EXISTS: + benum += 1 + except ldap.LDAPError, e: + print "Could not add backend entry " + dn, e + raise + + return cn + + def to_ldap_url(self, conn): + return "ldap://%s:%d/" % (conn.host, conn.port) + + def setup_chaining_farm(self, conn): + try: + conn.modify_s(self.suffix, [(ldap.MOD_ADD, 'aci', + [ "(targetattr = \"*\")(version 3.0; acl \"Proxied authorization for database links\"; allow (proxy) userdn = \"ldap:///%s\";)" % self.repl_man_dn ])]) + except ldap.TYPE_OR_VALUE_EXISTS: + logging.debug("proxy aci already exists in suffix %s on %s" % (self.suffix, conn.host)) + + def get_mapping_tree_entry(self): + try: + entry = self.conn.getEntry("cn=mapping tree,cn=config", ldap.SCOPE_ONELEVEL, + "(cn=\"%s\")" % (self.suffix)) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e: + logging.debug("failed to find mappting tree entry for %s" % self.suffix) + raise e + + return entry + + + def enable_chain_on_update(self, bename): + mtent = self.get_mapping_tree_entry() + dn = mtent.dn + + plgent = self.conn.getEntry("cn=Multimaster Replication Plugin,cn=plugins,cn=config", + ldap.SCOPE_BASE, "(objectclass=*)", ['nsslapd-pluginPath']) + path = plgent.getValue('nsslapd-pluginPath') + + mod = [(ldap.MOD_REPLACE, 'nsslapd-state', 'backend'), + (ldap.MOD_ADD, 'nsslapd-backend', bename), + (ldap.MOD_ADD, 'nsslapd-distribution-plugin', path), + (ldap.MOD_ADD, 'nsslapd-distribution-funct', 'repl_chain_on_update')] + + try: + self.conn.modify_s(dn, mod) + except ldap.TYPE_OR_VALUE_EXISTS: + logging.debug("chainOnUpdate already enabled for %s" % self.suffix) + + def setup_chain_on_update(self, other_conn): + chainbe = self.setup_chaining_backend(other_conn) + self.enable_chain_on_update(chainbe) + + def add_passsync_user(self, conn, password): + pass_dn = "uid=passsync,cn=sysaccounts,cn=etc,%s" % self.suffix + print "The user for the Windows PassSync service is %s" % pass_dn + try: + conn.getEntry(pass_dn, ldap.SCOPE_BASE) + print "Windows PassSync entry exists, not resetting password" + return + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + pass + + # The user doesn't exist, add it + entry = ipaldap.Entry(pass_dn) + entry.setValues("objectclass", ["account", "simplesecurityobject"]) + entry.setValues("uid", "passsync") + entry.setValues("userPassword", password) + conn.add_s(entry) + + # Add it to the list of users allowed to bypass password policy + extop_dn = "cn=ipa_pwd_extop,cn=plugins,cn=config" + entry = conn.getEntry(extop_dn, ldap.SCOPE_BASE) + pass_mgrs = entry.getValues('passSyncManagersDNs') + if not pass_mgrs: + pass_mgrs = [] + if not isinstance(pass_mgrs, list): + pass_mgrs = [pass_mgrs] + pass_mgrs.append(pass_dn) + mod = [(ldap.MOD_REPLACE, 'passSyncManagersDNs', pass_mgrs)] + conn.modify_s(extop_dn, mod) + + # And finally grant it permission to write passwords + mod = [(ldap.MOD_ADD, 'aci', + ['(targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Windows PassSync service can write passwords"; allow (write) userdn="ldap:///%s";)' % pass_dn])] + try: + conn.modify_s(self.suffix, mod) + except ldap.TYPE_OR_VALUE_EXISTS: + logging.debug("passsync aci already exists in suffix %s on %s" % (self.suffix, conn.host)) + + def setup_winsync_agmt(self, entry, **kargs): + entry.setValues("objectclass", "nsDSWindowsReplicationAgreement") + entry.setValues("nsds7WindowsReplicaSubtree", + kargs.get("win_subtree", + WIN_USER_CONTAINER + "," + self.suffix)) + entry.setValues("nsds7DirectoryReplicaSubtree", + kargs.get("ds_subtree", + IPA_USER_CONTAINER + "," + self.suffix)) + # for now, just sync users and ignore groups + entry.setValues("nsds7NewWinUserSyncEnabled", kargs.get('newwinusers', 'true')) + entry.setValues("nsds7NewWinGroupSyncEnabled", kargs.get('newwingroups', 'false')) + windomain = '' + if kargs.has_key('windomain'): + windomain = kargs['windomain'] + else: + windomain = '.'.join(ldap.explode_dn(self.suffix, 1)) + entry.setValues("nsds7WindowsDomain", windomain) + + def agreement_dn(self, hostname, port=PORT): + cn = "meTo%s%d" % (hostname, port) + dn = "cn=%s, %s" % (cn, self.replica_dn()) + + return (cn, dn) + + def setup_agreement(self, a, b, **kargs): + cn, dn = self.agreement_dn(b.host) + try: + a.getEntry(dn, ldap.SCOPE_BASE) + return + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + pass + + iswinsync = kargs.get("winsync", False) + repl_man_dn = kargs.get("binddn", self.repl_man_dn) + repl_man_passwd = kargs.get("bindpw", self.repl_man_passwd) + port = kargs.get("port", PORT) + + entry = ipaldap.Entry(dn) + entry.setValues('objectclass', "nsds5replicationagreement") + entry.setValues('cn', cn) + entry.setValues('nsds5replicahost', b.host) + entry.setValues('nsds5replicaport', str(port)) + entry.setValues('nsds5replicatimeout', str(TIMEOUT)) + entry.setValues('nsds5replicabinddn', repl_man_dn) + entry.setValues('nsds5replicacredentials', repl_man_passwd) + entry.setValues('nsds5replicabindmethod', 'simple') + entry.setValues('nsds5replicaroot', self.suffix) + entry.setValues('nsds5replicaupdateschedule', '0000-2359 0123456') + entry.setValues('nsds5replicatransportinfo', 'SSL') + entry.setValues('nsDS5ReplicatedAttributeList', '(objectclass=*) $ EXCLUDE memberOf') + entry.setValues('description', "me to %s%d" % (b.host, port)) + if iswinsync: + self.setup_winsync_agmt(entry, **kargs) + + a.add_s(entry) + + entry = a.waitForEntry(entry) + + def delete_agreement(self, hostname): + cn, dn = self.agreement_dn(hostname) + return self.conn.deleteEntry(dn) + + def check_repl_init(self, conn, agmtdn): + done = False + hasError = 0 + attrlist = ['cn', 'nsds5BeginReplicaRefresh', 'nsds5replicaUpdateInProgress', + 'nsds5ReplicaLastInitStatus', 'nsds5ReplicaLastInitStart', + 'nsds5ReplicaLastInitEnd'] + entry = conn.getEntry(agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) + if not entry: + print "Error reading status from agreement", agmtdn + hasError = 1 + else: + refresh = entry.nsds5BeginReplicaRefresh + inprogress = entry.nsds5replicaUpdateInProgress + status = entry.nsds5ReplicaLastInitStatus + if not refresh: # done - check status + if not status: + print "No status yet" + elif status.find("replica busy") > -1: + print "[%s] reports: Replica Busy! Status: [%s]" % (conn.host, status) + done = True + hasError = 2 + elif status.find("Total update succeeded") > -1: + print "Update succeeded" + done = True + elif inprogress.lower() == 'true': + print "Update in progress yet not in progress" + else: + print "[%s] reports: Update failed! Status: [%s]" % (conn.host, status) + hasError = 1 + done = True + else: + print "Update in progress" + + return done, hasError + + def check_repl_update(self, conn, agmtdn): + done = False + hasError = 0 + attrlist = ['cn', 'nsds5replicaUpdateInProgress', + 'nsds5ReplicaLastUpdateStatus', 'nsds5ReplicaLastUpdateStart', + 'nsds5ReplicaLastUpdateEnd'] + entry = conn.getEntry(agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) + if not entry: + print "Error reading status from agreement", agmtdn + hasError = 1 + else: + inprogress = entry.nsds5replicaUpdateInProgress + status = entry.nsds5ReplicaLastUpdateStatus + start = entry.nsds5ReplicaLastUpdateStart + end = entry.nsds5ReplicaLastUpdateEnd + # incremental update is done if inprogress is false and end >= start + done = inprogress and inprogress.lower() == 'false' and start and end and (start <= end) + logging.info("Replication Update in progress: %s: status: %s: start: %s: end: %s" % + (inprogress, status, start, end)) + if not done and status: # check for errors + # status will usually be a number followed by a string + # number != 0 means error + rc, msg = status.split(' ', 1) + if rc != '0': + hasError = 1 + done = True + + return done, hasError + + def wait_for_repl_init(self, conn, agmtdn): + done = False + haserror = 0 + while not done and not haserror: + time.sleep(1) # give it a few seconds to get going + done, haserror = self.check_repl_init(conn, agmtdn) + return haserror + + def wait_for_repl_update(self, conn, agmtdn, maxtries=600): + done = False + haserror = 0 + while not done and not haserror and maxtries > 0: + time.sleep(1) # give it a few seconds to get going + done, haserror = self.check_repl_update(conn, agmtdn) + maxtries -= 1 + if maxtries == 0: # too many tries + print "Error: timeout: could not determine agreement status: please check your directory server logs for possible errors" + haserror = 1 + return haserror + + def start_replication(self, other_conn, conn=None): + print "Starting replication, please wait until this has completed." + if conn == None: + conn = self.conn + cn, dn = self.agreement_dn(conn.host) + + mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')] + other_conn.modify_s(dn, mod) + + return self.wait_for_repl_init(other_conn, dn) + + def basic_replication_setup(self, conn, replica_id): + self.add_replication_manager(conn) + self.local_replica_config(conn, replica_id) + self.setup_changelog(conn) + + def setup_replication(self, other_hostname, realm_name, **kargs): + """ + NOTES: + - the directory manager password needs to be the same on + both directories. Or use the optional binddn and bindpw + """ + iswinsync = kargs.get("winsync", False) + oth_port = kargs.get("port", PORT) + oth_cacert = kargs.get("cacert", CACERT) + oth_binddn = kargs.get("binddn", DIRMAN_CN) + oth_bindpw = kargs.get("bindpw", self.dirman_passwd) + # note - there appears to be a bug in python-ldap - it does not + # allow connections using two different CA certs + other_conn = ipaldap.IPAdmin(other_hostname, port=oth_port, cacert=oth_cacert) + try: + other_conn.do_simple_bind(binddn=oth_binddn, bindpw=oth_bindpw) + except Exception, e: + if iswinsync: + logging.info("Could not validate connection to remote server %s:%d - continuing" % + (other_hostname, oth_port)) + logging.info("The error was: %s" % e) + else: + raise e + + self.suffix = ipaldap.IPAdmin.normalizeDN(dsinstance.realm_to_suffix(realm_name)) + + if not iswinsync: + local_id = self._get_replica_id(self.conn, other_conn) + else: + # there is no other side to get a replica ID from + local_id = self._get_replica_id(self.conn, self.conn) + self.basic_replication_setup(self.conn, local_id) + + if not iswinsync: + other_id = self._get_replica_id(other_conn, other_conn) + self.basic_replication_setup(other_conn, other_id) + self.setup_agreement(other_conn, self.conn) + self.setup_agreement(self.conn, other_conn) + return self.start_replication(other_conn) + else: + self.add_passsync_user(self.conn, kargs.get("passsync")) + self.setup_agreement(self.conn, other_conn, **kargs) + logging.info("Added new sync agreement, waiting for it to become ready . . .") + cn, dn = self.agreement_dn(other_hostname) + self.wait_for_repl_update(self.conn, dn, 30) + logging.info("Agreement is ready, starting replication . . .") + return self.start_replication(self.conn, other_conn) + + def initialize_replication(self, dn, conn): + mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')] + try: + conn.modify_s(dn, mod) + except ldap.ALREADY_EXISTS: + return + + def force_synch(self, dn, schedule, conn): + newschedule = '2358-2359 0' + + # On the remote chance of a match. We force a synch to happen right + # now by changing the schedule to something else and quickly changing + # it back. + if newschedule == schedule: + newschedule = '2358-2359 1' + logging.info("Changing agreement %s schedule to %s to force synch" % + (dn, newschedule)) + mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaUpdateSchedule', [ newschedule ])] + conn.modify_s(dn, mod) + time.sleep(1) + logging.info("Changing agreement %s to restore original schedule %s" % + (dn, schedule)) + mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaUpdateSchedule', [ schedule ])] + conn.modify_s(dn, mod) + + def get_agreement_type(self, hostname): + cn, dn = self.agreement_dn(hostname) + + entry = self.conn.getEntry(dn, ldap.SCOPE_BASE) + + objectclass = entry.getValues("objectclass") + + for o in objectclass: + if o.lower() == "nsdswindowsreplicationagreement": + return WINSYNC + + return IPA_REPLICA diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py new file mode 100644 index 00000000..b9f6c505 --- /dev/null +++ b/ipaserver/install/service.py @@ -0,0 +1,169 @@ +# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com> +# +# Copyright (C) 2007 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 +# + +import logging, sys +from ipa import sysrestore +from ipa import ipautil + + +def stop(service_name): + ipautil.run(["/sbin/service", service_name, "stop"]) + +def start(service_name): + ipautil.run(["/sbin/service", service_name, "start"]) + +def restart(service_name): + ipautil.run(["/sbin/service", service_name, "restart"]) + +def is_running(service_name): + ret = True + try: + ipautil.run(["/sbin/service", service_name, "status"]) + except ipautil.CalledProcessError: + ret = False + return ret + +def chkconfig_on(service_name): + ipautil.run(["/sbin/chkconfig", service_name, "on"]) + +def chkconfig_off(service_name): + ipautil.run(["/sbin/chkconfig", service_name, "off"]) + +def chkconfig_add(service_name): + ipautil.run(["/sbin/chkconfig", "--add", service_name]) + +def chkconfig_del(service_name): + ipautil.run(["/sbin/chkconfig", "--del", service_name]) + +def is_enabled(service_name): + (stdout, stderr) = ipautil.run(["/sbin/chkconfig", "--list", service_name]) + + runlevels = {} + for runlevel in range(0, 7): + runlevels[runlevel] = False + + for line in stdout.split("\n"): + parts = line.split() + if parts[0] == service_name: + for s in parts[1:]: + (runlevel, status) = s.split(":")[0:2] + try: + runlevels[int(runlevel)] = status == "on" + except ValueError: + pass + break + + return (runlevels[3] and runlevels[4] and runlevels[5]) + +def print_msg(message, output_fd=sys.stdout): + logging.debug(message) + output_fd.write(message) + output_fd.write("\n") + + +class Service: + def __init__(self, service_name, sstore=None): + self.service_name = service_name + self.steps = [] + self.output_fd = sys.stdout + + if sstore: + self.sstore = sstore + else: + self.sstore = sysrestore.StateFile('/var/lib/ipa/sysrestore') + + def set_output(self, fd): + self.output_fd = fd + + def stop(self): + stop(self.service_name) + + def start(self): + start(self.service_name) + + def restart(self): + restart(self.service_name) + + def is_running(self): + return is_running(self.service_name) + + def chkconfig_add(self): + chkconfig_add(self.service_name) + + def chkconfig_del(self): + chkconfig_del(self.service_name) + + def chkconfig_on(self): + chkconfig_on(self.service_name) + + def chkconfig_off(self): + chkconfig_off(self.service_name) + + def is_enabled(self): + return is_enabled(self.service_name) + + def backup_state(self, key, value): + self.sstore.backup_state(self.service_name, key, value) + + def restore_state(self, key): + return self.sstore.restore_state(self.service_name, key) + + def print_msg(self, message): + print_msg(message, self.output_fd) + + def step(self, message, method): + self.steps.append((message, method)) + + def start_creation(self, message): + self.print_msg(message) + + step = 0 + for (message, method) in self.steps: + self.print_msg(" [%d/%d]: %s" % (step+1, len(self.steps), message)) + method() + step += 1 + + self.print_msg("done configuring %s." % self.service_name) + + self.steps = [] + +class SimpleServiceInstance(Service): + def create_instance(self): + self.step("starting %s " % self.service_name, self.__start) + self.step("configuring %s to start on boot" % self.service_name, self.__enable) + self.start_creation("Configuring %s" % self.service_name) + + def __start(self): + self.backup_state("running", self.is_running()) + self.restart() + + def __enable(self): + self.chkconfig_add() + self.backup_state("enabled", self.is_enabled()) + self.chkconfig_on() + + def uninstall(self): + running = self.restore_state("running") + enabled = not self.restore_state("enabled") + + if not running is None and not running: + self.stop() + if not enabled is None and not enabled: + self.chkconfig_off() + self.chkconfig_del() diff --git a/selinux/Makefile b/selinux/Makefile new file mode 100644 index 00000000..a662d2fd --- /dev/null +++ b/selinux/Makefile @@ -0,0 +1,28 @@ +SUBDIRS = ipa_webgui ipa_kpasswd +POLICY_MAKEFILE = /usr/share/selinux/devel/Makefile +POLICY_DIR = $(DESTDIR)/usr/share/selinux/targeted + +all: + if [ ! -e $(POLICY_MAKEFILE) ]; then echo "You need to install the SELinux development tools (selinux-policy-devel)" && exit 1; fi + + @for subdir in $(SUBDIRS); do \ + (cd $$subdir && $(MAKE) -f $(POLICY_MAKEFILE) $@) || exit 1; \ + done + +clean: + @for subdir in $(SUBDIRS); do \ + (cd $$subdir && $(MAKE) -f $(POLICY_MAKEFILE) $@) || exit 1; \ + done + +distclean: clean + rm -f ipa-server-selinux.spec + +maintainer-clean: distclean + +install: all + install -d $(POLICY_DIR) + install -m 644 ipa_webgui/ipa_webgui.pp $(POLICY_DIR) + install -m 644 ipa_kpasswd/ipa_kpasswd.pp $(POLICY_DIR) + +load: + /usr/sbin/semodule -i ipa_webgui/ipa_webgui.pp ipa_kpasswd/ipa_kpasswd.pp diff --git a/selinux/ipa-server-selinux.spec.in b/selinux/ipa-server-selinux.spec.in new file mode 100644 index 00000000..3387553a --- /dev/null +++ b/selinux/ipa-server-selinux.spec.in @@ -0,0 +1,86 @@ +%define POLICYCOREUTILSVER 1.33.12-1 + +Name: ipa-server-selinux +Version: __VERSION__ +Release: __RELEASE__%{?dist} +Summary: IPA server SELinux policies + +Group: System Environment/Base +License: GPLv2 +URL: http://www.freeipa.org +Source0: ipa-server-%{version}.tgz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +BuildArch: noarch + +BuildRequires: selinux-policy-devel m4 make policycoreutils >= %{POLICYCOREUTILSVER} +Requires(pre): policycoreutils >= %{POLICYCOREUTILSVER} libsemanage + +%description +SELinux policy for ipa-server + +%prep +%setup -n ipa-server-%{version} -q + +%build +cd selinux +make + +%clean +%{__rm} -fR %{buildroot} + +%install +%{__rm} -fR %{buildroot} +cd selinux +install -d %{buildroot}/%{_usr}/share/selinux/targeted/ +make DESTDIR=%{buildroot} install + +%files +%{_usr}/share/selinux/targeted/ipa_webgui.pp +%{_usr}/share/selinux/targeted/ipa_kpasswd.pp + + +%define saveFileContext() \ +if [ -s /etc/selinux/config ]; then \ + . %{_sysconfdir}/selinux/config; \ + FILE_CONTEXT=%{_sysconfdir}/selinux/%1/contexts/files/file_contexts; \ + if [ "${SELINUXTYPE}" == %1 -a -f ${FILE_CONTEXT} ]; then \ + cp -f ${FILE_CONTEXT} ${FILE_CONTEXT}.%{name}; \ + fi \ +fi; + +%define relabel() \ +. %{_sysconfdir}/selinux/config; \ +FILE_CONTEXT=%{_sysconfdir}/selinux/%1/contexts/files/file_contexts; \ +selinuxenabled; \ +if [ $? == 0 -a "${SELINUXTYPE}" == %1 -a -f ${FILE_CONTEXT}.%{name} ]; then \ + fixfiles -C ${FILE_CONTEXT}.%{name} restore; \ + rm -f ${FILE_CONTEXT}.%name; \ +fi; + +%pre +%saveFileContext targeted + +%post +semodule -s targeted -i /usr/share/selinux/targeted/ipa_webgui.pp /usr/share/selinux/targeted/ipa_kpasswd.pp +%relabel targeted + +%preun +if [ $1 = 0 ]; then +%saveFileContext targeted +fi + +%postun +if [ $1 = 0 ]; then +semodule -s targeted -r ipa_webgui ipa_kpasswd +%relabel targeted +fi + +%changelog +* Thu Apr 3 2008 Rob Crittenden <rcritten@redhat.com> - 1.0.0-1 +- Version bump for release + +* Thu Feb 21 2008 Rob Crittenden <rcritten@redhat.com> - 0.99.0-1 +- Version bump for release + +* Thu Jan 17 2008 Karl MacMillan <kmacmill@redhat.com> - 0.6.0-1 +- Initial version diff --git a/selinux/ipa_kpasswd/ipa_kpasswd.fc b/selinux/ipa_kpasswd/ipa_kpasswd.fc new file mode 100644 index 00000000..2dcf827d --- /dev/null +++ b/selinux/ipa_kpasswd/ipa_kpasswd.fc @@ -0,0 +1,9 @@ +# +# /usr +# +/usr/sbin/ipa_kpasswd -- gen_context(system_u:object_r:ipa_kpasswd_exec_t,s0) + +# +# /var +# +/var/cache/ipa/kpasswd(/.*)? gen_context(system_u:object_r:ipa_kpasswd_ccache_t,s0) diff --git a/selinux/ipa_kpasswd/ipa_kpasswd.te b/selinux/ipa_kpasswd/ipa_kpasswd.te new file mode 100644 index 00000000..b5203a4e --- /dev/null +++ b/selinux/ipa_kpasswd/ipa_kpasswd.te @@ -0,0 +1,71 @@ +policy_module(ipa_kpasswd, 1.0) + +######################################## +# +# Declarations +# + +type ipa_kpasswd_t; +type ipa_kpasswd_exec_t; +type ipa_kpasswd_var_run_t; +type ipa_kpasswd_ccache_t; +init_daemon_domain(ipa_kpasswd_t, ipa_kpasswd_exec_t) + +######################################## +# +# IPA kpasswd local policy +# + +allow ipa_kpasswd_t self:capability { sys_nice dac_override }; +allow ipa_kpasswd_t self:tcp_socket create_stream_socket_perms; +allow ipa_kpasswd_t self:udp_socket create_socket_perms; + +files_read_etc_files(ipa_kpasswd_t) +files_search_usr(ipa_kpasswd_t) + +files_pid_file(ipa_kpasswd_var_run_t); +allow ipa_kpasswd_t ipa_kpasswd_var_run_t:file manage_file_perms; +files_pid_filetrans(ipa_kpasswd_t,ipa_kpasswd_var_run_t,file) + +auth_use_nsswitch(ipa_kpasswd_t) + +libs_use_ld_so(ipa_kpasswd_t) +libs_use_shared_libs(ipa_kpasswd_t) + +logging_send_syslog_msg(ipa_kpasswd_t) + +miscfiles_read_localization(ipa_kpasswd_t) + +kerberos_use(ipa_kpasswd_t) +kerberos_manage_host_rcache(ipa_kpasswd_t) +kerberos_read_kdc_config(ipa_kpasswd_t) + +kernel_read_system_state(ipa_kpasswd_t) + +# /var/cache/ipa/kpasswd +files_type(ipa_kpasswd_ccache_t) +manage_dirs_pattern(ipa_kpasswd_t, ipa_kpasswd_ccache_t, ipa_kpasswd_ccache_t) +manage_files_pattern(ipa_kpasswd_t, ipa_kpasswd_ccache_t, ipa_kpasswd_ccache_t) +files_var_filetrans(ipa_kpasswd_t, ipa_kpasswd_ccache_t,dir) + +kernel_read_network_state(ipa_kpasswd_t) +kernel_read_network_state_symlinks(ipa_kpasswd_t) + +corenet_tcp_sendrecv_all_if(ipa_kpasswd_t) +corenet_udp_sendrecv_all_if(ipa_kpasswd_t) +corenet_raw_sendrecv_all_if(ipa_kpasswd_t) +corenet_tcp_sendrecv_all_nodes(ipa_kpasswd_t) +corenet_udp_sendrecv_all_nodes(ipa_kpasswd_t) +corenet_raw_sendrecv_all_nodes(ipa_kpasswd_t) +corenet_tcp_sendrecv_all_ports(ipa_kpasswd_t) +corenet_udp_sendrecv_all_ports(ipa_kpasswd_t) +corenet_non_ipsec_sendrecv(ipa_kpasswd_t) +corenet_tcp_bind_all_nodes(ipa_kpasswd_t) +corenet_udp_bind_all_nodes(ipa_kpasswd_t) +corenet_tcp_bind_kerberos_admin_port(ipa_kpasswd_t) +corenet_udp_bind_kerberos_admin_port(ipa_kpasswd_t) +require { + type krb5kdc_conf_t; +}; + +allow ipa_kpasswd_t krb5kdc_conf_t:dir search_dir_perms; diff --git a/selinux/ipa_webgui/ipa_webgui.fc b/selinux/ipa_webgui/ipa_webgui.fc new file mode 100644 index 00000000..c9dfb2b5 --- /dev/null +++ b/selinux/ipa_webgui/ipa_webgui.fc @@ -0,0 +1,11 @@ +# +# /usr +# +/usr/sbin/ipa_webgui -- gen_context(system_u:object_r:ipa_webgui_exec_t,s0) + + +# +# /var +# +/var/log/ipa_error\.log -- gen_context(system_u:object_r:ipa_webgui_log_t,s0) +/var/cache/ipa/sessions(/.*)? gen_context(system_u:object_r:ipa_cache_t,s0) diff --git a/selinux/ipa_webgui/ipa_webgui.te b/selinux/ipa_webgui/ipa_webgui.te new file mode 100644 index 00000000..a9818d82 --- /dev/null +++ b/selinux/ipa_webgui/ipa_webgui.te @@ -0,0 +1,97 @@ +policy_module(ipa_webgui, 1.0) + +######################################## +# +# Declarations +# + +require { + type sbin_t; +} +type ipa_webgui_t; +type ipa_webgui_exec_t; +type ipa_webgui_var_run_t; +type ipa_cache_t; +files_type(ipa_cache_t) +init_daemon_domain(ipa_webgui_t, ipa_webgui_exec_t) + +type ipa_webgui_log_t; +logging_log_file(ipa_webgui_log_t) + +######################################## +# +# IPA webgui local policy +# + +allow ipa_webgui_t self:tcp_socket create_stream_socket_perms; +allow ipa_webgui_t self:udp_socket create_socket_perms; +allow ipa_webgui_t self:process setfscreate; + +# This is how the kerberos credential cache is passed to +# the ipa_webgui process. Unfortunately, the kerberos +# libraries seem to insist that it be open rw. To top it +# all off there is no interface for this either. +require { + type httpd_tmp_t; +} +allow ipa_webgui_t httpd_tmp_t:file read_file_perms; +dontaudit ipa_webgui_t httpd_tmp_t:file write; + +apache_search_sys_content(ipa_webgui_t) +apache_read_config(ipa_webgui_t) + +corecmd_list_bin(ipa_webgui_t) + +miscfiles_read_localization(ipa_webgui_t) + +files_list_usr(ipa_webgui_t) +files_read_etc_files(ipa_webgui_t) +files_read_usr_files(ipa_webgui_t) +files_read_usr_symlinks(ipa_webgui_t) +files_search_etc(ipa_webgui_t) +files_search_tmp(ipa_webgui_t) + +files_pid_file(ipa_webgui_var_run_t) +allow ipa_webgui_t ipa_webgui_var_run_t:file manage_file_perms; +files_pid_filetrans(ipa_webgui_t,ipa_webgui_var_run_t,file) + +kerberos_read_config(ipa_webgui_t) + +kernel_read_system_state(ipa_webgui_t) + +auth_use_nsswitch(ipa_webgui_t) + +libs_use_ld_so(ipa_webgui_t) +libs_use_shared_libs(ipa_webgui_t) + +logging_search_logs(ipa_webgui_t) +logging_log_filetrans(ipa_webgui_t,ipa_webgui_log_t,file) +allow ipa_webgui_t ipa_webgui_log_t:file rw_file_perms; + +allow ipa_webgui_t self:capability { setgid setuid }; + +# /var/cache/ipa/sessions +files_type(ipa_cache_t) +manage_dirs_pattern(ipa_webgui_t, ipa_cache_t, ipa_cache_t) +manage_files_pattern(ipa_webgui_t, ipa_cache_t, ipa_cache_t) +files_var_filetrans(ipa_webgui_t, ipa_cache_t,dir) + +userdom_dontaudit_search_sysadm_home_dirs(ipa_webgui_t) + +corenet_tcp_sendrecv_all_if(ipa_webgui_t) +corenet_udp_sendrecv_all_if(ipa_webgui_t) +corenet_raw_sendrecv_all_if(ipa_webgui_t) +corenet_tcp_sendrecv_all_nodes(ipa_webgui_t) +corenet_udp_sendrecv_all_nodes(ipa_webgui_t) +corenet_raw_sendrecv_all_nodes(ipa_webgui_t) +corenet_tcp_sendrecv_all_ports(ipa_webgui_t) +corenet_udp_sendrecv_all_ports(ipa_webgui_t) +corenet_non_ipsec_sendrecv(ipa_webgui_t) +corenet_tcp_bind_all_nodes(ipa_webgui_t) +corenet_udp_bind_all_nodes(ipa_webgui_t) +corenet_tcp_bind_http_cache_port(ipa_webgui_t) +corenet_tcp_connect_http_cache_port(ipa_webgui_t) +corenet_tcp_connect_ldap_port(ipa_webgui_t) + +corecmd_search_sbin(ipa_webgui_t) +allow ipa_webgui_t sbin_t:dir read; diff --git a/version.m4.in b/version.m4.in new file mode 100644 index 00000000..5ddc8cea --- /dev/null +++ b/version.m4.in @@ -0,0 +1 @@ +define([IPA_VERSION], [__VERSION__]) -- cgit