From 1c48b5a62f73234ed26bb20f0ab345ab61cda0ab Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Thu, 18 Feb 2010 07:49:04 -0500 Subject: Rename server/ directory to src/ Also update BUILD.txt --- src/Makefile.am | 941 ++++ src/build_macros.m4 | 21 + src/conf_macros.m4 | 207 + src/confdb/confdb.c | 908 ++++ src/confdb/confdb.h | 364 ++ src/confdb/confdb_private.h | 35 + src/confdb/confdb_setup.c | 423 ++ src/confdb/confdb_setup.h | 52 + src/config/SSSDConfig.py | 1664 +++++++ src/config/SSSDConfigTest.py | 1521 +++++++ src/config/etc/sssd.api.conf | 66 + src/config/etc/sssd.api.d/sssd-ipa.conf | 77 + src/config/etc/sssd.api.d/sssd-krb5.conf | 13 + src/config/etc/sssd.api.d/sssd-ldap.conf | 68 + src/config/etc/sssd.api.d/sssd-local.conf | 10 + src/config/etc/sssd.api.d/sssd-proxy.conf | 7 + src/config/ipachangeconf.py | 588 +++ src/config/setup.py | 35 + src/config/testconfigs/noparse.api.conf | 7 + src/config/testconfigs/sssd-badversion.conf | 42 + src/config/testconfigs/sssd-invalid-badbool.conf | 43 + src/config/testconfigs/sssd-invalid.conf | 3 + src/config/testconfigs/sssd-noversion.conf | 41 + src/config/testconfigs/sssd-valid.conf | 43 + src/config/upgrade_config.py | 405 ++ src/configure.ac | 145 + src/db/sysdb.c | 1883 ++++++++ src/db/sysdb.h | 650 +++ src/db/sysdb_ops.c | 5059 ++++++++++++++++++++++ src/db/sysdb_private.h | 107 + src/db/sysdb_search.c | 691 +++ src/doxy.config.in | 1538 +++++++ src/examples/sssd.conf | 81 + src/examples/sssdproxytest | 5 + src/examples/sudo | 6 + src/external/crypto.m4 | 13 + src/external/docbook.m4 | 35 + src/external/krb5.m4 | 62 + src/external/ldap.m4 | 63 + src/external/libcares.m4 | 20 + src/external/libcollection.m4 | 12 + src/external/libdhash.m4 | 12 + src/external/libini_config.m4 | 12 + src/external/libldb.m4 | 28 + src/external/libpcre.m4 | 15 + src/external/libpopt.m4 | 9 + src/external/libtalloc.m4 | 7 + src/external/libtdb.m4 | 8 + src/external/libtevent.m4 | 7 + src/external/pam.m4 | 6 + src/external/pkg.m4 | 156 + src/external/platform.m4 | 29 + src/external/python.m4 | 58 + src/external/selinux.m4 | 13 + src/external/sizes.m4 | 44 + src/krb5_plugin/sssd_krb5_locator_plugin.c | 289 ++ src/ldb_modules/memberof.c | 3612 +++++++++++++++ src/m4/.dir | 0 src/man/include/failover.xml | 42 + src/man/include/param_help.xml | 10 + src/man/include/upstream.xml | 4 + src/man/sss_groupadd.8.xml | 81 + src/man/sss_groupdel.8.xml | 69 + src/man/sss_groupmod.8.xml | 95 + src/man/sss_groupshow.8.xml | 76 + src/man/sss_useradd.8.xml | 191 + src/man/sss_userdel.8.xml | 105 + src/man/sss_usermod.8.xml | 150 + src/man/sssd-ipa.5.xml | 159 + src/man/sssd-krb5.5.xml | 250 ++ src/man/sssd-ldap.5.xml | 688 +++ src/man/sssd.8.xml | 148 + src/man/sssd.conf.5.xml | 808 ++++ src/man/sssd_krb5_locator_plugin.8.xml | 89 + src/monitor/monitor.c | 2631 +++++++++++ src/monitor/monitor.h | 36 + src/monitor/monitor_interfaces.h | 55 + src/monitor/monitor_sbus.c | 195 + src/po/LINGUAS | 10 + src/po/Makevars | 41 + src/po/POTFILES.in | 16 + src/po/de.po | 692 +++ src/po/es.po | 720 +++ src/po/fr.po | 692 +++ src/po/it.po | 702 +++ src/po/ja.po | 692 +++ src/po/nl.po | 693 +++ src/po/pl.po | 718 +++ src/po/pt.po | 712 +++ src/po/sss_daemon.pot | 691 +++ src/po/sv.po | 711 +++ src/providers/child_common.c | 416 ++ src/providers/child_common.h | 73 + src/providers/data_provider.h | 219 + src/providers/data_provider_be.c | 1235 ++++++ src/providers/data_provider_fo.c | 356 ++ src/providers/data_provider_opts.c | 384 ++ src/providers/dp_auth_util.c | 414 ++ src/providers/dp_backend.h | 142 + src/providers/dp_sbus.c | 45 + src/providers/fail_over.c | 651 +++ src/providers/fail_over.h | 108 + src/providers/ipa/ipa_access.c | 1823 ++++++++ src/providers/ipa/ipa_access.h | 66 + src/providers/ipa/ipa_auth.c | 313 ++ src/providers/ipa/ipa_auth.h | 32 + src/providers/ipa/ipa_common.c | 597 +++ src/providers/ipa/ipa_common.h | 83 + src/providers/ipa/ipa_init.c | 293 ++ src/providers/ipa/ipa_timerules.c | 1186 +++++ src/providers/ipa/ipa_timerules.h | 56 + src/providers/krb5/krb5_auth.c | 1193 +++++ src/providers/krb5/krb5_auth.h | 91 + src/providers/krb5/krb5_become_user.c | 61 + src/providers/krb5/krb5_child.c | 1030 +++++ src/providers/krb5/krb5_common.c | 356 ++ src/providers/krb5/krb5_common.h | 72 + src/providers/krb5/krb5_init.c | 152 + src/providers/krb5/krb5_utils.c | 145 + src/providers/krb5/krb5_utils.h | 39 + src/providers/ldap/ldap_auth.c | 1055 +++++ src/providers/ldap/ldap_child.c | 429 ++ src/providers/ldap/ldap_common.c | 589 +++ src/providers/ldap/ldap_common.h | 115 + src/providers/ldap/ldap_id.c | 795 ++++ src/providers/ldap/ldap_id_cleanup.c | 555 +++ src/providers/ldap/ldap_id_enum.c | 608 +++ src/providers/ldap/ldap_init.c | 179 + src/providers/ldap/sdap.c | 388 ++ src/providers/ldap/sdap.h | 258 ++ src/providers/ldap/sdap_async.c | 1018 +++++ src/providers/ldap/sdap_async.h | 126 + src/providers/ldap/sdap_async_accounts.c | 2065 +++++++++ src/providers/ldap/sdap_async_connection.c | 1141 +++++ src/providers/ldap/sdap_async_private.h | 68 + src/providers/ldap/sdap_child_helpers.c | 462 ++ src/providers/providers.h | 24 + src/providers/proxy.c | 2521 +++++++++++ src/providers/sssd_be.exports | 4 + src/python/pysss.c | 937 ++++ src/resolv/ares/ares_data.c | 140 + src/resolv/ares/ares_data.h | 68 + src/resolv/ares/ares_dns.h | 91 + src/resolv/ares/ares_parse_srv_reply.c | 183 + src/resolv/ares/ares_parse_srv_reply.h | 35 + src/resolv/ares/ares_parse_txt_reply.c | 204 + src/resolv/ares/ares_parse_txt_reply.h | 33 + src/resolv/async_resolv.c | 1062 +++++ src/resolv/async_resolv.h | 95 + src/responder/common/responder.h | 152 + src/responder/common/responder_cmd.c | 103 + src/responder/common/responder_common.c | 589 +++ src/responder/common/responder_dp.c | 590 +++ src/responder/common/responder_packet.c | 253 ++ src/responder/common/responder_packet.h | 43 + src/responder/nss/nsssrv.c | 367 ++ src/responder/nss/nsssrv.h | 70 + src/responder/nss/nsssrv_cmd.c | 3182 ++++++++++++++ src/responder/nss/nsssrv_nc.c | 321 ++ src/responder/nss/nsssrv_nc.h | 51 + src/responder/pam/pam_LOCAL_domain.c | 476 ++ src/responder/pam/pamsrv.c | 224 + src/responder/pam/pamsrv.h | 57 + src/responder/pam/pamsrv_cmd.c | 1181 +++++ src/responder/pam/pamsrv_dp.c | 142 + src/sbus/sbus_client.c | 57 + src/sbus/sbus_client.h | 36 + src/sbus/sssd_dbus.h | 153 + src/sbus/sssd_dbus_common.c | 444 ++ src/sbus/sssd_dbus_connection.c | 692 +++ src/sbus/sssd_dbus_private.h | 98 + src/sbus/sssd_dbus_server.c | 171 + src/sss_client/common.c | 669 +++ src/sss_client/group.c | 435 ++ src/sss_client/man/pam_sss.8.xml | 97 + src/sss_client/pam_sss.c | 1166 +++++ src/sss_client/pam_test_client.c | 95 + src/sss_client/passwd.c | 373 ++ src/sss_client/protos.h | 137 + src/sss_client/sss_cli.h | 220 + src/sss_client/sss_nss.exports | 73 + src/sss_client/sss_pam.exports | 4 + src/sss_client/sss_pam_macros.h | 30 + src/sysv/SUSE/sssd | 78 + src/sysv/sssd | 121 + src/tests/auth-tests.c | 343 ++ src/tests/check_and_open-tests.c | 218 + src/tests/common.c | 109 + src/tests/common.h | 21 + src/tests/fail_over-tests.c | 304 ++ src/tests/files-tests.c | 323 ++ src/tests/find_uid-tests.c | 125 + src/tests/ipa_ldap_opt-tests.c | 59 + src/tests/ipa_timerules-tests.c | 580 +++ src/tests/krb5_utils-tests.c | 307 ++ src/tests/python-test.py | 445 ++ src/tests/refcount-tests.c | 232 + src/tests/resolv-tests.c | 598 +++ src/tests/stress-tests.c | 328 ++ src/tests/strtonum-tests.c | 455 ++ src/tests/sysdb-tests.c | 3330 ++++++++++++++ src/tools/files.c | 736 ++++ src/tools/sss_groupadd.c | 155 + src/tools/sss_groupdel.c | 155 + src/tools/sss_groupmod.c | 246 ++ src/tools/sss_groupshow.c | 944 ++++ src/tools/sss_sync_ops.c | 1838 ++++++++ src/tools/sss_sync_ops.h | 125 + src/tools/sss_useradd.c | 349 ++ src/tools/sss_userdel.c | 205 + src/tools/sss_usermod.c | 265 ++ src/tools/tools_util.c | 520 +++ src/tools/tools_util.h | 108 + src/util/backup_file.c | 122 + src/util/check_and_open.c | 89 + src/util/crypto_sha512crypt.c | 382 ++ src/util/debug.c | 154 + src/util/dlinklist.h | 116 + src/util/find_uid.c | 296 ++ src/util/find_uid.h | 36 + src/util/memory.c | 67 + src/util/nss_sha512crypt.c | 419 ++ src/util/refcount.c | 88 + src/util/refcount.h | 39 + src/util/server.c | 433 ++ src/util/sha512crypt.h | 4 + src/util/signal.c | 146 + src/util/signal.m4 | 1 + src/util/sss_krb5.c | 196 + src/util/sss_krb5.h | 50 + src/util/sss_ldap.c | 70 + src/util/sss_ldap.h | 30 + src/util/strtonum.c | 65 + src/util/strtonum.h | 32 + src/util/user_info_msg.c | 51 + src/util/user_info_msg.h | 33 + src/util/usertools.c | 175 + src/util/util.c | 138 + src/util/util.h | 256 ++ 239 files changed, 91971 insertions(+) create mode 100644 src/Makefile.am create mode 100644 src/build_macros.m4 create mode 100644 src/conf_macros.m4 create mode 100644 src/confdb/confdb.c create mode 100644 src/confdb/confdb.h create mode 100644 src/confdb/confdb_private.h create mode 100644 src/confdb/confdb_setup.c create mode 100644 src/confdb/confdb_setup.h create mode 100644 src/config/SSSDConfig.py create mode 100755 src/config/SSSDConfigTest.py create mode 100644 src/config/etc/sssd.api.conf create mode 100644 src/config/etc/sssd.api.d/sssd-ipa.conf create mode 100644 src/config/etc/sssd.api.d/sssd-krb5.conf create mode 100644 src/config/etc/sssd.api.d/sssd-ldap.conf create mode 100644 src/config/etc/sssd.api.d/sssd-local.conf create mode 100644 src/config/etc/sssd.api.d/sssd-proxy.conf create mode 100644 src/config/ipachangeconf.py create mode 100644 src/config/setup.py create mode 100644 src/config/testconfigs/noparse.api.conf create mode 100644 src/config/testconfigs/sssd-badversion.conf create mode 100644 src/config/testconfigs/sssd-invalid-badbool.conf create mode 100644 src/config/testconfigs/sssd-invalid.conf create mode 100644 src/config/testconfigs/sssd-noversion.conf create mode 100644 src/config/testconfigs/sssd-valid.conf create mode 100644 src/config/upgrade_config.py create mode 100644 src/configure.ac create mode 100644 src/db/sysdb.c create mode 100644 src/db/sysdb.h create mode 100644 src/db/sysdb_ops.c create mode 100644 src/db/sysdb_private.h create mode 100644 src/db/sysdb_search.c create mode 100644 src/doxy.config.in create mode 100644 src/examples/sssd.conf create mode 100644 src/examples/sssdproxytest create mode 100644 src/examples/sudo create mode 100644 src/external/crypto.m4 create mode 100644 src/external/docbook.m4 create mode 100644 src/external/krb5.m4 create mode 100644 src/external/ldap.m4 create mode 100644 src/external/libcares.m4 create mode 100644 src/external/libcollection.m4 create mode 100644 src/external/libdhash.m4 create mode 100644 src/external/libini_config.m4 create mode 100644 src/external/libldb.m4 create mode 100644 src/external/libpcre.m4 create mode 100644 src/external/libpopt.m4 create mode 100644 src/external/libtalloc.m4 create mode 100644 src/external/libtdb.m4 create mode 100644 src/external/libtevent.m4 create mode 100644 src/external/pam.m4 create mode 100644 src/external/pkg.m4 create mode 100644 src/external/platform.m4 create mode 100644 src/external/python.m4 create mode 100644 src/external/selinux.m4 create mode 100644 src/external/sizes.m4 create mode 100644 src/krb5_plugin/sssd_krb5_locator_plugin.c create mode 100644 src/ldb_modules/memberof.c create mode 100644 src/m4/.dir create mode 100644 src/man/include/failover.xml create mode 100644 src/man/include/param_help.xml create mode 100644 src/man/include/upstream.xml create mode 100644 src/man/sss_groupadd.8.xml create mode 100644 src/man/sss_groupdel.8.xml create mode 100644 src/man/sss_groupmod.8.xml create mode 100644 src/man/sss_groupshow.8.xml create mode 100644 src/man/sss_useradd.8.xml create mode 100644 src/man/sss_userdel.8.xml create mode 100644 src/man/sss_usermod.8.xml create mode 100644 src/man/sssd-ipa.5.xml create mode 100644 src/man/sssd-krb5.5.xml create mode 100644 src/man/sssd-ldap.5.xml create mode 100644 src/man/sssd.8.xml create mode 100644 src/man/sssd.conf.5.xml create mode 100644 src/man/sssd_krb5_locator_plugin.8.xml create mode 100644 src/monitor/monitor.c create mode 100644 src/monitor/monitor.h create mode 100644 src/monitor/monitor_interfaces.h create mode 100644 src/monitor/monitor_sbus.c create mode 100644 src/po/LINGUAS create mode 100644 src/po/Makevars create mode 100644 src/po/POTFILES.in create mode 100644 src/po/de.po create mode 100644 src/po/es.po create mode 100644 src/po/fr.po create mode 100644 src/po/it.po create mode 100644 src/po/ja.po create mode 100644 src/po/nl.po create mode 100644 src/po/pl.po create mode 100644 src/po/pt.po create mode 100644 src/po/sss_daemon.pot create mode 100644 src/po/sv.po create mode 100644 src/providers/child_common.c create mode 100644 src/providers/child_common.h create mode 100644 src/providers/data_provider.h create mode 100644 src/providers/data_provider_be.c create mode 100644 src/providers/data_provider_fo.c create mode 100644 src/providers/data_provider_opts.c create mode 100644 src/providers/dp_auth_util.c create mode 100644 src/providers/dp_backend.h create mode 100644 src/providers/dp_sbus.c create mode 100644 src/providers/fail_over.c create mode 100644 src/providers/fail_over.h create mode 100644 src/providers/ipa/ipa_access.c create mode 100644 src/providers/ipa/ipa_access.h create mode 100644 src/providers/ipa/ipa_auth.c create mode 100644 src/providers/ipa/ipa_auth.h create mode 100644 src/providers/ipa/ipa_common.c create mode 100644 src/providers/ipa/ipa_common.h create mode 100644 src/providers/ipa/ipa_init.c create mode 100644 src/providers/ipa/ipa_timerules.c create mode 100644 src/providers/ipa/ipa_timerules.h create mode 100644 src/providers/krb5/krb5_auth.c create mode 100644 src/providers/krb5/krb5_auth.h create mode 100644 src/providers/krb5/krb5_become_user.c create mode 100644 src/providers/krb5/krb5_child.c create mode 100644 src/providers/krb5/krb5_common.c create mode 100644 src/providers/krb5/krb5_common.h create mode 100644 src/providers/krb5/krb5_init.c create mode 100644 src/providers/krb5/krb5_utils.c create mode 100644 src/providers/krb5/krb5_utils.h create mode 100644 src/providers/ldap/ldap_auth.c create mode 100644 src/providers/ldap/ldap_child.c create mode 100644 src/providers/ldap/ldap_common.c create mode 100644 src/providers/ldap/ldap_common.h create mode 100644 src/providers/ldap/ldap_id.c create mode 100644 src/providers/ldap/ldap_id_cleanup.c create mode 100644 src/providers/ldap/ldap_id_enum.c create mode 100644 src/providers/ldap/ldap_init.c create mode 100644 src/providers/ldap/sdap.c create mode 100644 src/providers/ldap/sdap.h create mode 100644 src/providers/ldap/sdap_async.c create mode 100644 src/providers/ldap/sdap_async.h create mode 100644 src/providers/ldap/sdap_async_accounts.c create mode 100644 src/providers/ldap/sdap_async_connection.c create mode 100644 src/providers/ldap/sdap_async_private.h create mode 100644 src/providers/ldap/sdap_child_helpers.c create mode 100644 src/providers/providers.h create mode 100644 src/providers/proxy.c create mode 100644 src/providers/sssd_be.exports create mode 100644 src/python/pysss.c create mode 100644 src/resolv/ares/ares_data.c create mode 100644 src/resolv/ares/ares_data.h create mode 100644 src/resolv/ares/ares_dns.h create mode 100644 src/resolv/ares/ares_parse_srv_reply.c create mode 100644 src/resolv/ares/ares_parse_srv_reply.h create mode 100644 src/resolv/ares/ares_parse_txt_reply.c create mode 100644 src/resolv/ares/ares_parse_txt_reply.h create mode 100644 src/resolv/async_resolv.c create mode 100644 src/resolv/async_resolv.h create mode 100644 src/responder/common/responder.h create mode 100644 src/responder/common/responder_cmd.c create mode 100644 src/responder/common/responder_common.c create mode 100644 src/responder/common/responder_dp.c create mode 100644 src/responder/common/responder_packet.c create mode 100644 src/responder/common/responder_packet.h create mode 100644 src/responder/nss/nsssrv.c create mode 100644 src/responder/nss/nsssrv.h create mode 100644 src/responder/nss/nsssrv_cmd.c create mode 100644 src/responder/nss/nsssrv_nc.c create mode 100644 src/responder/nss/nsssrv_nc.h create mode 100644 src/responder/pam/pam_LOCAL_domain.c create mode 100644 src/responder/pam/pamsrv.c create mode 100644 src/responder/pam/pamsrv.h create mode 100644 src/responder/pam/pamsrv_cmd.c create mode 100644 src/responder/pam/pamsrv_dp.c create mode 100644 src/sbus/sbus_client.c create mode 100644 src/sbus/sbus_client.h create mode 100644 src/sbus/sssd_dbus.h create mode 100644 src/sbus/sssd_dbus_common.c create mode 100644 src/sbus/sssd_dbus_connection.c create mode 100644 src/sbus/sssd_dbus_private.h create mode 100644 src/sbus/sssd_dbus_server.c create mode 100644 src/sss_client/common.c create mode 100644 src/sss_client/group.c create mode 100644 src/sss_client/man/pam_sss.8.xml create mode 100644 src/sss_client/pam_sss.c create mode 100644 src/sss_client/pam_test_client.c create mode 100644 src/sss_client/passwd.c create mode 100644 src/sss_client/protos.h create mode 100644 src/sss_client/sss_cli.h create mode 100644 src/sss_client/sss_nss.exports create mode 100644 src/sss_client/sss_pam.exports create mode 100644 src/sss_client/sss_pam_macros.h create mode 100644 src/sysv/SUSE/sssd create mode 100644 src/sysv/sssd create mode 100644 src/tests/auth-tests.c create mode 100644 src/tests/check_and_open-tests.c create mode 100644 src/tests/common.c create mode 100644 src/tests/common.h create mode 100644 src/tests/fail_over-tests.c create mode 100644 src/tests/files-tests.c create mode 100644 src/tests/find_uid-tests.c create mode 100644 src/tests/ipa_ldap_opt-tests.c create mode 100644 src/tests/ipa_timerules-tests.c create mode 100644 src/tests/krb5_utils-tests.c create mode 100644 src/tests/python-test.py create mode 100644 src/tests/refcount-tests.c create mode 100644 src/tests/resolv-tests.c create mode 100644 src/tests/stress-tests.c create mode 100644 src/tests/strtonum-tests.c create mode 100644 src/tests/sysdb-tests.c create mode 100644 src/tools/files.c create mode 100644 src/tools/sss_groupadd.c create mode 100644 src/tools/sss_groupdel.c create mode 100644 src/tools/sss_groupmod.c create mode 100644 src/tools/sss_groupshow.c create mode 100644 src/tools/sss_sync_ops.c create mode 100644 src/tools/sss_sync_ops.h create mode 100644 src/tools/sss_useradd.c create mode 100644 src/tools/sss_userdel.c create mode 100644 src/tools/sss_usermod.c create mode 100644 src/tools/tools_util.c create mode 100644 src/tools/tools_util.h create mode 100644 src/util/backup_file.c create mode 100644 src/util/check_and_open.c create mode 100644 src/util/crypto_sha512crypt.c create mode 100644 src/util/debug.c create mode 100644 src/util/dlinklist.h create mode 100644 src/util/find_uid.c create mode 100644 src/util/find_uid.h create mode 100644 src/util/memory.c create mode 100644 src/util/nss_sha512crypt.c create mode 100644 src/util/refcount.c create mode 100644 src/util/refcount.h create mode 100644 src/util/server.c create mode 100644 src/util/sha512crypt.h create mode 100644 src/util/signal.c create mode 100644 src/util/signal.m4 create mode 100644 src/util/sss_krb5.c create mode 100644 src/util/sss_krb5.h create mode 100644 src/util/sss_ldap.c create mode 100644 src/util/sss_ldap.h create mode 100644 src/util/strtonum.c create mode 100644 src/util/strtonum.h create mode 100644 src/util/user_info_msg.c create mode 100644 src/util/user_info_msg.h create mode 100644 src/util/usertools.c create mode 100644 src/util/util.c create mode 100644 src/util/util.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..eeb8cfef --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,941 @@ +SUBDIRS = po +topdir=. + +# Some old versions of automake don't define builddir +builddir ?= . + +DOXYGEN = @DOXYGEN@ + +sssdlibexecdir = $(libexecdir)/sssd +sssdlibdir = $(libdir)/sssd +ldblibdir = @ldblibdir@ +if BUILD_KRB5_LOCATOR_PLUGIN +krb5plugindir = @krb5pluginpath@ +endif +sssdconfdir = $(sysconfdir)/sssd +sssdapiplugindir = $(sssdconfdir)/sssd.api.d +dbusintrospectdir = $(datarootdir)/sssd/introspect +dbuspolicydir = $(sysconfdir)/dbus-1/system.d +localedir = @localedir@ +nsslibdir = @nsslibdir@ +pamlibdir = $(nsslibdir)/security + +dbpath = @dbpath@ +pluginpath = @pluginpath@ +pidpath = @pidpath@ +pipepath = @pipepath@ +initdir = @initdir@ +logpath = @logpath@ +pubconfpath = @pubconfpath@ + +AM_CFLAGS = +if WANT_AUX_INFO + AM_CFLAGS += -aux-info $@.X +endif +if HAVE_GCC + AM_CFLAGS += -Wall -Wshadow -Wstrict-prototypes -Wpointer-arith \ + -Wcast-qual -Wcast-align -Wwrite-strings \ + -Werror-implicit-function-declaration +endif + +ACLOCAL_AMFLAGS = -I m4 -I . + +sbin_PROGRAMS = \ + sssd \ + sss_useradd \ + sss_userdel \ + sss_groupadd \ + sss_groupdel \ + sss_usermod \ + sss_groupmod \ + sss_groupshow + +sssdlibexec_PROGRAMS = \ + sssd_nss \ + sssd_pam \ + sssd_be \ + krb5_child \ + ldap_child \ + $(sssd_pk) \ + $(sssd_info) + +dist_sssdlibexec_SCRIPTS = \ + config/upgrade_config.py + +if HAVE_CHECK + non_interactive_check_based_tests = \ + sysdb-tests \ + strtonum-tests \ + resolv-tests \ + krb5-utils-tests \ + check_and_open-tests \ + ipa_timerules-tests \ + files-tests \ + refcount-tests \ + fail_over-tests \ + find_uid-tests \ + auth-tests \ + ipa_ldap_opt-tests +endif + +check_PROGRAMS = \ + stress-tests \ + $(non_interactive_check_based_tests) + +TESTS = \ + $(srcdir)/config/SSSDConfigTest.py \ + $(non_interactive_check_based_tests) + +sssdlib_LTLIBRARIES = \ + libsss_ldap.la \ + libsss_krb5.la \ + libsss_proxy.la \ + libsss_ipa.la + +ldblib_LTLIBRARIES = \ + memberof.la + +if BUILD_KRB5_LOCATOR_PLUGIN +krb5plugin_LTLIBRARIES = \ + sssd_krb5_locator_plugin.la +endif + +noinst_LTLIBRARIES = \ + libsss_crypt.la + +if HAVE_NSS + SSS_CRYPT_SOURCES = util/nss_sha512crypt.c + SSS_CRYPT_CFLAGS = $(NSS_CFLAGS) + SSS_CRYPT_LIBS = $(NSS_LIBS) +else + SSS_CRYPT_SOURCES = util/crypto_sha512crypt.c + SSS_CRYPT_CFLAGS = $(CRYPTO_CFLAGS) + SSS_CRYPT_LIBS = $(CRYPTO_LIBS) +endif + +libsss_crypt_la_SOURCES = \ + $(SSS_CRYPT_SOURCES) +libsss_crypt_la_CPPFLAGS = \ + $(SSS_CRYPT_CFLAGS) +libsss_crypt_la_LIBADD = \ + $(SSS_CRYPT_LIBS) + +if BUILD_PYTHON_BINDINGS +pyexec_LTLIBRARIES = \ + pysss.la +endif + +dist_noinst_SCRIPTS = \ + $(EXTRA_SCRIPTS) \ + config/setup.py \ + config/ipachangeconf.py \ + config/SSSDConfig.py \ + config/SSSDConfigTest.py + +dist_noinst_DATA = \ + config/testconfigs/sssd-valid.conf \ + config/testconfigs/noparse.api.conf \ + config/testconfigs/sssd-noversion.conf \ + config/testconfigs/sssd-badversion.conf \ + config/testconfigs/sssd-invalid.conf \ + config/testconfigs/sssd-invalid-badbool.conf + +############################### +# Global compilation settings # +############################### + +if HAVE_SYSTEM_COLLECTION + COLLECTION_CFLAGS = $(SYSTEM_COLLECTION_CFLAGS) + COLLECTION_LIBS = $(SYSTEM_COLLECTION_LIBS) +else + COLLECTION_CFLAGS = \ + -I$(srcdir)/../common/collection + COLLECTION_LIBS = \ + -L$(builddir)/../common/collection \ + -lcollection +endif + +if HAVE_SYSTEM_INI_CONFIG + INI_CFG_CFLAGS = $(SYSTEM_INI_CONFIG_CFLAGS) + INI_CFG_LIBS = $(SYSTEM_INI_CONFIG_LIBS) +else + INI_CFG_CFLAGS = \ + -I$(srcdir)/../common/ini + INI_CFG_LIBS = \ + -L$(builddir)/../common/ini/ \ + -lini_config +endif + +if HAVE_SYSTEM_DHASH + DHASH_CFLAGS = $(SYSTEM_DHASH_CFLAGS) + DHASH_LIBS = $(SYSTEM_DHASH_LIBS) +else + DHASH_CFLAGS = \ + -I$(srcdir)/../common/dhash + DHASH_LIBS = \ + -L$(builddir)/../common/dhash/ \ + -ldhash +endif + +AM_CPPFLAGS = -Wall \ + -Iinclude \ + -I.. \ + -I$(srcdir)/include \ + -I$(srcdir)/sss_client \ + -Iinclude \ + -I. \ + $(POPT_CFLAGS) \ + $(TALLOC_CFLAGS) \ + $(TDB_CFLAGS) \ + $(TEVENT_CFLAGS) \ + $(LDB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(PCRE_CFLAGS) \ + $(COLLECTION_CFLAGS) \ + $(INI_CFG_CFLAGS) \ + $(DHASH_CFLAGS) \ + -DLIBDIR=\"$(libdir)\" \ + -DVARDIR=\"$(localstatedir)\" \ + -DSHLIBEXT=\"$(SHLIBEXT)\" \ + -DSSSD_LIBEXEC_PATH=\"$(sssdlibexecdir)\" \ + -DSSSD_INTROSPECT_PATH=\"$(dbusinstropectdir)\" \ + -DSSSD_CONF_DIR=\"$(sssdconfdir)\" \ + -DSSS_NSS_SOCKET_NAME=\"$(pipepath)/nss\" \ + -DSSS_PAM_SOCKET_NAME=\"$(pipepath)/pam\" \ + -DSSS_PAM_PRIV_SOCKET_NAME=\"$(pipepath)/private/pam\" \ + -DUSE_MMAP=1 \ + -DTEVENT_DEPRECATED=1\ + -DLOCALEDIR=\"$(localedir)\" + +EXTRA_DIST = build/config.rpath + +SSSD_DEBUG_OBJ = \ + util/debug.c + +SSSD_UTIL_OBJ = \ + confdb/confdb.c \ + db/sysdb.c \ + db/sysdb_ops.c \ + db/sysdb_search.c \ + monitor/monitor_sbus.c \ + providers/dp_auth_util.c \ + providers/dp_sbus.c \ + sbus/sbus_client.c \ + sbus/sssd_dbus_common.c \ + sbus/sssd_dbus_connection.c \ + sbus/sssd_dbus_server.c \ + util/util.c \ + util/memory.c \ + util/server.c \ + util/signal.c \ + util/usertools.c \ + util/backup_file.c \ + util/strtonum.c \ + util/check_and_open.c \ + util/refcount.c \ + $(SSSD_DEBUG_OBJ) + +SSSD_RESPONDER_OBJ = \ + responder/common/responder_cmd.c \ + responder/common/responder_common.c \ + responder/common/responder_dp.c \ + responder/common/responder_packet.c + +SSSD_TOOLS_OBJ = \ + tools/sss_sync_ops.c \ + tools/tools_util.c \ + tools/files.c + +SSSD_RESOLV_OBJ = \ + resolv/async_resolv.c + +SSSD_FAILOVER_OBJ = \ + providers/fail_over.c \ + $(SSSD_RESOLV_OBJ) + +SSSD_LIBS = \ + $(TALLOC_LIBS) \ + $(TEVENT_LIBS) \ + $(POPT_LIBS) \ + $(LDB_LIBS) \ + $(DBUS_LIBS) \ + $(PCRE_LIBS) \ + $(INI_CFG_LIBS) \ + $(COLLECTION_LIBS) \ + $(DHASH_LIBS) \ + $(SSS_CRYPT_LIBS) \ + libsss_crypt.la + +PYTHON_BINDINGS_LIBS = \ + $(TALLOC_LIBS) \ + $(TEVENT_LIBS) \ + $(POPT_LIBS) \ + $(LDB_LIBS) \ + $(DBUS_LIBS) \ + $(PCRE_LIBS) \ + $(SSS_CRYPT_LIBS) \ + libsss_crypt.la + +TOOLS_LIBS = \ + $(TALLOC_LIBS) \ + $(TEVENT_LIBS) \ + $(POPT_LIBS) \ + $(LDB_LIBS) \ + $(DBUS_LIBS) \ + $(PCRE_LIBS) \ + $(INI_CFG_LIBS) \ + $(COLLECTION_LIBS) \ + $(DHASH_LIBS) \ + libsss_crypt.la + +if BUILD_SELINUX + TOOLS_LIBS += $(SELINUX_LIBS) +endif + +dist_noinst_HEADERS = \ + monitor/monitor.h \ + util/sha512crypt.h \ + util/dlinklist.h \ + util/util.h \ + util/strtonum.h \ + util/sss_ldap.h \ + util/sss_krb5.h \ + util/refcount.h \ + util/find_uid.h \ + util/user_info_msg.h \ + config.h \ + monitor/monitor.h \ + monitor/monitor_interfaces.h \ + responder/common/responder.h \ + responder/common/responder_packet.h \ + responder/pam/pamsrv.h \ + responder/nss/nsssrv.h \ + responder/nss/nsssrv_nc.h \ + sbus/sbus_client.h \ + sbus/sssd_dbus.h \ + sbus/sssd_dbus_private.h \ + db/sysdb.h \ + db/sysdb_private.h \ + confdb/confdb.h \ + confdb/confdb_private.h \ + confdb/confdb_setup.h \ + providers/data_provider.h \ + providers/dp_backend.h \ + providers/fail_over.h \ + providers/providers.h \ + providers/child_common.h \ + providers/krb5/krb5_auth.h \ + providers/krb5/krb5_common.h \ + providers/krb5/krb5_utils.h \ + providers/ldap/ldap_common.h \ + providers/ldap/sdap.h \ + providers/ldap/sdap_async.h \ + providers/ldap/sdap_async_private.h \ + providers/ipa/ipa_common.h \ + providers/ipa/ipa_access.h \ + providers/ipa/ipa_timerules.h \ + providers/ipa/ipa_auth.h \ + tools/tools_util.h \ + tools/sss_sync_ops.h \ + resolv/async_resolv.h \ + resolv/ares/ares_parse_srv_reply.h \ + resolv/ares/ares_parse_txt_reply.h \ + resolv/ares/ares_data.h \ + tests/common.h + + +#################### +# Program Binaries # +#################### +sssd_SOURCES = \ + monitor/monitor.c \ + confdb/confdb_setup.c \ + $(SSSD_UTIL_OBJ) +sssd_LDADD = \ + $(SSSD_LIBS) + +sssd_nss_SOURCES = \ + responder/nss/nsssrv.c \ + responder/nss/nsssrv_cmd.c \ + responder/nss/nsssrv_nc.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_RESPONDER_OBJ) +sssd_nss_LDADD = \ + $(TDB_LIBS) \ + $(SSSD_LIBS) + +sssd_pam_SOURCES = \ + responder/pam/pam_LOCAL_domain.c \ + responder/pam/pamsrv.c \ + responder/pam/pamsrv_cmd.c \ + responder/pam/pamsrv_dp.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_RESPONDER_OBJ) +sssd_pam_LDADD = \ + $(SSSD_LIBS) + +sssd_be_SOURCES = \ + providers/data_provider_be.c \ + providers/data_provider_fo.c \ + providers/data_provider_opts.c \ + $(SSSD_FAILOVER_OBJ) \ + $(SSSD_UTIL_OBJ) +sssd_be_LDADD = $(SSSD_LIBS) $(CARES_LIBS) +sssd_be_LDFLAGS = \ + -Wl,--version-script,$(srcdir)/providers/sssd_be.exports \ + -export-dynamic + +dist_noinst_DATA += \ + examples/sssd.conf \ + examples/sssdproxytest \ + examples/sudo \ + providers/sssd_be.exports \ + m4 + +###################### +# Command-line Tools # +###################### +sss_useradd_SOURCES = \ + tools/sss_useradd.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_TOOLS_OBJ) +sss_useradd_LDADD = \ + $(TOOLS_LIBS) + +sss_userdel_SOURCES = \ + tools/sss_userdel.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_TOOLS_OBJ) +sss_userdel_LDADD = \ + $(TOOLS_LIBS) + +sss_groupadd_SOURCES = \ + tools/sss_groupadd.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_TOOLS_OBJ) +sss_groupadd_LDADD = \ + $(TOOLS_LIBS) + +sss_groupdel_SOURCES = \ + tools/sss_groupdel.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_TOOLS_OBJ) +sss_groupdel_LDADD = \ + $(TOOLS_LIBS) + +sss_usermod_SOURCES = \ + tools/sss_usermod.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_TOOLS_OBJ) +sss_usermod_LDADD = \ + $(TOOLS_LIBS) + +sss_groupmod_SOURCES = \ + tools/sss_groupmod.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_TOOLS_OBJ) +sss_groupmod_LDADD = \ + $(TOOLS_LIBS) + +sss_groupshow_SOURCES = \ + tools/sss_groupshow.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_TOOLS_OBJ) +sss_groupshow_LDADD = \ + $(TOOLS_LIBS) + +################# +# Feature Tests # +################# +if HAVE_CHECK +sysdb_tests_DEPENDENCIES = \ + $(ldblib_LTLIBRARIES) +sysdb_tests_SOURCES = \ + tests/sysdb-tests.c \ + $(SSSD_UTIL_OBJ) +sysdb_tests_CFLAGS = \ + -DSYSDB_TEST \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +sysdb_tests_LDADD = \ + $(SSSD_LIBS) \ + $(CHECK_LIBS) + +strtonum_tests_SOURCES = \ + tests/strtonum-tests.c \ + util/debug.c \ + util/strtonum.c +strtonum_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +strtonum_tests_LDADD = \ + $(SSSD_LIBS) \ + $(CHECK_LIBS) + +krb5_utils_tests_SOURCES = \ + tests/krb5_utils-tests.c \ + providers/krb5/krb5_utils.c \ + providers/krb5/krb5_common.c \ + providers/data_provider_fo.c \ + providers/data_provider_opts.c \ + $(SSSD_FAILOVER_OBJ) \ + $(SSSD_UTIL_OBJ) +krb5_utils_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +krb5_utils_tests_LDADD = \ + $(SSSD_LIBS)\ + $(CARES_LIBS) \ + $(CHECK_LIBS) + + +check_and_open_tests_SOURCES = \ + $(SSSD_DEBUG_OBJ) \ + tests/check_and_open-tests.c \ + util/check_and_open.c +check_and_open_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +check_and_open_tests_LDADD = \ + $(CHECK_LIBS) + +FILES_TESTS_LIBS = \ + $(CHECK_LIBS) \ + $(POPT_LIBS) \ + $(TALLOC_LIBS) +if BUILD_SELINUX + FILES_TESTS_LIBS += $(SELINUX_LIBS) +endif + +files_tests_SOURCES = \ + $(SSSD_DEBUG_OBJ) \ + tests/files-tests.c \ + util/check_and_open.c \ + tools/files.c +files_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +files_tests_LDADD = \ + $(FILES_TESTS_LIBS) + +SSSD_RESOLV_TESTS_OBJ = \ + $(SSSD_RESOLV_OBJ) +if BUILD_ARES_DATA + SSSD_RESOLV_TESTS_OBJ += \ + resolv/ares/ares_parse_srv_reply.c \ + resolv/ares/ares_parse_txt_reply.c \ + resolv/ares/ares_data.c +endif + +resolv_tests_SOURCES = \ + tests/resolv-tests.c \ + tests/common.c \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_RESOLV_TESTS_OBJ) +resolv_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) \ + -DBUILD_TXT_SRV +resolv_tests_LDADD = \ + $(SSSD_LIBS) \ + $(CHECK_LIBS) \ + $(CARES_LIBS) + +refcount_tests_SOURCES = \ + tests/refcount-tests.c \ + tests/common.c \ + $(CHECK_OBJ) \ + $(SSSD_UTIL_OBJ) +refcount_tests_CFLAGS = \ + $(CHECK_CFLAGS) +refcount_tests_LDADD = \ + $(SSSD_LIBS) \ + $(CHECK_LIBS) + +fail_over_tests_SOURCES = \ + tests/fail_over-tests.c \ + tests/common.c \ + $(SSSD_FAILOVER_OBJ) \ + $(CHECK_OBJ) \ + $(SSSD_UTIL_OBJ) +fail_over_tests_CFLAGS = \ + $(CHECK_CFLAGS) +fail_over_tests_LDADD = \ + $(SSSD_LIBS) \ + $(CHECK_LIBS) \ + $(CARES_LIBS) + +ipa_timerules_tests_SOURCES = \ + providers/ipa/ipa_timerules.c \ + tests/ipa_timerules-tests.c \ + tests/common.c \ + $(SSSD_DEBUG_OBJ) +ipa_timerules_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(POPT_CFLAGS) \ + $(TALLOC_CFLAGS) \ + $(PCRE_CFLAGS) \ + $(CHECK_CFLAGS) +ipa_timerules_tests_LDADD = \ + $(POPT_LIBS) \ + $(PCRE_LIBS) \ + $(TALLOC_LIBS) \ + $(CHECK_LIBS) + +find_uid_tests_SOURCES = \ + tests/find_uid-tests.c \ + util/find_uid.c \ + $(SSSD_DEBUG_OBJ) +find_uid_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(TALLOC_CFLAGS) \ + $(DHASH_CFLAGS) \ + $(CHECK_CFLAGS) +find_uid_tests_LDADD = \ + $(TALLOC_LIBS) \ + $(DHASH_LIBS) \ + $(CHECK_LIBS) + +auth_tests_SOURCES = \ + tests/auth-tests.c \ + $(SSSD_UTIL_OBJ) +auth_tests_CFLAG = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +auth_tests_LDADD = \ + $(SSSD_LIBS) \ + $(CHECK_LIBS) + +ipa_ldap_opt_tests_SOURCES = \ + tests/ipa_ldap_opt-tests.c +ipa_ldap_opt_tests_CFLAG = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +ipa_ldap_opt_tests_LDADD = \ + $(CHECK_LIBS) + +endif + +stress_tests_SOURCES = \ + tests/stress-tests.c \ + $(SSSD_UTIL_OBJ) +stress_tests_LDADD = \ + $(SSSD_LIBS) + +noinst_PROGRAMS = pam_test_client +pam_test_client_SOURCES = sss_client/pam_test_client.c +pam_test_client_LDFLAGS = -lpam -lpam_misc + +#################### +# Client Libraries # +#################### + +nsslib_LTLIBRARIES = libnss_sss.la +libnss_sss_la_SOURCES = \ + sss_client/common.c \ + sss_client/passwd.c \ + sss_client/group.c \ + sss_client/sss_cli.h +libnss_sss_la_LDFLAGS = \ + -module \ + -version-info 2:0:0 \ + -Wl,--version-script,$(srcdir)/sss_client/sss_nss.exports + +pamlib_LTLIBRARIES = pam_sss.la +pam_sss_la_SOURCES = \ + sss_client/pam_sss.c \ + sss_client/common.c \ + sss_client/sss_cli.h \ + sss_client/sss_pam_macros.h + +pam_sss_la_LDFLAGS = \ + -lpam \ + -module \ + -avoid-version \ + -Wl,--version-script,$(srcdir)/sss_client/sss_pam.exports + +dist_noinst_DATA += \ + sss_client/sss_nss.exports \ + sss_client/sss_pam.exports + +#################### +# Plugin Libraries # +#################### +libsss_ldap_la_SOURCES = \ + providers/child_common.c \ + providers/ldap/ldap_id.c \ + providers/ldap/ldap_id_enum.c \ + providers/ldap/ldap_id_cleanup.c \ + providers/ldap/ldap_auth.c \ + providers/ldap/ldap_init.c \ + providers/ldap/ldap_common.c \ + providers/ldap/sdap_async.c \ + providers/ldap/sdap_async_accounts.c \ + providers/ldap/sdap_async_connection.c \ + providers/ldap/sdap_child_helpers.c \ + providers/ldap/sdap.c \ + util/user_info_msg.c \ + util/sss_ldap.c \ + util/sss_krb5.c +libsss_ldap_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(LDAP_CFLAGS) \ + $(KRB5_CFLAGS) +libsss_ldap_la_LIBADD = \ + $(OPENLDAP_LIBS) \ + $(KRB5_LIBS) +libsss_ldap_la_LDFLAGS = \ + -version-info 1:0:0 \ + -module + +libsss_proxy_la_SOURCES = \ + providers/proxy.c +libsss_proxy_la_CFLAGS = \ + $(AM_CFLAGS) +libsss_proxy_la_LIBADD = \ + $(PAM_LIBS) +libsss_proxy_la_LDFLAGS = \ + -version-info 1:0:0 \ + -module + +libsss_krb5_la_SOURCES = \ + util/find_uid.c \ + providers/child_common.c \ + providers/krb5/krb5_utils.c \ + providers/krb5/krb5_become_user.c \ + providers/krb5/krb5_auth.c \ + providers/krb5/krb5_common.c \ + providers/krb5/krb5_init.c \ + util/sss_krb5.c +libsss_krb5_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(DHASH_CFLAGS) +libsss_krb5_la_LIBADD = \ + $(DHASH_LIBS) \ + $(KRB5_LIBS) +libsss_krb5_la_LDFLAGS = \ + -version-info 1:0:0 \ + -module + +libsss_ipa_la_SOURCES = \ + providers/child_common.c \ + providers/ipa/ipa_init.c \ + providers/ipa/ipa_common.c \ + providers/ipa/ipa_auth.c \ + providers/ipa/ipa_access.c \ + providers/ipa/ipa_timerules.c \ + providers/ldap/ldap_id.c \ + providers/ldap/ldap_id_enum.c \ + providers/ldap/ldap_id_cleanup.c \ + providers/ldap/ldap_auth.c \ + providers/ldap/ldap_common.c \ + providers/ldap/sdap_async.c \ + providers/ldap/sdap_async_accounts.c \ + providers/ldap/sdap_async_connection.c \ + providers/ldap/sdap_child_helpers.c \ + providers/ldap/sdap.c \ + util/user_info_msg.c \ + util/sss_ldap.c \ + util/sss_krb5.c \ + util/find_uid.c \ + providers/krb5/krb5_utils.c \ + providers/krb5/krb5_become_user.c \ + providers/krb5/krb5_common.c \ + providers/krb5/krb5_auth.c +libsss_ipa_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(LDAP_CFLAGS) \ + $(DHASH_CFLAGS) \ + $(KRB5_CFLAGS) +libsss_ipa_la_LIBADD = \ + $(OPENLDAP_LIBS) \ + $(DHASH_LIBS) \ + $(KRB5_LIBS) +libsss_ipa_la_LDFLAGS = \ + -version-info 1:0:0 \ + -module + +krb5_child_SOURCES = \ + $(SSSD_DEBUG_OBJ) \ + providers/krb5/krb5_become_user.c \ + providers/krb5/krb5_child.c \ + providers/child_common.c \ + util/user_info_msg.c \ + util/sss_krb5.c +krb5_child_CFLAGS = \ + $(AM_CFLAGS) \ + $(POPT_CFLAGS) \ + $(KRB5_CFLAGS) +krb5_child_LDADD = \ + $(TALLOC_LIBS) \ + $(TEVENT_LIBS) \ + $(POPT_LIBS) \ + $(KRB5_LIBS) + +ldap_child_SOURCES = \ + $(SSSD_DEBUG_OBJ) \ + providers/ldap/ldap_child.c \ + providers/child_common.c \ + util/sss_krb5.c +ldap_child_CFLAGS = \ + $(AM_CFLAGS) \ + $(POPT_CFLAGS) \ + $(KRB5_CFLAGS) +ldap_child_LDADD = \ + $(TALLOC_LIBS) \ + $(TEVENT_LIBS) \ + $(POPT_LIBS) \ + $(OPENLDAP_LIBS) \ + $(KRB5_LIBS) + +memberof_la_SOURCES = \ + ldb_modules/memberof.c +memberof_la_CFLAGS = \ + $(AM_CFLAGS) +memberof_la_LIBADD = $(LDB_LIBS) $(DHASH_LIBS) +memberof_la_LDFLAGS = \ + -avoid-version \ + -module + +if BUILD_KRB5_LOCATOR_PLUGIN +sssd_krb5_locator_plugin_la_SOURCES = \ + krb5_plugin/sssd_krb5_locator_plugin.c +sssd_krb5_locator_plugin_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(KRB5_CFLAGS) +sssd_krb5_locator_plugin_la_LDFLAGS = \ + -avoid-version \ + -module +endif + +if BUILD_PYTHON_BINDINGS +pysss_la_SOURCES = \ + $(SSSD_UTIL_OBJ) \ + $(SSSD_TOOLS_OBJ) \ + python/pysss.c +pysss_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(PYTHON_CFLAGS) +pysss_la_LIBADD = \ + $(PYTHON_BINDINGS_LIBS) \ + $(PYTHON_LIBS) +pysss_la_LDFLAGS = \ + -avoid-version \ + -module +endif + +############ +# MANPAGES # +############ + +#Special Rules: +export SGML_CATALOG_FILES +DOCBOOK_XSLT = http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl +XMLLINT_FLAGS = --catalogs --postvalid --nonet --xinclude --noout +XSLTPROC_FLAGS = --catalogs --xinclude --nonet + +dist_man_MANS = man/sss_useradd.8 man/sss_userdel.8 man/sss_usermod.8 \ + man/sss_groupadd.8 man/sss_groupdel.8 man/sss_groupmod.8 \ + man/sssd.8 man/sssd.conf.5 man/sssd-ldap.5 man/sssd-krb5.5 \ + man/sssd-ipa.5 man/sssd_krb5_locator_plugin.8 \ + man/sss_groupshow.8 sss_client/man/pam_sss.8 + +SUFFIXES = .1.xml .1 .3.xml .3 .5.xml .5 .8.xml .8 +.1.xml.1: + $(XMLLINT) $(XMLLINT_FLAGS) $< + $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(DOCBOOK_XSLT) $< + +.3.xml.3: + $(XMLLINT) $(XMLLINT_FLAGS) $< + $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(DOCBOOK_XSLT) $< + +.5.xml.5: + $(XMLLINT) $(XMLLINT_FLAGS) $< + $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(DOCBOOK_XSLT) $< + +.8.xml.8: + $(XMLLINT) $(XMLLINT_FLAGS) $< + $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(DOCBOOK_XSLT) $< + +####################### +# Installation Extras # +####################### + +dist_init_SCRIPTS = +if HAVE_SUSE + dist_init_SCRIPTS += \ + sysv/SUSE/sssd +else + dist_init_SCRIPTS += \ + sysv/sssd +endif + + +dist_sssdconf_DATA = \ + config/etc/sssd.api.conf +dist_sssdapiplugin_DATA = \ + config/etc/sssd.api.d/sssd-ipa.conf \ + config/etc/sssd.api.d/sssd-krb5.conf \ + config/etc/sssd.api.d/sssd-ldap.conf \ + config/etc/sssd.api.d/sssd-local.conf \ + config/etc/sssd.api.d/sssd-proxy.conf + +installsssddirs:: + mkdir -p \ + $(DESTDIR)$(includedir) \ + $(DESTDIR)$(libdir) \ + $(DESTDIR)$(sbindir) \ + $(DESTDIR)$(initdir) \ + $(DESTDIR)$(mandir) \ + $(DESTDIR)$(pluginpath) \ + $(DESTDIR)$(libdir)/ldb \ + $(DESTDIR)$(dbuspolicydir) \ + $(DESTDIR)$(infpintrospectdir) \ + $(DESTDIR)$(dbusintrospectdir) \ + $(DESTDIR)$(pipepath)/private \ + $(DESTDIR)$(sssdlibdir) \ + $(DESTDIR)$(sssdconfdir) \ + $(DESTDIR)$(dbpath) \ + $(DESTDIR)$(pidpath) \ + $(DESTDIR)$(initdir) \ + $(DESTDIR)$(logpath) \ + $(DESTDIR)$(pubconfpath) + +if HAVE_DOXYGEN +docs: + $(DOXYGEN) doxy.config +else +docs: + @echo "Doxygen not installed, cannot generate documentation" + @exit 1 +endif +all-local: + cd $(srcdir)/config; $(PYTHON) setup.py build --build-base $(abs_builddir)/config + +install-exec-hook: installsssddirs + if [ "$(DESTDIR)" = "" ]; then \ + cd $(srcdir)/config; $(PYTHON) setup.py build --build-base $(abs_builddir)/config install --prefix=$(PYTHON_PREFIX) --record=$(abs_builddir)/config/.files; \ + else \ + cd $(srcdir)/config; $(PYTHON) setup.py build --build-base $(abs_builddir)/config install --prefix=$(PYTHON_PREFIX) --root=$(DESTDIR) --record=$(abs_builddir)/config/.files; \ + fi + mkdir -p doc $(DESTDIR)/$(docdir); cp -a doc $(DESTDIR)/$(docdir)/ + +install-data-hook: + rm $(DESTDIR)/$(nsslibdir)/libnss_sss.so.2 \ + $(DESTDIR)/$(nsslibdir)/libnss_sss.so + mv $(DESTDIR)/$(nsslibdir)/libnss_sss.so.2.0.0 $(DESTDIR)/$(nsslibdir)/libnss_sss.so.2 + +uninstall-hook: + if [ -f $(abs_builddir)/config/.files ]; then \ + cat $(abs_builddir)/config/.files | xargs -iq rm -f $(DESTDIR)/q; \ + rm $(abs_builddir)/config/.files ; \ + fi + rm -Rf $(DESTDIR)/$(docdir)/doc + +clean-local: + cd $(srcdir)/config; $(PYTHON) setup.py build --build-base $(abs_builddir)/config clean --all + rm -Rf doc + +CLEANFILES = *.X */*.X */*/*.X + +tests: all $(check_PROGRAMS) diff --git a/src/build_macros.m4 b/src/build_macros.m4 new file mode 100644 index 00000000..37d96439 --- /dev/null +++ b/src/build_macros.m4 @@ -0,0 +1,21 @@ +AC_DEFUN([BUILD_WITH_SHARED_BUILD_DIR], + [ AC_ARG_WITH([shared-build-dir], + [AC_HELP_STRING([--with-shared-build-dir=DIR], + [temporary build directory where libraries are installed [$srcdir/sharedbuild]])]) + + sharedbuilddir="$srcdir/sharedbuild" + if test x"$with_shared_build_dir" != x; then + sharedbuilddir=$with_shared_build_dir + CFLAGS="$CFLAGS -I$with_shared_build_dir/include" + CPPFLAGS="$CPPFLAGS -I$with_shared_build_dir/include" + LDFLAGS="$LDFLAGS -L$with_shared_build_dir/lib" + fi + AC_SUBST(sharedbuilddir) + ]) + +AC_DEFUN([BUILD_WITH_AUX_INFO], + [ AC_ARG_WITH([aux-info], + [AC_HELP_STRING([--with-aux-info], + [Build with -aux-info output])]) + ]) +AM_CONDITIONAL([WANT_AUX_INFO], [test x$with_aux_info = xyes]) diff --git a/src/conf_macros.m4 b/src/conf_macros.m4 new file mode 100644 index 00000000..86ccf5d9 --- /dev/null +++ b/src/conf_macros.m4 @@ -0,0 +1,207 @@ +AC_DEFUN([WITH_DB_PATH], + [ AC_ARG_WITH([db-path], + [AC_HELP_STRING([--with-db-path=PATH], + [Path to the SSSD databases [/var/lib/sss/db]] + ) + ] + ) + config_dbpath="\"VARDIR\"/lib/sss/db" + dbpath="${localstatedir}/lib/sss/db" + if test x"$with_db_path" != x; then + config_dbpath=$with_db_path + dbpath=$with_db_path + fi + AC_SUBST(dbpath) + AC_DEFINE_UNQUOTED(DB_PATH, "$config_dbpath", [Path to the SSSD databases]) + ]) + +AC_DEFUN([WITH_PLUGIN_PATH], + [ AC_ARG_WITH([plugin-path], + [AC_HELP_STRING([--with-plugin-path=PATH], + [Path to the SSSD data provider plugins [/usr/lib/sssd]] + ) + ] + ) + pluginpath="${libdir}/sssd" + config_pluginpath="\"LIBDIR\"/sssd" + if test x"$with_plugin_path" != x; then + pluginpath=$with_plugin_path + config_pluginpath=$with_plugin_path + fi + AC_SUBST(pluginpath) + AC_DEFINE_UNQUOTED(DATA_PROVIDER_PLUGINS_PATH, "$config_pluginpath", [Path to the SSSD data provider plugins]) + ]) + +AC_DEFUN([WITH_PID_PATH], + [ AC_ARG_WITH([pid-path], + [AC_HELP_STRING([--with-pid-path=PATH], + [Where to store pid files for the SSSD [/var/run]] + ) + ] + ) + config_pidpath="\"VARDIR\"/run" + pidpath="${localstatedir}/run" + if test x"$with_pid_path" != x; then + config_pidpath=$with_pid_path + pidpath=$with_pid_path + fi + AC_SUBST(pidpath) + AC_DEFINE_UNQUOTED(PID_PATH, "$config_pidpath", [Where to store pid files for the SSSD]) + ]) + +AC_DEFUN([WITH_LOG_PATH], + [ AC_ARG_WITH([log-path], + [AC_HELP_STRING([--with-log-path=PATH], + [Where to store log files for the SSSD [/var/log/sssd]] + ) + ] + ) + config_logpath="\"VARDIR\"/log/sssd" + logpath="${localstatedir}/log/sssd" + if test x"$with_log_path" != x; then + config_logpath=$with_log_path + logpath=$with_log_path + fi + AC_SUBST(logpath) + AC_DEFINE_UNQUOTED(LOG_PATH, "$config_logpath", [Where to store log files for the SSSD]) + ]) + +AC_DEFUN([WITH_PUBCONF_PATH], + [ AC_ARG_WITH([pubconf-path], + [AC_HELP_STRING([--with-pubconf-path=PATH], + [Where to store pubconf files for the SSSD [/var/lib/sss/pubconf]] + ) + ] + ) + config_pubconfpath="\"VARDIR\"/lib/sss/pubconf" + pubconfpath="${localstatedir}/lib/sss/pubconf" + if test x"$with_pubconf_path" != x; then + config_pubconfpath=$with_pubconf_path + pubconfpath=$with_pubconf_path + fi + AC_SUBST(pubconfpath) + AC_DEFINE_UNQUOTED(PUBCONF_PATH, "$config_pubconfpath", [Where to store pubconf files for the SSSD]) + ]) + +AC_DEFUN([WITH_PIPE_PATH], + [ AC_ARG_WITH([pipe-path], + [AC_HELP_STRING([--with-pipe-path=PATH], + [Where to store pipe files for the SSSD interconnects [/var/lib/sss/pipes]] + ) + ] + ) + config_pipepath="\"VARDIR\"/lib/sss/pipes" + pipepath="${localstatedir}/lib/sss/pipes" + if test x"$with_pipe_path" != x; then + config_pipepath=$with_pipe_path + pipepath=$with_pipe_path + fi + AC_SUBST(pipepath) + AC_DEFINE_UNQUOTED(PIPE_PATH, "$config_pipepath", [Where to store pipe files for the SSSD interconnects]) + ]) + +AC_DEFUN([WITH_INIT_DIR], + [ AC_ARG_WITH([init-dir], + [AC_HELP_STRING([--with-init-dir=DIR], + [Where to store init script for sssd [/etc/rc.d/init.d]] + ) + ] + ) + initdir="${sysconfdir}/rc.d/init.d" + if test x"$with_init_dir" != x; then + initdir=$with_init_dir + fi + AC_SUBST(initdir) + ]) + +AC_DEFUN([WITH_SHADOW_UTILS_PATH], + [ AC_ARG_WITH([shadow-utils-path], + [AC_HELP_STRING([--with-shadow-utils-path=PATH], + [Where to look for shadow-utils binaries [/usr/sbin]] + ) + ] + ) + shadow_utils_path="${sbindir}" + if test x"$with_shadow_utils_path" != x; then + shadow_utils_path=$with_shadow_utils_path + fi + AC_SUBST(shadow_utils_path) + ]) + +AC_DEFUN([WITH_MANPAGES], + [ AC_ARG_WITH([manpages], + [AC_HELP_STRING([--with-manpages], + [Whether to regenerate man pages from DocBook sources [yes]] + ) + ], + [], + with_manpages=yes + ) + if test x"$with_manpages" == xyes; then + HAVE_MANPAGES=1 + AC_SUBST(HAVE_MANPAGES) + fi + ]) +AM_CONDITIONAL([BUILD_MANPAGES], [test x$with_manpages = xyes]) + +AC_DEFUN([WITH_XML_CATALOG], + [ AC_ARG_WITH([xml-catalog-path], + [AC_HELP_STRING([--with-xml-catalog-path=PATH], + [Where to look for XML catalog [/etc/xml/catalog]] + ) + ] + ) + SGML_CATALOG_FILES="/etc/xml/catalog" + if test x"$with_xml_catalog_path" != x; then + SGML_CATALOG_FILES="$with_xml_catalog_path" + fi + AC_SUBST([SGML_CATALOG_FILES]) + ]) + +AC_DEFUN([WITH_KRB5_PLUGIN_PATH], + [ AC_ARG_WITH([krb5-plugin-path], + [AC_HELP_STRING([--with-krb5-plugin-path=PATH], + [Path to kerberos plugin store [/usr/lib/krb5/plugins/libkrb5]] + ) + ] + ) + krb5pluginpath="${libdir}/krb5/plugins/libkrb5" + if test x"$with_krb5_plugin_path" != x; then + krb5pluginpath=$with_krb5_plugin_path + fi + AC_SUBST(krb5pluginpath) + ]) + +AC_DEFUN([WITH_PYTHON_BINDINGS], + [ AC_ARG_WITH([python-bindings], + [AC_HELP_STRING([--with-python-bindings], + [Whether to build python bindings [yes]] + ) + ], + [], + with_python_bindings=yes + ) + if test x"$with_python_bindings" == xyes; then + HAVE_PYTHON_BINDINGS=1 + AC_SUBST(HAVE_PYTHON_BINDINGS) + fi + AM_CONDITIONAL([BUILD_PYTHON_BINDINGS], [test x"$with_python_bindings" = xyes]) + ]) + +AC_DEFUN([WITH_SELINUX], + [ AC_ARG_WITH([selinux], + [AC_HELP_STRING([--with-selinux], + [Whether to build with SELinux support [yes]] + ) + ], + [], + with_selinux=yes + ) + if test x"$with_selinux" == xyes; then + HAVE_SELINUX=1 + AC_SUBST(HAVE_SELINUX) + AC_DEFINE_UNQUOTED(HAVE_SELINUX, 1, [Build with SELinux support]) + fi + AM_CONDITIONAL([BUILD_SELINUX], [test x"$with_selinux" = xyes]) + ]) + diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c new file mode 100644 index 00000000..6981baa5 --- /dev/null +++ b/src/confdb/confdb.c @@ -0,0 +1,908 @@ +/* + SSSD + + NSS Configuratoin DB + + Copyright (C) Simo Sorce 2008 + + 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; either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#define _GNU_SOURCE + +#include +#include "config.h" +#include "util/util.h" +#include "confdb/confdb.h" +#include "confdb/confdb_private.h" +#include "util/strtonum.h" +#include "db/sysdb.h" + +#define CONFDB_ZERO_CHECK_OR_JUMP(var, ret, err, label) do { \ + if (!var) { \ + ret = err; \ + goto label; \ + } \ +} while(0) + +static char *prepend_cn(char *str, int *slen, const char *comp, int clen) +{ + char *ret; + + ret = talloc_realloc(NULL, str, char, *slen + 4 + clen + 1); + if (!ret) + return NULL; + + /* move current string to the end */ + memmove(&ret[clen +4], ret, *slen+1); /* includes termination */ + memcpy(ret, "cn=", 3); + memcpy(&ret[3], comp, clen); + ret[clen+3] = ','; + + *slen = *slen + 4 + clen; + + return ret; +} + +int parse_section(TALLOC_CTX *mem_ctx, const char *section, + char **sec_dn, const char **rdn_name) +{ + TALLOC_CTX *tmp_ctx; + char *dn = NULL; + char *p; + const char *s; + int l, ret; + + /* section must be a non null string and must not start with '/' */ + if (!section || !*section || *section == '/') return EINVAL; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + s = section; + l = 0; + while ((p = strchrnul(s, '/'))) { + if (l == 0) { + dn = talloc_asprintf(tmp_ctx, "cn=%s", s); + l = 3 + (p-s); + dn[l] = '\0'; + } else { + dn = prepend_cn(dn, &l, s, p-s); + } + if (!dn) { + ret = ENOMEM; + goto done; + } + if (*p == '\0') { + if (rdn_name) *rdn_name = s; + break; /* reached end */ + } + s = p+1; + if (*s == '\0') { /* a section cannot end in '.' */ + ret = EINVAL; + goto done; + } + } + + *sec_dn = talloc_steal(mem_ctx, dn); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int confdb_add_param(struct confdb_ctx *cdb, + bool replace, + const char *section, + const char *attribute, + const char **values) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_message *msg; + struct ldb_result *res; + struct ldb_dn *dn; + char *secdn; + const char *rdn_name; + int ret, i; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + ret = parse_section(tmp_ctx, section, &secdn, &rdn_name); + if (ret != EOK) { + goto done; + } + + dn = ldb_dn_new(tmp_ctx, cdb->ldb, secdn); + CONFDB_ZERO_CHECK_OR_JUMP(dn, ret, EIO, done); + + ret = ldb_search(cdb->ldb, tmp_ctx, &res, + dn, LDB_SCOPE_BASE, NULL, NULL); + if (ret != LDB_SUCCESS) { + ret = EIO; + goto done; + } + + msg = ldb_msg_new(tmp_ctx); + CONFDB_ZERO_CHECK_OR_JUMP(msg, ret, ENOMEM, done); + + msg->dn = talloc_steal(msg, dn); + CONFDB_ZERO_CHECK_OR_JUMP(msg->dn, ret, ENOMEM, done); + + if (res->count == 0) { /* add a new message */ + errno = 0; + + /* cn first */ + ret = ldb_msg_add_string(msg, "cn", rdn_name); + if (ret != LDB_SUCCESS) { + if (errno) ret = errno; + else ret = EIO; + goto done; + } + + /* now the requested attribute */ + for (i = 0; values[i]; i++) { + ret = ldb_msg_add_string(msg, attribute, values[i]); + if (ret != LDB_SUCCESS) { + if (errno) ret = errno; + else ret = EIO; + goto done; + } + } + + ret = ldb_add(cdb->ldb, msg); + if (ret != LDB_SUCCESS) { + ret = EIO; + goto done; + } + + } else { + int optype; + errno = 0; + + /* mark this as a replacement */ + if (replace) optype = LDB_FLAG_MOD_REPLACE; + else optype = LDB_FLAG_MOD_ADD; + ret = ldb_msg_add_empty(msg, attribute, optype, NULL); + if (ret != LDB_SUCCESS) { + if (errno) ret = errno; + else ret = EIO; + goto done; + } + + /* now the requested attribute */ + for (i = 0; values[i]; i++) { + ret = ldb_msg_add_string(msg, attribute, values[i]); + if (ret != LDB_SUCCESS) { + if (errno) ret = errno; + else ret = EIO; + goto done; + } + } + + ret = ldb_modify(cdb->ldb, msg); + if (ret != LDB_SUCCESS) { + ret = EIO; + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + if (ret != EOK) { + DEBUG(1, ("Failed to add [%s] to [%s], error [%d] (%s)", + attribute, section, ret, strerror(ret))); + } + return ret; +} + +int confdb_get_param(struct confdb_ctx *cdb, + TALLOC_CTX *mem_ctx, + const char *section, + const char *attribute, + char ***values) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + struct ldb_dn *dn; + char *secdn; + const char *attrs[] = { attribute, NULL }; + char **vals; + struct ldb_message_element *el; + int ret, i; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) + return ENOMEM; + + ret = parse_section(tmp_ctx, section, &secdn, NULL); + if (ret != EOK) { + goto done; + } + + dn = ldb_dn_new(tmp_ctx, cdb->ldb, secdn); + if (!dn) { + ret = EIO; + goto done; + } + + ret = ldb_search(cdb->ldb, tmp_ctx, &res, + dn, LDB_SCOPE_BASE, attrs, NULL); + if (ret != LDB_SUCCESS) { + ret = EIO; + goto done; + } + if (res->count > 1) { + ret = EIO; + goto done; + } + + vals = talloc_zero(mem_ctx, char *); + ret = EOK; + + if (res->count > 0) { + el = ldb_msg_find_element(res->msgs[0], attribute); + if (el && el->num_values > 0) { + vals = talloc_realloc(mem_ctx, vals, char *, el->num_values +1); + if (!vals) { + ret = ENOMEM; + goto done; + } + /* should always be strings so this should be safe */ + for (i = 0; i < el->num_values; i++) { + struct ldb_val v = el->values[i]; + vals[i] = talloc_strndup(vals, (char *)v.data, v.length); + if (!vals[i]) { + ret = ENOMEM; + goto done; + } + } + vals[i] = NULL; + } + } + + *values = vals; + +done: + talloc_free(tmp_ctx); + if (ret != EOK) { + DEBUG(1, ("Failed to get [%s] from [%s], error [%d] (%s)", + attribute, section, ret, strerror(ret))); + } + return ret; +} + +int confdb_get_string(struct confdb_ctx *cdb, TALLOC_CTX *ctx, + const char *section, const char *attribute, + const char *defstr, char **result) +{ + char **values = NULL; + char *restr; + int ret; + + ret = confdb_get_param(cdb, ctx, section, attribute, &values); + if (ret != EOK) { + goto failed; + } + + if (values[0]) { + if (values[1] != NULL) { + /* too many values */ + ret = EINVAL; + goto failed; + } + restr = talloc_steal(ctx, values[0]); + } else { + /* Did not return a value, so use the default */ + + if (defstr == NULL) { /* No default given */ + *result = NULL; + talloc_free(values); + return EOK; + } + + /* Copy the default string */ + restr = talloc_strdup(ctx, defstr); + } + if (!restr) { + ret = ENOMEM; + goto failed; + } + + talloc_free(values); + + *result = restr; + return EOK; + +failed: + talloc_free(values); + DEBUG(1, ("Failed to get [%s] from [%s], error [%d] (%s)", + attribute, section, ret, strerror(ret))); + return ret; +} + +int confdb_get_int(struct confdb_ctx *cdb, TALLOC_CTX *ctx, + const char *section, const char *attribute, + int defval, int *result) +{ + char **values = NULL; + long val; + int ret; + + ret = confdb_get_param(cdb, ctx, section, attribute, &values); + if (ret != EOK) { + goto failed; + } + + if (values[0]) { + if (values[1] != NULL) { + /* too many values */ + ret = EINVAL; + goto failed; + } + + errno = 0; + val = strtol(values[0], NULL, 0); + if (errno) { + ret = errno; + goto failed; + } + + if (val < INT_MIN || val > INT_MAX) { + ret = ERANGE; + goto failed; + } + + } else { + val = defval; + } + + talloc_free(values); + + *result = (int)val; + return EOK; + +failed: + talloc_free(values); + DEBUG(1, ("Failed to read [%s] from [%s], error [%d] (%s)", + attribute, section, ret, strerror(ret))); + return ret; +} + +long confdb_get_long(struct confdb_ctx *cdb, TALLOC_CTX *ctx, + const char *section, const char *attribute, + long defval, long *result) +{ + char **values = NULL; + long val; + int ret; + + ret = confdb_get_param(cdb, ctx, section, attribute, &values); + if (ret != EOK) { + goto failed; + } + + if (values[0]) { + if (values[1] != NULL) { + /* too many values */ + ret = EINVAL; + goto failed; + } + + errno = 0; + val = strtol(values[0], NULL, 0); + if (errno) { + ret = errno; + goto failed; + } + + } else { + val = defval; + } + + talloc_free(values); + + *result = val; + return EOK; + +failed: + talloc_free(values); + DEBUG(1, ("Failed to read [%s] from [%s], error [%d] (%s)", + attribute, section, ret, strerror(ret))); + return ret; +} + +int confdb_get_bool(struct confdb_ctx *cdb, TALLOC_CTX *ctx, + const char *section, const char *attribute, + bool defval, bool *result) +{ + char **values = NULL; + bool val; + int ret; + + ret = confdb_get_param(cdb, ctx, section, attribute, &values); + if (ret != EOK) { + goto failed; + } + + if (values[0]) { + if (values[1] != NULL) { + /* too many values */ + ret = EINVAL; + goto failed; + } + + if (strcasecmp(values[0], "FALSE") == 0) { + val = false; + + } else if (strcasecmp(values[0], "TRUE") == 0) { + val = true; + + } else { + + DEBUG(2, ("Value is not a boolean!\n")); + ret = EINVAL; + goto failed; + } + + } else { + val = defval; + } + + talloc_free(values); + + *result = val; + return EOK; + +failed: + talloc_free(values); + DEBUG(1, ("Failed to read [%s] from [%s], error [%d] (%s)", + attribute, section, ret, strerror(ret))); + return ret; +} + +/* WARNING: Unlike other similar functions, this one does NOT take a default, + * and returns ENOENT if the attribute was not found ! */ +int confdb_get_string_as_list(struct confdb_ctx *cdb, TALLOC_CTX *ctx, + const char *section, const char *attribute, + char ***result) +{ + char **values = NULL; + int ret; + + ret = confdb_get_param(cdb, ctx, section, attribute, &values); + if (ret != EOK) { + goto done; + } + + if (values && values[0]) { + if (values[1] != NULL) { + /* too many values */ + ret = EINVAL; + goto done; + } + } else { + /* Did not return a value */ + ret = ENOENT; + goto done; + } + + ret = split_on_separator(ctx, values[0], ',', true, result, NULL); + +done: + talloc_free(values); + if (ret != EOK && ret != ENOENT) { + DEBUG(2, ("Failed to get [%s] from [%s], error [%d] (%s)", + attribute, section, ret, strerror(ret))); + } + return ret; +} + +int confdb_init(TALLOC_CTX *mem_ctx, + struct confdb_ctx **cdb_ctx, + char *confdb_location) +{ + struct confdb_ctx *cdb; + int ret = EOK; + + cdb = talloc_zero(mem_ctx, struct confdb_ctx); + if (!cdb) + return ENOMEM; + + /* Because confdb calls use sync ldb calls, we create a separate event + * context here. This will prevent the ldb sync calls to start nested + * events. + * NOTE: this means that we *cannot* do async calls and return in confdb + * unless we convert all calls and hook back to the main event context. + */ + + cdb->pev = tevent_context_init(cdb); + if (!cdb->pev) { + talloc_free(cdb); + return EIO; + } + + cdb->ldb = ldb_init(cdb, cdb->pev); + if (!cdb->ldb) { + talloc_free(cdb); + return EIO; + } + + ret = ldb_set_debug(cdb->ldb, ldb_debug_messages, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Could not set up debug fn.\n")); + talloc_free(cdb); + return EIO; + } + + ret = ldb_connect(cdb->ldb, confdb_location, 0, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Unable to open config database [%s]\n", + confdb_location)); + talloc_free(cdb); + return EIO; + } + + *cdb_ctx = cdb; + + return EOK; +} + +static errno_t get_entry_as_uint32(struct ldb_message *msg, + uint32_t *return_value, + const char *entry, + uint32_t default_value) +{ + const char *tmp = NULL; + char *endptr; + uint32_t u32ret = 0; + + *return_value = 0; + + if (!msg || !entry) { + return EFAULT; + } + + tmp = ldb_msg_find_attr_as_string(msg, entry, NULL); + if (tmp == NULL) { + *return_value = default_value; + return EOK; + } + + if ((*tmp == '-') || (*tmp == '\0')) { + return EINVAL; + } + + u32ret = strtouint32 (tmp, &endptr, 10); + if (errno) { + return errno; + } + + if (*endptr != '\0') { + /* Not all of the string was a valid number */ + return EINVAL; + } + + *return_value = u32ret; + return EOK; +} + +static errno_t get_entry_as_bool(struct ldb_message *msg, + bool *return_value, + const char *entry, + bool default_value) +{ + const char *tmp = NULL; + + *return_value = 0; + + if (!msg || !entry) { + return EFAULT; + } + + tmp = ldb_msg_find_attr_as_string(msg, entry, NULL); + if (tmp == NULL || *tmp == '\0') { + *return_value = default_value; + return EOK; + } + + if (strcasecmp(tmp, "FALSE") == 0) { + *return_value = 0; + } + else if (strcasecmp(tmp, "TRUE") == 0) { + *return_value = 1; + } + else { + return EINVAL; + } + + return EOK; +} + +static int confdb_get_domain_internal(struct confdb_ctx *cdb, + TALLOC_CTX *mem_ctx, + const char *name, + struct sss_domain_info **_domain) +{ + struct sss_domain_info *domain; + struct ldb_result *res; + TALLOC_CTX *tmp_ctx; + struct ldb_dn *dn; + const char *tmp; + int ret, val; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + dn = ldb_dn_new_fmt(tmp_ctx, cdb->ldb, + "cn=%s,%s", name, CONFDB_DOMAIN_BASEDN); + if (!dn) { + ret = ENOMEM; + goto done; + } + + ret = ldb_search(cdb->ldb, tmp_ctx, &res, dn, + LDB_SCOPE_BASE, NULL, NULL); + if (ret != LDB_SUCCESS) { + ret = EIO; + goto done; + } + + if (res->count != 1) { + DEBUG(0, ("Unknown domain [%s]\n", name)); + ret = ENOENT; + goto done; + } + + domain = talloc_zero(mem_ctx, struct sss_domain_info); + if (!domain) { + ret = ENOMEM; + goto done; + } + + tmp = ldb_msg_find_attr_as_string(res->msgs[0], "cn", NULL); + if (!tmp) { + DEBUG(0, ("Invalid configuration entry, fatal error!\n")); + ret = EINVAL; + goto done; + } + domain->name = talloc_strdup(domain, tmp); + if (!domain->name) { + ret = ENOMEM; + goto done; + } + + tmp = ldb_msg_find_attr_as_string(res->msgs[0], + CONFDB_DOMAIN_ID_PROVIDER, + NULL); + if (tmp) { + domain->provider = talloc_strdup(domain, tmp); + if (!domain->provider) { + ret = ENOMEM; + goto done; + } + } + else { + DEBUG(0, ("Domain [%s] does not specify an ID provider, disabling!\n", + domain->name)); + ret = EINVAL; + goto done; + } + + if (strcasecmp(domain->provider, "files") == 0) { + /* The files provider is not valid anymore */ + DEBUG(0, ("The \"files\" provider is invalid\n")); + ret = EINVAL; + goto done; + } + + if (strcasecmp(domain->provider, "local") == 0) { + /* If this is the local provider, we need to ensure that + * no other provider was specified for other types, since + * the local provider cannot load them. + */ + tmp = ldb_msg_find_attr_as_string(res->msgs[0], + CONFDB_DOMAIN_AUTH_PROVIDER, + NULL); + if (tmp && strcasecmp(tmp, "local") != 0) { + DEBUG(0, ("Local ID provider does not support [%s] as an AUTH provider.\n", tmp)); + ret = EINVAL; + goto done; + } + + tmp = ldb_msg_find_attr_as_string(res->msgs[0], + CONFDB_DOMAIN_ACCESS_PROVIDER, + NULL); + if (tmp && strcasecmp(tmp, "local") != 0) { + DEBUG(0, ("Local ID provider does not support [%s] as an ACCESS provider.\n", tmp)); + ret = EINVAL; + goto done; + } + + tmp = ldb_msg_find_attr_as_string(res->msgs[0], + CONFDB_DOMAIN_CHPASS_PROVIDER, + NULL); + if (tmp && strcasecmp(tmp, "local") != 0) { + DEBUG(0, ("Local ID provider does not support [%s] as a CHPASS provider.\n", tmp)); + ret = EINVAL; + goto done; + } + } + + domain->timeout = ldb_msg_find_attr_as_int(res->msgs[0], + CONFDB_DOMAIN_TIMEOUT, 0); + + /* Determine if this domain can be enumerated */ + + /* TEMP: test if the old bitfield conf value is used and warn it has been + * superceeded. */ + val = ldb_msg_find_attr_as_int(res->msgs[0], CONFDB_DOMAIN_ENUMERATE, 0); + if (val > 0) { /* ok there was a number in here */ + DEBUG(0, ("Warning: enumeration parameter in %s still uses integers! " + "Enumeration is now a boolean and takes true/false values. " + "Interpreting as true\n", domain->name)); + domain->enumerate = true; + } else { /* assume the new format */ + ret = get_entry_as_bool(res->msgs[0], &domain->enumerate, + CONFDB_DOMAIN_ENUMERATE, 1); + if(ret != EOK) { + DEBUG(0, ("Invalid value for %s\n", CONFDB_DOMAIN_ENUMERATE)); + goto done; + } + } + if (!domain->enumerate) { + DEBUG(1, ("No enumeration for [%s]!\n", domain->name)); + } + + /* Determine if user/group names will be Fully Qualified + * in NSS interfaces */ + ret = get_entry_as_bool(res->msgs[0], &domain->fqnames, CONFDB_DOMAIN_FQ, 0); + if(ret != EOK) { + DEBUG(0, ("Invalid value for %s\n", CONFDB_DOMAIN_FQ)); + goto done; + } + + ret = get_entry_as_uint32(res->msgs[0], &domain->id_min, + CONFDB_DOMAIN_MINID, SSSD_MIN_ID); + if (ret != EOK) { + DEBUG(0, ("Invalid value for minId\n")); + ret = EINVAL; + goto done; + } + + ret = get_entry_as_uint32(res->msgs[0], &domain->id_max, + CONFDB_DOMAIN_MAXID, 0); + if (ret != EOK) { + DEBUG(0, ("Invalid value for maxId\n")); + ret = EINVAL; + goto done; + } + + if (domain->id_max && (domain->id_max < domain->id_min)) { + DEBUG(0, ("Invalid domain range\n")); + ret = EINVAL; + goto done; + } + + /* Do we allow to cache credentials */ + ret = get_entry_as_bool(res->msgs[0], &domain->cache_credentials, + CONFDB_DOMAIN_CACHE_CREDS, 0); + if(ret != EOK) { + DEBUG(0, ("Invalid value for %s\n", CONFDB_DOMAIN_CACHE_CREDS)); + goto done; + } + + ret = get_entry_as_bool(res->msgs[0], &domain->legacy_passwords, + CONFDB_DOMAIN_LEGACY_PASS, 0); + if(ret != EOK) { + DEBUG(0, ("Invalid value for %s\n", CONFDB_DOMAIN_LEGACY_PASS)); + goto done; + } + + *_domain = domain; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int confdb_get_domains(struct confdb_ctx *cdb, + struct sss_domain_info **domains) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *domain, *prevdom = NULL; + char **domlist; + int ret, i; + + if (cdb->doms) { + *domains = cdb->doms; + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = confdb_get_string_as_list(cdb, tmp_ctx, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_ACTIVE_DOMAINS, + &domlist); + if (ret == ENOENT) { + DEBUG(0, ("No domains configured, fatal error!\n")); + goto done; + } + if (ret != EOK ) { + DEBUG(0, ("Fatal error retrieving domains list!\n")); + goto done; + } + + for (i = 0; domlist[i]; i++) { + ret = confdb_get_domain_internal(cdb, cdb, domlist[i], &domain); + if (ret) { + DEBUG(0, ("Error (%d [%s]) retrieving domain [%s], skipping!\n", + ret, strerror(ret), domlist[i])); + ret = EOK; + continue; + } + + if (cdb->doms == NULL) { + cdb->doms = domain; + prevdom = cdb->doms; + } else { + prevdom->next = domain; + prevdom = domain; + } + } + + if (cdb->doms == NULL) { + DEBUG(0, ("No properly configured domains, fatal error!\n")); + ret = ENOENT; + goto done; + } + + *domains = cdb->doms; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int confdb_get_domain(struct confdb_ctx *cdb, + const char *name, + struct sss_domain_info **_domain) +{ + struct sss_domain_info *dom, *doms; + int ret; + + ret = confdb_get_domains(cdb, &doms); + if (ret != EOK) { + return ret; + } + + for (dom = doms; dom; dom = dom->next) { + if (strcasecmp(dom->name, name) == 0) { + *_domain = dom; + return EOK; + } + } + + return ENOENT; +} diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h new file mode 100644 index 00000000..e848e8bc --- /dev/null +++ b/src/confdb/confdb.h @@ -0,0 +1,364 @@ +/* + SSSD + + NSS Configuratoin DB + + Copyright (C) Simo Sorce 2008 + + 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; either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#ifndef _CONF_DB_H +#define _CONF_DB_H + +#include +#include "talloc.h" +#include "tevent.h" +#include "ldb.h" +#include "ldb_errors.h" +#include "config.h" + +/** + * @defgroup sss_confdb The ConfDB API + * The ConfDB is an interface for data providers to + * access the configuration information provided in + * the sssd.conf + * @{ + */ + +#define CONFDB_FILE "config.ldb" +#define CONFDB_DEFAULT_CONFIG_FILE SSSD_CONF_DIR"/sssd.conf" +#define SSSD_MIN_ID 1000 + + +/* Configuration options */ + +/* Services */ +#define CONFDB_SERVICE_PATH_TMPL "config/%s" +#define CONFDB_SERVICE_COMMAND "command" +#define CONFDB_SERVICE_DEBUG_LEVEL "debug_level" +#define CONFDB_SERVICE_DEBUG_TIMESTAMPS "debug_timestamps" +#define CONFDB_SERVICE_DEBUG_TO_FILES "debug_to_files" +#define CONFDB_SERVICE_TIMEOUT "timeout" +#define CONFDB_SERVICE_RECON_RETRIES "reconnection_retries" + +/* Monitor */ +#define CONFDB_MONITOR_CONF_ENTRY "config/sssd" +#define CONFDB_MONITOR_SBUS_TIMEOUT "sbus_timeout" +#define CONFDB_MONITOR_ACTIVE_SERVICES "services" +#define CONFDB_MONITOR_ACTIVE_DOMAINS "domains" +#define CONFDB_MONITOR_NAME_REGEX "re_expression" +#define CONFDB_MONITOR_FULL_NAME_FORMAT "full_name_format" + +/* NSS */ +#define CONFDB_NSS_CONF_ENTRY "config/nss" +#define CONFDB_NSS_ENUM_CACHE_TIMEOUT "enum_cache_timeout" +#define CONFDB_NSS_ENTRY_CACHE_NOWAIT_PERCENTAGE "entry_cache_nowait_percentage" +#define CONFDB_NSS_ENTRY_NEG_TIMEOUT "entry_negative_timeout" +#define CONFDB_NSS_FILTER_USERS_IN_GROUPS "filter_users_in_groups" +#define CONFDB_NSS_FILTER_USERS "filter_users" +#define CONFDB_NSS_FILTER_GROUPS "filter_groups" +#define CONFDB_NSS_PWFIELD "pwfield" + +/* PAM */ +#define CONFDB_PAM_CONF_ENTRY "config/pam" +#define CONFDB_PAM_CRED_TIMEOUT "offline_credentials_expiration" +#define CONFDB_PAM_FAILED_LOGIN_ATTEMPTS "offline_failed_login_attempts" +#define CONFDB_DEFAULT_PAM_FAILED_LOGIN_ATTEMPTS 0 +#define CONFDB_PAM_FAILED_LOGIN_DELAY "offline_failed_login_delay" +#define CONFDB_DEFAULT_PAM_FAILED_LOGIN_DELAY 5 + +/* Data Provider */ +#define CONFDB_DP_CONF_ENTRY "config/dp" + +/* Domains */ +#define CONFDB_DOMAIN_PATH_TMPL "config/domain/%s" +#define CONFDB_DOMAIN_BASEDN "cn=domain,cn=config" +#define CONFDB_DOMAIN_ID_PROVIDER "id_provider" +#define CONFDB_DOMAIN_AUTH_PROVIDER "auth_provider" +#define CONFDB_DOMAIN_ACCESS_PROVIDER "access_provider" +#define CONFDB_DOMAIN_CHPASS_PROVIDER "chpass_provider" +#define CONFDB_DOMAIN_COMMAND "command" +#define CONFDB_DOMAIN_TIMEOUT "timeout" +#define CONFDB_DOMAIN_ATTR "cn" +#define CONFDB_DOMAIN_ENUMERATE "enumerate" +#define CONFDB_DOMAIN_MINID "min_id" +#define CONFDB_DOMAIN_MAXID "max_id" +#define CONFDB_DOMAIN_CACHE_CREDS "cache_credentials" +#define CONFDB_DOMAIN_LEGACY_PASS "store_legacy_passwords" +#define CONFDB_DOMAIN_MPG "magic_private_groups" +#define CONFDB_DOMAIN_FQ "use_fully_qualified_names" +#define CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT "entry_cache_timeout" + +/* Local Provider */ +#define CONFDB_LOCAL_DEFAULT_SHELL "default_shell" +#define CONFDB_LOCAL_DEFAULT_BASEDIR "base_directory" +#define CONFDB_LOCAL_CREATE_HOMEDIR "create_homedir" +#define CONFDB_LOCAL_REMOVE_HOMEDIR "remove_homedir" +#define CONFDB_LOCAL_UMASK "homedir_umask" +#define CONFDB_LOCAL_SKEL_DIR "skel_dir" +#define CONFDB_LOCAL_MAIL_DIR "mail_dir" + +/* Proxy Provider */ +#define CONFDB_PROXY_LIBNAME "proxy_lib_name" +#define CONFDB_PROXY_PAM_TARGET "proxy_pam_target" + +/* KRB5 Provider */ +#define CONFDB_KRB5_KDCIP "krb5_kdcip" +#define CONFDB_KRB5_REALM "krb5_realm" +#define CONFDB_KRB5_CCACHEDIR "krb5_ccachedir" +#define CONFDB_KRB5_CCNAME_TMPL "krb5_ccname_template" +#define CONFDB_KRB5_CHANGEPW_PRINC "krb5_changepw_principal" +#define CONFDB_KRB5_AUTH_TIMEOUT "krb5_auth_timeout" + +struct confdb_ctx; +struct config_file_ctx; + +/** + * Data structure storing all of the basic features + * of a domain. + */ +struct sss_domain_info { + char *name; + char *provider; + int timeout; + bool enumerate; + bool fqnames; + uint32_t id_min; + uint32_t id_max; + + bool cache_credentials; + bool legacy_passwords; + + struct sss_domain_info *next; +}; + +/** + * Initialize the connection to the ConfDB + * + * @param[in] mem_ctx The parent memory context for the confdb_ctx + * @param[out] cdb_ctx The newly-created connection object + * @param[in] confdb_location The absolute path to the ConfDB file on the + * filesystem + * + * @return 0 - Connection succeeded and cdb_ctx was populated + * @return ENOMEM - There was not enough memory to create the cdb_ctx + * @return EIO - There was an I/O error communicating with the ConfDB file + */ +int confdb_init(TALLOC_CTX *mem_ctx, + struct confdb_ctx **cdb_ctx, + char *confdb_location); + +/** + * Get a domain object for the named domain + * + * @param[in] cdb The connection object to the confdb + * @param[in] name The name of the domain to retrieve + * @param[out] domain A pointer to a domain object for the domain given by + * name + * + * @return 0 - Lookup succeeded and domain was populated + * @return ENOMEM - There was insufficient memory to complete the operation + * @return ENOENT - The named domain does not exist or is not set active + */ +int confdb_get_domain(struct confdb_ctx *cdb, + const char *name, + struct sss_domain_info **domain); + +/** + * Get a null-terminated linked-list of active domain objects + * @param[in] cdb The connection object to the confdb + * @param[out] domains A pointer to the first entry of a linked-list of domain + * objects + * + * @return 0 - Lookup succeeded and all active domains are in the list + * @return ENOMEM - There was insufficient memory to complete the operation + * @return ENOENT - No active domains are configured + */ +int confdb_get_domains(struct confdb_ctx *cdb, + struct sss_domain_info **domains); + + +/** + * @brief Add an arbitrary parameter to the confdb. + * + * This is mostly useful + * for testing, as they will not persist between SSSD restarts. For + * persistence, make changes to the sssd.conf file. + * + * @param[in] cdb The connection object to the confdb + * @param[in] replace If replace is set to true, pre-existing values will be + * overwritten. + * If it is false, the provided values will be added to the + * attribute. + * @param[in] section The ConfDB section to update. This is constructed from + * the format of the sssd.conf file. All sections start + * with 'config/'. Subsections are separated by slashes. + * e.g. [domain/LDAP] in sssd.conf would translate to + * config/domain/LDAP + * @param[in] attribute The name of the attribute to update + * @param[in] values A null-terminated array of values to add to the attribute + * + * @return 0 - Successfully added the provided value(s) + * @return ENOMEM - There was insufficient memory to complete the operation + * @return EINVAL - The section could not be parsed + * @return EIO - An I/O error occurred communicating with the ConfDB + */ +int confdb_add_param(struct confdb_ctx *cdb, + bool replace, + const char *section, + const char *attribute, + const char **values); + +/** + * @brief Retrieve all values for an attribute + * + * @param[in] cdb The connection object to the confdb + * @param[in] mem_ctx The parent memory context for the value list + * @param[in] section The ConfDB section to update. This is constructed from + * the format of the sssd.conf file. All sections start + * with 'config/'. Subsections are separated by slashes. + * e.g. [domain/LDAP] in sssd.conf would translate to + * config/domain/LDAP + * @param[in] attribute The name of the attribute to update + * @param[out] values A null-terminated array of cstrings containing all + * values for this attribute + * + * @return 0 - Successfully retrieved the value(s) + * @return ENOMEM - There was insufficient memory to complete the operation + * @return EINVAL - The section could not be parsed + * @return EIO - An I/O error occurred while communicating with the ConfDB + */ +int confdb_get_param(struct confdb_ctx *cdb, + TALLOC_CTX *mem_ctx, + const char *section, + const char *attribute, + char ***values); + +/** + * @brief Convenience function to retrieve a single-valued attribute as a + * string + * + * @param[in] cdb The connection object to the confdb + * @param[in] ctx The parent memory context for the returned string + * @param[in] section The ConfDB section to update. This is constructed from + * the format of the sssd.conf file. All sections start + * with 'config/'. Subsections are separated by slashes. + * e.g. [domain/LDAP] in sssd.conf would translate to + * config/domain/LDAP + * @param[in] attribute The name of the attribute to update + * @param[in] defstr If not NULL, the string to use if the attribute does not + * exist in the ConfDB + * @param[out] result A pointer to the retrieved (or default) string + * + * @return 0 - Successfully retrieved the entry (or used the default) + * @return ENOMEM - There was insufficient memory to complete the operation + * @return EINVAL - The section could not be parsed, or the attribute was not + * single-valued. + * @return EIO - An I/O error occurred while communicating with the ConfDB + */ +int confdb_get_string(struct confdb_ctx *cdb, TALLOC_CTX *ctx, + const char *section, const char *attribute, + const char *defstr, char **result); + +/** + * @brief Convenience function to retrieve a single-valued attribute as an + * integer + * + * @param[in] cdb The connection object to the confdb + * @param[in] ctx The parent memory context for the returned string + * @param[in] section The ConfDB section to update. This is constructed from + * the format of the sssd.conf file. All sections start + * with 'config/'. Subsections are separated by slashes. + * e.g. [domain/LDAP] in sssd.conf would translate to + * config/domain/LDAP + * @param[in] attribute The name of the attribute to update + * @param[in] defval If not NULL, the integer to use if the attribute does not + * exist in the ConfDB + * @param[out] result A pointer to the retrieved (or default) integer + * + * @return 0 - Successfully retrieved the entry (or used the default) + * @return ENOMEM - There was insufficient memory to complete the operation + * @return EINVAL - The section could not be parsed, or the attribute was not + * single-valued. + * @return EIO - An I/O error occurred while communicating with the ConfDB + * @return ERANGE - The value stored in the ConfDB was outside the range + * [INT_MIN..INT_MAX] + */ +int confdb_get_int(struct confdb_ctx *cdb, TALLOC_CTX *ctx, + const char *section, const char *attribute, + int defval, int *result); + +/** + * @brief Convenience function to retrieve a single-valued attribute as a + * boolean + * + * This function will read (in a case-insensitive manner) a "true" or "false" + * value from the ConfDB and convert it to an integral bool value. + * + * @param[in] cdb The connection object to the confdb + * @param[in] ctx The parent memory context for the returned string + * @param[in] section The ConfDB section to update. This is constructed from + * the format of the sssd.conf file. All sections start + * with 'config/'. Subsections are separated by slashes. + * e.g. [domain/LDAP] in sssd.conf would translate to + * config/domain/LDAP + * @param[in] attribute The name of the attribute to update + * @param[in] defval If not NULL, the boolean state to use if the attribute + * does not exist in the ConfDB + * @param[out] result A pointer to the retrieved (or default) bool + * + * @return 0 - Successfully retrieved the entry (or used the default) + * @return ENOMEM - There was insufficient memory to complete the operation + * @return EINVAL - The section could not be parsed, the attribute was not + * single-valued, or the value was not a boolean. + * @return EIO - An I/O error occurred while communicating with the ConfDB + */ +int confdb_get_bool(struct confdb_ctx *cdb, TALLOC_CTX *ctx, + const char *section, const char *attribute, + bool defval, bool *result); + +/** + * @brief Convenience function to retrieve a single-valued attribute as a + * null-terminated array of strings + * + * This function will automatically split a comma-separated string in an + * attribute into a null-terminated array of strings. This is useful for + * storing and retrieving ordered lists, as ConfDB multivalued attributes do + * not guarantee retrieval order. + * + * @param[in] cdb The connection object to the confdb + * @param[in] ctx The parent memory context for the returned string + * @param[in] section The ConfDB section to update. This is constructed from + * the format of the sssd.conf file. All sections start + * with 'config/'. Subsections are separated by slashes. + * e.g. [domain/LDAP] in sssd.conf would translate to + * config/domain/LDAP + * @param[in] attribute The name of the attribute to update + * @param[out] result A pointer to the retrieved array of strings + * + * @return 0 - Successfully retrieved the entry (or used the default) + * @return ENOMEM - There was insufficient memory to complete the operation + * @return EINVAL - The section could not be parsed, or the attribute was not + * single-valued. + * @return ENOENT - The attribute was not found. + * @return EIO - An I/O error occurred while communicating with the ConfDB + */ +int confdb_get_string_as_list(struct confdb_ctx *cdb, TALLOC_CTX *ctx, + const char *section, const char *attribute, + char ***result); +/** + * @} + */ +#endif diff --git a/src/confdb/confdb_private.h b/src/confdb/confdb_private.h new file mode 100644 index 00000000..1bab99ca --- /dev/null +++ b/src/confdb/confdb_private.h @@ -0,0 +1,35 @@ +/* + SSSD + + Configuration Database + + Copyright (C) Stephen Gallagher 2009 + + 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; either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#ifndef CONFDB_PRIVATE_H_ +#define CONFDB_PRIVATE_H_ + +struct confdb_ctx { + struct tevent_context *pev; + struct ldb_context *ldb; + + struct sss_domain_info *doms; +}; + +int parse_section(TALLOC_CTX *mem_ctx, const char *section, + char **sec_dn, const char **rdn_name); + +#endif /* CONFDB_PRIVATE_H_ */ diff --git a/src/confdb/confdb_setup.c b/src/confdb/confdb_setup.c new file mode 100644 index 00000000..3c10c06c --- /dev/null +++ b/src/confdb/confdb_setup.c @@ -0,0 +1,423 @@ +/* + SSSD + + Configuration Database + + Copyright (C) Stephen Gallagher 2009 + + 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; either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#include "config.h" +#include +#include "util/util.h" +#include "db/sysdb.h" +#include "confdb.h" +#include "confdb_private.h" +#include "confdb_setup.h" +#include "collection.h" +#include "collection_tools.h" +#include "ini_config.h" + + +int confdb_test(struct confdb_ctx *cdb) +{ + char **values; + int ret; + + ret = confdb_get_param(cdb, cdb, + "config", + "version", + &values); + if (ret != EOK) { + return ret; + } + + if (values[0] == NULL) { + /* empty database, will need to init */ + talloc_free(values); + return ENOENT; + } + + if (values[1] != NULL) { + /* more than 1 value ?? */ + talloc_free(values); + return EIO; + } + + if (strcmp(values[0], CONFDB_VERSION) != 0) { + /* Existing version does not match executable version */ + DEBUG(1, ("Upgrading confdb version from %s to %s\n", + values[0], CONFDB_VERSION)); + + /* This is recoverable, since we purge the confdb file + * when we re-initialize it. + */ + talloc_free(values); + return ENOENT; + } + + talloc_free(values); + return EOK; +} + +static int confdb_purge(struct confdb_ctx *cdb) +{ + int ret, i; + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + struct ldb_dn *dn; + const char *attrs[] = { "dn", NULL }; + + tmp_ctx = talloc_new(NULL); + + dn = ldb_dn_new(tmp_ctx, cdb->ldb, "cn=config"); + + /* Get the list of all DNs */ + ret = ldb_search(cdb->ldb, tmp_ctx, &res, dn, + LDB_SCOPE_SUBTREE, attrs, NULL); + if (ret != LDB_SUCCESS) { + ret = sysdb_error_to_errno(ret); + goto done; + } + + for(i=0; icount; i++) { + /* Delete this DN */ + ret = ldb_delete(cdb->ldb, res->msgs[i]->dn); + if (ret != LDB_SUCCESS) { + ret = sysdb_error_to_errno(ret); + goto done; + } + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +int confdb_create_base(struct confdb_ctx *cdb) +{ + int ret; + struct ldb_ldif *ldif; + + const char *base_ldif = CONFDB_BASE_LDIF; + + while ((ldif = ldb_ldif_read_string(cdb->ldb, &base_ldif))) { + ret = ldb_add(cdb->ldb, ldif->msg); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Failed to initialize DB (%d,[%s]), aborting!\n", + ret, ldb_errstring(cdb->ldb))); + return EIO; + } + ldb_ldif_read_free(cdb->ldb, ldif); + } + + return EOK; +} + +static int confdb_create_ldif(TALLOC_CTX *mem_ctx, + struct collection_item *sssd_config, + char **config_ldif) +{ + int ret, i, j; + char *ldif; + char *tmp_ldif; + char *writer; + char **sections; + int section_count; + char *dn; + char *tmp_dn; + char *sec_dn; + char **attrs; + int attr_count; + char *ldif_attr; + struct collection_item *attr; + TALLOC_CTX *tmp_ctx; + size_t dn_size; + size_t ldif_len; + size_t attr_len; + + ldif_len = strlen(CONFDB_INTERNAL_LDIF); + ldif = talloc_array(mem_ctx, char, ldif_len+1); + if (!ldif) return ENOMEM; + + tmp_ctx = talloc_new(ldif); + if (!tmp_ctx) { + ret = ENOMEM; + goto error; + } + + memcpy(ldif, CONFDB_INTERNAL_LDIF, ldif_len); + writer = ldif+ldif_len; + + /* Read in the collection and convert it to an LDIF */ + /* Get the list of sections */ + sections = get_section_list(sssd_config, §ion_count, &ret); + if (ret != EOK) { + goto error; + } + + for(i = 0; i < section_count; i++) { + const char *rdn = NULL; + DEBUG(6,("Processing config section [%s]\n", sections[i])); + ret = parse_section(tmp_ctx, sections[i], &sec_dn, &rdn); + if (ret != EOK) { + goto error; + } + + dn = talloc_asprintf(tmp_ctx, + "dn: %s,cn=config\n" + "cn: %s\n", + sec_dn, rdn); + if(!dn) { + ret = ENOMEM; + free_section_list(sections); + goto error; + } + dn_size = strlen(dn); + + /* Get all of the attributes and their values as LDIF */ + attrs = get_attribute_list(sssd_config, sections[i], + &attr_count, &ret); + if (ret != EOK) { + free_section_list(sections); + goto error; + } + + for(j = 0; j < attr_count; j++) { + DEBUG(6, ("Processing attribute [%s]\n", attrs[j])); + ret = get_config_item(sections[i], attrs[j], sssd_config, + &attr); + if (ret != EOK) goto error; + + const char *value = get_const_string_config_value(attr, &ret); + if (ret != EOK) goto error; + + ldif_attr = talloc_asprintf(tmp_ctx, + "%s: %s\n", attrs[j], value); + DEBUG(9, ("%s", ldif_attr)); + + attr_len = strlen(ldif_attr); + + tmp_dn = talloc_realloc(tmp_ctx, dn, char, + dn_size+attr_len+1); + if(!tmp_dn) { + ret = ENOMEM; + free_attribute_list(attrs); + free_section_list(sections); + goto error; + } + dn = tmp_dn; + memcpy(dn+dn_size, ldif_attr, attr_len+1); + dn_size += attr_len; + } + + dn_size ++; + tmp_dn = talloc_realloc(tmp_ctx, dn, char, + dn_size+1); + if(!tmp_dn) { + ret = ENOMEM; + free_attribute_list(attrs); + free_section_list(sections); + goto error; + } + dn = tmp_dn; + dn[dn_size-1] = '\n'; + dn[dn_size] = '\0'; + + DEBUG(9, ("Section dn\n%s", dn)); + + tmp_ldif = talloc_realloc(mem_ctx, ldif, char, + ldif_len+dn_size+1); + if(!tmp_ldif) { + ret = ENOMEM; + free_attribute_list(attrs); + free_section_list(sections); + goto error; + } + ldif = tmp_ldif; + memcpy(ldif+ldif_len, dn, dn_size); + ldif_len += dn_size; + + free_attribute_list(attrs); + talloc_free(dn); + } + + ldif[ldif_len] = '\0'; + + free_section_list(sections); + + *config_ldif = ldif; + talloc_free(tmp_ctx); + return EOK; + +error: + talloc_free(ldif); + return ret; +} + +int confdb_init_db(const char *config_file, struct confdb_ctx *cdb) +{ + int ret, i; + int fd = -1; + struct collection_item *sssd_config = NULL; + struct collection_item *error_list = NULL; + struct collection_item *item = NULL; + char *config_ldif; + struct ldb_ldif *ldif; + TALLOC_CTX *tmp_ctx; + char *lasttimestr, timestr[21]; + const char *vals[2] = { timestr, NULL }; + struct stat cstat; + int version; + + tmp_ctx = talloc_new(cdb); + if (tmp_ctx == NULL) return ENOMEM; + + ret = check_and_open_readonly(config_file, &fd, 0, 0, (S_IRUSR|S_IWUSR)); + if (ret != EOK) { + DEBUG(1, ("Permission check on config file failed.\n")); + talloc_zfree(tmp_ctx); + return EIO; + } + + /* Determine if the conf file has changed since we last updated + * the confdb + */ + ret = fstat(fd, &cstat); + if (ret != 0) { + DEBUG(0, ("Unable to stat config file [%s]! (%d [%s])\n", + config_file, errno, strerror(errno))); + close(fd); + talloc_zfree(tmp_ctx); + return errno; + } + ret = snprintf(timestr, 21, "%llu", (long long unsigned)cstat.st_mtime); + if (ret <= 0 || ret >= 21) { + DEBUG(0, ("Failed to convert time_t to string ??\n")); + close(fd); + talloc_zfree(tmp_ctx); + return errno ? errno: EFAULT; + } + + /* check if we need to re-init the db */ + ret = confdb_get_string(cdb, tmp_ctx, "config", "lastUpdate", NULL, &lasttimestr); + if (ret == EOK && lasttimestr != NULL) { + + /* now check if we lastUpdate and last file modification change differ*/ + if (strcmp(lasttimestr, timestr) == 0) { + /* not changed, get out, nothing more to do */ + close(fd); + talloc_zfree(tmp_ctx); + return EOK; + } + } + + /* Set up a transaction to replace the configuration */ + ret = ldb_transaction_start(cdb->ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Failed to start a transaction for updating the configuration\n")); + talloc_zfree(tmp_ctx); + close(fd); + return sysdb_error_to_errno(ret); + } + + /* Purge existing database */ + ret = confdb_purge(cdb); + if (ret != EOK) { + DEBUG(0, ("Could not purge existing configuration\n")); + close(fd); + goto done; + } + + /* Read the configuration into a collection */ + ret = config_from_fd("sssd", fd, config_file, &sssd_config, + INI_STOP_ON_ANY, &error_list); + close(fd); + if (ret != EOK) { + DEBUG(0, ("Parse error reading configuration file [%s]\n", + config_file)); + print_file_parsing_errors(stderr, error_list); + free_ini_config_errors(error_list); + free_ini_config(sssd_config); + goto done; + } + + /* Make sure that the config file version matches the confdb version */ + ret = get_config_item("sssd", "config_file_version", + sssd_config, &item); + if (ret != EOK) { + DEBUG(0, ("Internal error determining config_file_version\n")); + goto done; + } + if (item == NULL) { + /* No known version. Assumed to be version 1 */ + DEBUG(0, ("Config file is an old version. " + "Please run configuration upgrade script.\n")); + ret = EINVAL; + goto done; + } + version = get_int_config_value(item, 1, -1, &ret); + if (ret != EOK) { + DEBUG(0, ("Config file version could not be determined\n")); + goto done; + } else if (version < CONFDB_VERSION_INT) { + DEBUG(0, ("Config file is an old version. " + "Please run configuration upgrade script.\n")); + ret = EINVAL; + goto done; + } else if (version > CONFDB_VERSION_INT) { + DEBUG(0, ("Config file version is newer than confdb\n")); + ret = EINVAL; + goto done; + } + + ret = confdb_create_ldif(tmp_ctx, sssd_config, &config_ldif); + free_ini_config(sssd_config); + if (ret != EOK) { + DEBUG(0, ("Could not create LDIF for confdb\n")); + goto done; + } + + DEBUG(7, ("LDIF file to import: \n%s", config_ldif)); + + i=0; + while ((ldif = ldb_ldif_read_string(cdb->ldb, (const char **)&config_ldif))) { + ret = ldb_add(cdb->ldb, ldif->msg); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Failed to initialize DB (%d,[%s]), aborting!\n", + ret, ldb_errstring(cdb->ldb))); + ret = EIO; + goto done; + } + ldb_ldif_read_free(cdb->ldb, ldif); + } + + /* now store the lastUpdate time so that we do not re-init if nothing + * changed on restart */ + + ret = confdb_add_param(cdb, true, "config", "lastUpdate", vals); + if (ret != EOK) { + DEBUG(1, ("Failed to set last update time on db!\n")); + } + + ret = EOK; + +done: + ret == EOK ? + ldb_transaction_commit(cdb->ldb) : + ldb_transaction_cancel(cdb->ldb); + talloc_zfree(tmp_ctx); + return ret; +} diff --git a/src/confdb/confdb_setup.h b/src/confdb/confdb_setup.h new file mode 100644 index 00000000..2b8802f6 --- /dev/null +++ b/src/confdb/confdb_setup.h @@ -0,0 +1,52 @@ +/* + SSSD + + Configuration Database + + Copyright (C) Stephen Gallagher 2009 + + 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; either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#ifndef CONFDB_SETUP_H_ +#define CONFDB_SETUP_H_ + +#define CONFDB_VERSION "2" +#define CONFDB_VERSION_INT 2 + +#define CONFDB_BASE_LDIF \ + "dn: @ATTRIBUTES\n" \ + "cn: CASE_INSENSITIVE\n" \ + "dc: CASE_INSENSITIVE\n" \ + "dn: CASE_INSENSITIVE\n" \ + "name: CASE_INSENSITIVE\n" \ + "objectclass: CASE_INSENSITIVE\n" \ + "\n" \ + "dn: @INDEXLIST\n" \ + "@IDXATTR: cn\n" \ + "\n" \ + "dn: @MODULES\n" \ + "@LIST: server_sort\n" \ + "\n" + +#define CONFDB_INTERNAL_LDIF \ + "dn: cn=config\n" \ + "version: "CONFDB_VERSION"\n" \ + "\n" + +int confdb_create_base(struct confdb_ctx *cdb); +int confdb_test(struct confdb_ctx *cdb); +int confdb_init_db(const char *config_file, struct confdb_ctx *cdb); + +#endif /* CONFDB_SETUP_H_ */ diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py new file mode 100644 index 00000000..a004c33b --- /dev/null +++ b/src/config/SSSDConfig.py @@ -0,0 +1,1664 @@ +''' +Created on Sep 18, 2009 + +@author: sgallagh +''' + +import os +import gettext +import exceptions +from ipachangeconf import SSSDChangeConf + +# Exceptions +class SSSDConfigException(Exception): pass +class ParsingError(Exception): pass +class AlreadyInitializedError(SSSDConfigException): pass +class NotInitializedError(SSSDConfigException): pass +class NoOutputFileError(SSSDConfigException): pass +class NoServiceError(SSSDConfigException): pass +class NoSectionError(SSSDConfigException): pass +class NoOptionError(SSSDConfigException): pass +class ServiceNotRecognizedError(SSSDConfigException): pass +class ServiceAlreadyExists(SSSDConfigException): pass +class NoDomainError(SSSDConfigException): pass +class DomainNotRecognized(SSSDConfigException): pass +class DomainAlreadyExistsError(SSSDConfigException): pass +class NoSuchProviderError(SSSDConfigException): pass +class NoSuchProviderSubtypeError(SSSDConfigException): pass +class ProviderSubtypeInUse(SSSDConfigException): pass + +PACKAGE = 'sss_daemon' +LOCALEDIR = '/usr/share/locale' + +translation = gettext.translation(PACKAGE, LOCALEDIR, fallback=True) +_ = translation.ugettext + +# TODO: This needs to be made external +option_strings = { + # [service] + 'debug_level' : _('Set the verbosity of the debug logging'), + 'debug_timestamps' : _('Include timestamps in debug logs'), + 'debug_to_files' : _('Write debug messages to logfiles'), + 'timeout' : _('Ping timeout before restarting service'), + 'command' : _('Command to start service'), + 'reconnection_retries' : _('Number of times to attempt connection to Data Providers'), + + # [sssd] + 'services' : _('SSSD Services to start'), + 'domains' : _('SSSD Domains to start'), + 'sbus_timeout' : _('Timeout for messages sent over the SBUS'), + 're_expression' : _('Regex to parse username and domain'), + 'full_name_format' : _('Printf-compatible format for displaying fully-qualified names'), + + # [nss] + 'enum_cache_timeout' : _('Enumeration cache timeout length (seconds)'), + 'entry_cache_no_wait_timeout' : _('Entry cache background update timeout length (seconds)'), + 'entry_negative_timeout' : _('Negative cache timeout length (seconds)'), + 'filter_users' : _('Users that SSSD should explicitly ignore'), + 'filter_groups' : _('Groups that SSSD should explicitly ignore'), + 'filter_users_in_groups' : _('Should filtered users appear in groups'), + 'pwfield' : _('The value of the password field the NSS provider should return'), + + # [pam] + 'offline_credentials_expiration' : _('How long to allow cached logins between online logins (days)'), + 'offline_failed_login_attempts' : _('How many failed logins attempts are allowed when offline'), + 'offline_failed_login_delay' : _('How long (minutes) to deny login after offline_failed_login_attempts has been reached'), + + # [provider] + 'id_provider' : _('Identity provider'), + 'auth_provider' : _('Authentication provider'), + 'access_provider' : _('Access control provider'), + 'chpass_provider' : _('Password change provider'), + + # [domain] + 'min_id' : _('Minimum user ID'), + 'max_id' : _('Maximum user ID'), + 'timeout' : _('Ping timeout before restarting domain'), + 'enumerate' : _('Enable enumerating all users/groups'), + 'cache_credentials' : _('Cache credentials for offline login'), + 'store_legacy_passwords' : _('Store password hashes'), + 'use_fully_qualified_names' : _('Display users/groups in fully-qualified form'), + 'entry_cache_timeout' : _('Entry cache timeout length (seconds)'), + + # [provider/ipa] + 'ipa_domain' : _('IPA domain'), + 'ipa_server' : _('IPA server address'), + 'ipa_hostname' : _('IPA client hostname'), + + # [provider/krb5] + 'krb5_kdcip' : _('Kerberos server address'), + 'krb5_realm' : _('Kerberos realm'), + 'krb5_auth_timeout' : _('Authentication timeout'), + + # [provider/krb5/auth] + 'krb5_ccachedir' : _('Directory to store credential caches'), + 'krb5_ccname_template' : _("Location of the user's credential cache"), + 'krb5_keytab' : _("Location of the keytab to validate credentials"), + 'krb5_validate' : _("Enable credential validation"), + + # [provider/krb5/chpass] + 'krb5_changepw_principal' : _('The principal of the change password service'), + + # [provider/ldap] + 'ldap_uri' : _('ldap_uri, The URI of the LDAP server'), + 'ldap_search_base' : _('The default base DN'), + 'ldap_schema' : _('The Schema Type in use on the LDAP server, rfc2307'), + 'ldap_default_bind_dn' : _('The default bind DN'), + 'ldap_default_authtok_type' : _('The type of the authentication token of the default bind DN'), + 'ldap_default_authtok' : _('The authentication token of the default bind DN'), + 'ldap_network_timeout' : _('Length of time to attempt connection'), + 'ldap_opt_timeout' : _('Length of time to attempt synchronous LDAP operations'), + 'ldap_offline_timeout' : _('Length of time between attempts to reconnect while offline'), + 'ldap_tls_cacert' : _('file that contains CA certificates'), + 'ldap_tls_reqcert' : _('Require TLS certificate verification'), + 'ldap_sasl_mech' : _('Specify the sasl mechanism to use'), + 'ldap_sasl_authid' : _('Specify the sasl authorization id to use'), + 'krb5_kdcip' : _('Kerberos server address'), + 'krb5_realm' : _('Kerberos realm'), + 'ldap_krb5_keytab' : _('Kerberos service keytab'), + 'ldap_krb5_init_creds' : _('Use Kerberos auth for LDAP connection'), + 'ldap_referrals' : _('Follow LDAP referrals'), + + # [provider/ldap/id] + 'ldap_search_timeout' : _('Length of time to wait for a search request'), + 'ldap_enumeration_refresh_timeout' : _('Length of time between enumeration updates'), + 'ldap_id_use_start_tls' : _('Require TLS for ID lookups, false'), + 'ldap_user_search_base' : _('Base DN for user lookups'), + 'ldap_user_search_scope' : _('Scope of user lookups'), + 'ldap_user_search_filter' : _('Filter for user lookups'), + 'ldap_user_object_class' : _('Objectclass for users'), + 'ldap_user_name' : _('Username attribute'), + 'ldap_user_uid_number' : _('UID attribute'), + 'ldap_user_gid_number' : _('Primary GID attribute'), + 'ldap_user_gecos' : _('GECOS attribute'), + 'ldap_user_homedir' : _('Home directory attribute'), + 'ldap_user_shell' : _('Shell attribute'), + 'ldap_user_uuid' : _('UUID attribute'), + 'ldap_user_principal' : _('User principal attribute (for Kerberos)'), + 'ldap_user_fullname' : _('Full Name'), + 'ldap_user_member_of' : _('memberOf attribute'), + 'ldap_user_modify_timestamp' : _('Modification time attribute'), + + # [provider/ldap/auth] + 'ldap_pwd_policy' : _('Policy to evaluate the password expiration'), + + # [provider/local/id] + 'default_shell' : _('Default shell, /bin/bash'), + 'base_directory' : _('Base for home directories'), + + # [provider/proxy/id] + 'proxy_lib_name' : _('The name of the NSS library to use'), + + # [provider/proxy/auth] + 'proxy_pam_target' : _('PAM stack to use') +} + +def striplist(l): + return([x.strip() for x in l]) + +def options_overlap(options1, options2): + overlap = [] + for option in options1: + if option in options2: + overlap.append(option) + return overlap + +class SSSDConfigSchema(SSSDChangeConf): + def __init__(self, schemafile, schemaplugindir): + SSSDChangeConf.__init__(self) + #TODO: get these from a global setting + if not schemafile: + schemafile = '/etc/sssd/sssd.api.conf' + if not schemaplugindir: + schemaplugindir = '/etc/sssd/sssd.api.d' + + try: + #Read the primary config file + fd = open(schemafile, 'r') + self.readfp(fd) + fd.close() + # Read in the provider files + for file in os.listdir(schemaplugindir): + fd = open(schemaplugindir+ "/" + file) + self.readfp(fd) + fd.close() + except IOError: + raise + except SyntaxError: # can be raised with readfp + raise ParsingError + + # Set up lookup table for types + self.type_lookup = { + 'bool' : bool, + 'int' : int, + 'long' : long, + 'float': float, + 'str' : str, + 'list' : list, + 'None' : None + } + + # Lookup table for acceptable boolean values + self.bool_lookup = { + 'false' : False, + 'true' : True, + } + + def get_options(self, section): + if not self.has_section(section): + raise NoSectionError + options = self.options(section) + + # Indexes + PRIMARY_TYPE = 0 + SUBTYPE = 1 + MANDATORY = 2 + DEFAULT = 3 + + # Parse values + parsed_options = {} + for option in self.strip_comments_empty(options): + unparsed_option = option['value'] + split_option = striplist(unparsed_option.split(',')) + optionlen = len(split_option) + + primarytype = self.type_lookup[split_option[PRIMARY_TYPE]] + subtype = self.type_lookup[split_option[SUBTYPE]] + mandatory = self.bool_lookup[split_option[MANDATORY]] + + if option_strings.has_key(option['name']): + desc = option_strings[option['name']] + else: + desc = None + + if optionlen == 3: + # This option has no defaults + parsed_options[option['name']] = \ + (primarytype, + subtype, + mandatory, + desc, + None) + elif optionlen == 4: + if type(split_option[DEFAULT]) == primarytype: + parsed_options[option['name']] = \ + (primarytype, + subtype, + mandatory, + desc, + split_option[DEFAULT]) + elif primarytype == list: + if (type(split_option[DEFAULT]) == subtype): + parsed_options[option['name']] = \ + (primarytype, + subtype, + mandatory, + desc, + [split_option[DEFAULT]]) + else: + try: + if subtype == bool and \ + type(split_option[DEFAULT]) == str: + parsed_options[option['name']] = \ + (primarytype, + subtype, + mandatory, + desc, + [self.bool_lookup[split_option[DEFAULT].lower()]]) + else: + parsed_options[option['name']] = \ + (primarytype, + subtype, + mandatory, + desc, + [subtype(split_option[DEFAULT])]) + except ValueError, KeyError: + raise ParsingError + else: + try: + if primarytype == bool and \ + type(split_option[DEFAULT]) == str: + parsed_options[option['name']] = \ + (primarytype, + subtype, + mandatory, + desc, + self.bool_lookup[split_option[DEFAULT].lower()]) + else: + parsed_options[option['name']] = \ + (primarytype, + subtype, + mandatory, + desc, + primarytype(split_option[DEFAULT])) + except ValueError, KeyError: + raise ParsingError + + elif optionlen > 4: + if (primarytype != list): + raise ParsingError + fixed_options = [] + for x in split_option[DEFAULT:]: + if type(x) != subtype: + try: + if (subtype == bool and type(x) == str): + newvalue = self.bool_lookup[x.lower()] + else: + newvalue = subtype(x) + fixed_options.extend([newvalue]) + except ValueError, KeyError: + raise ParsingError + else: + fixed_options.extend([x]) + parsed_options[option['name']] = \ + (primarytype, + subtype, + mandatory, + desc, + fixed_options) + else: + # Bad config file + raise ParsingError + + return parsed_options + + def get_option(self, section, option): + if not self.has_section(section): + raise NoSectionError(section) + if not self.has_option(section, option): + raise NoOptionError("Section [%s] has no option [%s]" % + (section, option)) + + return self.get_options(section)[option] + + def get_defaults(self, section): + if not self.has_section(section): + raise NoSectionError(section) + + schema_options = self.get_options(section) + defaults = dict([(x,schema_options[x][4]) + for x in schema_options.keys() + if schema_options[x][4] != None]) + + return defaults + + def get_services(self): + service_list = [x['name'] for x in self.sections() + if x['name'] != 'service' and + not x['name'].startswith('domain') and + not x['name'].startswith('provider')] + return service_list + + def get_providers(self): + providers = {} + for section in self.sections(): + splitsection = section['name'].split('/') + if (splitsection[0] == 'provider'): + if(len(splitsection) == 3): + if not providers.has_key(splitsection[1]): + providers[splitsection[1]] = [] + providers[splitsection[1]].extend([splitsection[2]]) + for key in providers.keys(): + providers[key] = tuple(providers[key]) + return providers + +class SSSDConfigObject(object): + def __init__(self): + self.name = None + self.options = {} + + def get_name(self): + """ + Return the name of the this object + + === Returns === + The domain name + + === Errors === + No errors + """ + return self.name + + def get_option(self, optionname): + """ + Return the value of an service option + + optionname: + The option to get. + + === Returns === + The value for the requested option. + + === Errors === + NoOptionError: + The specified option was not listed in the service + """ + if optionname in self.options.keys(): + return self.options[optionname] + raise NoOptionError(optionname) + + def get_all_options(self): + """ + Return a dictionary of name/value pairs for this object + + === Returns === + A dictionary of name/value pairs currently in use for this object + + === Errors === + No errors + """ + return self.options + + def remove_option(self, optionname): + """ + Remove an option from the object. If the option does not exist, it is ignored. + + === Returns === + No return value. + + === Errors === + No errors + """ + if self.options.has_key(optionname): + del self.options[optionname] + +class SSSDService(SSSDConfigObject): + ''' + Object to manipulate SSSD service options + ''' + + def __init__(self, servicename, apischema): + """ + Create a new SSSDService, setting its defaults to those found in the + schema. This constructor should not be used directly. Use + SSSDConfig.new_service() instead. + + name: + The service name + apischema: + An SSSDConfigSchema? object created by SSSDConfig.__init__() + + === Returns === + The newly-created SSSDService object. + + === Errors === + TypeError: + The API schema passed in was unusable or the name was not a string. + ServiceNotRecognizedError: + The service was not listed in the schema + """ + SSSDConfigObject.__init__(self) + + if not isinstance(apischema, SSSDConfigSchema) or type(servicename) != str: + raise TypeError + + if not apischema.has_section(servicename): + raise ServiceNotRecognizedError(servicename) + + self.name = servicename + self.schema = apischema + + # Set up the service object with any known defaults + self.options = {} + + # Include a list of hidden options + self.hidden_options = [] + + # Set up default options for all services + self.options.update(self.schema.get_defaults('service')) + + # Set up default options for this service + self.options.update(self.schema.get_defaults(self.name)) + + # For the [sssd] service, force the config file version + if servicename == 'sssd': + self.options['config_file_version'] = 2 + self.hidden_options.append('config_file_version') + + def list_options_with_mandatory(self): + """ + List options for the service, including the mandatory flag. + + === Returns === + A dictionary of configurable options. This dictionary is keyed on the + option name with a tuple of the variable type, subtype ('None' if the + type is not a collection type), whether it is mandatory, the + translated option description, and the default value (or 'None') as + the value. + + Example: + { 'enumerate' : + (bool, None, False, u'Enable enumerating all users/groups', True) } + + === Errors === + No errors + """ + options = {} + + # Get the list of available options for all services + schema_options = self.schema.get_options('service') + options.update(schema_options) + + schema_options = self.schema.get_options(self.name) + options.update(schema_options) + + return options + + def list_options(self): + """ + List all options that apply to this service + + === Returns === + A dictionary of configurable options. This dictionary is keyed on the + option name with a tuple of the variable type, subtype ('None' if the + type is not a collection type), the translated option description, and + the default value (or 'None') as the value. + + Example: + { 'services' : + (list, str, u'SSSD Services to start', ['nss', 'pam']) } + + === Errors === + No Errors + """ + options = self.list_options_with_mandatory() + + # Filter out the mandatory field to maintain compatibility + # with older versions of the API + filtered_options = {} + for key in options.keys(): + filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4]) + + return filtered_options + + def list_mandatory_options(self): + """ + List all mandatory options that apply to this service + + === Returns === + A dictionary of configurable options. This dictionary is keyed on the + option name with a tuple of the variable type, subtype ('None' if the + type is not a collection type), the translated option description, and + the default value (or 'None') as the value. + + Example: + { 'services' : + (list, str, u'SSSD Services to start', ['nss', 'pam']) } + + === Errors === + No Errors + """ + options = self.list_options_with_mandatory() + + # Filter out the mandatory field to maintain compatibility + # with older versions of the API + filtered_options = {} + for key in options.keys(): + if options[key][2]: + filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4]) + + return filtered_options + + def set_option(self, optionname, value): + """ + Set a service option to the specified value (or values) + + optionname: + The option to change + value: + The value to set. This may be a single value or a list of values. If + it is set to None, it resets the option to its default. + + === Returns === + No return value + + === Errors === + NoOptionError: + The specified option is not listed in the schema + TypeError: + The value specified was not of the expected type + """ + if self.schema.has_option(self.name, optionname): + option_schema = self.schema.get_option(self.name, optionname) + elif self.schema.has_option('service', optionname): + option_schema = self.schema.get_option('service', optionname) + elif optionname in self.hidden_options: + # Set this option and do not add it to the list of changeable values + self.options[optionname] = value + return + else: + raise NoOptionError('Section [%s] has no option [%s]' % (self.name, optionname)) + + if value == None: + self.remove_option(optionname) + return + + raise_error = False + + # If we were expecting a list and didn't get one, + # Create a list with a single entry. If it's the + # wrong subtype, it will fail below + if option_schema[0] == list and type(value) != list: + if type(value) == str: + value = striplist(value.split(',')) + else: + value = [value] + + if type(value) != option_schema[0]: + # If it's possible to convert it, do so + try: + if option_schema[0] == bool and \ + type(value) == str: + value = self.schema.bool_lookup[value.lower()] + else: + value = option_schema[0](value) + except ValueError: + raise_error = True + except KeyError: + raise_error = True + + if raise_error: + raise TypeError('Expected %s for %s, received %s' % + (option_schema[0], optionname, type(value))) + + if type(value) == list: + # Iterate through the list an ensure that all members + # are of the appropriate subtype + try: + newvalue = [] + for x in value: + if option_schema[1] == bool and \ + type(x) == str: + newvalue.extend([self.schema.bool_lookup[x.lower()]]) + else: + newvalue.extend([option_schema[1](x)]) + except ValueError: + raise_error = True + except KeyError: + raise_error = True + + if raise_error: + raise TypeError('Expected %s' % option_schema[1]) + + value = newvalue + + self.options[optionname] = value + +class SSSDDomain(SSSDConfigObject): + """ + Object to manipulate SSSD domain options + """ + def __init__(self, domainname, apischema): + """ + Creates a new, empty SSSDDomain. This domain is inactive by default. + This constructor should not be used directly. Use + SSSDConfig.new_domain() instead. + + name: + The domain name. + apischema: + An SSSDConfigSchema object created by SSSDConfig.__init__() + + === Returns === + The newly-created SSSDDomain object. + + === Errors === + TypeError: + apischema was not an SSSDConfigSchema object or domainname was not + a string + """ + SSSDConfigObject.__init__(self) + + if not isinstance(apischema, SSSDConfigSchema) or type(domainname) != str: + raise TypeError + + self.name = domainname + self.schema = apischema + self.active = False + self.oldname = None + self.providers = [] + + # Set up the domain object with any known defaults + self.options = {} + + # Set up default options for all domains + self.options.update(self.schema.get_defaults('provider')) + self.options.update(self.schema.get_defaults('domain')) + + def set_active(self, active): + """ + Enable or disable this domain + + active: + Boolean value. If True, this domain will be added to the active + domains list when it is saved. If False, it will be removed from the + active domains list when it is saved. + + === Returns === + No return value + + === Errors === + No errors + """ + self.active = bool(active) + + def list_options_with_mandatory(self): + """ + List options for the currently-configured providers, including the + mandatory flag + + === Returns === + A dictionary of configurable options. This dictionary is keyed on the + option name with a tuple of the variable type, subtype ('None' if the + type is not a collection type), whether it is mandatory, the + translated option description, and the default value (or 'None') as + the value. + + Example: + { 'enumerate' : + (bool, None, False, u'Enable enumerating all users/groups', True) } + + === Errors === + No errors + """ + options = {} + # Get the list of available options for all domains + options.update(self.schema.get_options('provider')) + + options.update(self.schema.get_options('domain')) + + # Candidate for future optimization: will update primary type + # for each subtype + for (provider, providertype) in self.providers: + schema_options = self.schema.get_options('provider/%s' + % provider) + options.update(schema_options) + schema_options = self.schema.get_options('provider/%s/%s' + % (provider, providertype)) + options.update(schema_options) + return options + + def list_options(self): + """ + List options available for the currently-configured providers. + + === Returns === + A dictionary of configurable options. This dictionary is keyed on the + option name with a tuple of the variable type, subtype ('None' if the + type is not a collection type), the translated option description, and + the default value (or 'None') as the value. + + Example: + { 'enumerate' : + (bool, None, u'Enable enumerating all users/groups', True) } + + === Errors === + No errors + """ + options = self.list_options_with_mandatory() + + # Filter out the mandatory field to maintain compatibility + # with older versions of the API + filtered_options = {} + for key in options.keys(): + filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4]) + + return filtered_options + + def list_mandatory_options(self): + """ + List mandatory options for the currently-configured providers. + + === Returns === + A dictionary of configurable options. This dictionary is keyed on the + option name with a tuple of the variable type, subtype ('None' if the + type is not a collection type), the translated option description, and + the default value (or 'None') as the value. + + Example: + { 'enumerate' : + (bool, None, u'Enable enumerating all users/groups', True) } + + === Errors === + No errors + """ + options = self.list_options_with_mandatory() + + # Filter out the mandatory field to maintain compatibility + # with older versions of the API + filtered_options = {} + for key in options.keys(): + if options[key][2]: + filtered_options[key] = (options[key][0], options[key][1], options[key][3], options[key][4]) + + return filtered_options + + def list_provider_options(self, provider, provider_type=None): + """ + If provider_type is specified, list all options applicable to that + target, otherwise list all possible options available for a provider. + + type: + Provider backend type. (e.g. local, ldap, krb5, etc.) + provider_type: + Subtype of the backend type. (e.g. id, auth, access, chpass) + + === Returns === + + A dictionary of configurable options for the specified provider type. + This dictionary is keyed on the option name with a tuple of the + variable type, subtype ('None' if the type is not a collection type), + the translated option description, and the default value (or 'None') + as the value. + + === Errors === + + NoSuchProviderError: + The specified provider is not listed in the schema or plugins + NoSuchProviderSubtypeError: + The specified provider subtype is not listed in the schema + """ + #TODO section checking + + options = self.schema.get_options('provider/%s' % provider) + if(provider_type): + options.update(self.schema.get_options('provider/%s/%s' % + (provider, provider_type))) + else: + # Add options from all provider subtypes + known_providers = self.list_providers() + for provider_type in known_providers[provider]: + options.update(self.list_provider_options(provider, + provider_type)) + return options + + def list_providers(self): + """ + Return a dictionary of providers. + + === Returns === + Returns a dictionary of providers, keyed on the primary type, with the + value being a tuple of the subtypes it supports. + + Example: + { 'ldap' : ('id', 'auth', 'chpass') } + + === Errors === + No Errors + """ + return self.schema.get_providers() + + def set_option(self, option, value): + """ + Set a domain option to the specified value (or values) + + option: + The option to change. + value: + The value to set. This may be a single value or a list of values. + If it is set to None, it resets the option to its default. + + === Returns === + No return value. + + === Errors === + NoOptionError: + The specified option is not listed in the schema + TypeError: + The value specified was not of the expected type + """ + options = self.list_options() + if (option not in options.keys()): + raise NoOptionError('Section [%s] has no option [%s]' % + (self.name, option)) + + if value == None: + self.remove_option(option) + return + + option_schema = options[option] + raise_error = False + + # If we were expecting a list and didn't get one, + # Create a list with a single entry. If it's the + # wrong subtype, it will fail below + if option_schema[0] == list and type(value) != list: + if type(value) == str: + value = striplist(value.split(',')) + else: + value = [value] + + if type(value) != option_schema[0]: + # If it's possible to convert it, do so + try: + if option_schema[0] == bool and \ + type(value) == str: + value = self.schema.bool_lookup[value.lower()] + else: + value = option_schema[0](value) + except ValueError: + raise_error = True + except KeyError: + raise_error = True + + if raise_error: + raise TypeError('Expected %s for %s, received %s' % + (option_schema[0], option, type(value))) + + if type(value) == list: + # Iterate through the list an ensure that all members + # are of the appropriate subtype + try: + newvalue = [] + for x in value: + if option_schema[1] == bool and \ + type(x) == str: + newvalue.extend([self.schema.bool_lookup[x.lower()]]) + else: + newvalue.extend([option_schema[1](x)]) + except ValueError: + raise_error = True + except KeyError: + raise_error = True + + if raise_error: + raise TypeError('Expected %s' % option_schema[1]) + value = newvalue + + # Check whether we're adding a provider entry. + is_provider = option.rfind('_provider') + if (is_provider > 0): + provider = option[:is_provider] + self.add_provider(value, provider) + else: + self.options[option] = value + + def set_name(self, newname): + """ + Change the name of the domain + + newname: + New name for this domain + + === Returns === + No return value. + + === Errors === + TypeError: + newname was not a string + """ + + if type(newname) != str: + raise TypeError + + if not self.oldname: + # Only set the oldname once + self.oldname = self.name + self.name = newname + + def add_provider(self, provider, provider_type): + """ + Add a new provider type to the domain + + type: + Provider backend type. (e.g. local, ldap, krb5, etc.) + subtype: + Subtype of the backend type. (e.g. id, auth, chpass) + + === Returns === + No return value. + + === Errors === + ProviderSubtypeInUse: + Another backend is already providing this subtype + NoSuchProviderError: + The specified provider is not listed in the schema or plugins + NoSuchProviderSubtypeError: + The specified provider subtype is not listed in the schema + """ + # Check that provider and provider_type are valid + configured_providers = self.list_providers() + if provider in configured_providers.keys(): + if provider_type not in configured_providers[provider]: + raise NoSuchProviderSubtypeError(provider_type) + else: + raise NoSuchProviderError + + # Don't add a provider twice + with_this_type = [x for x in self.providers if x[1] == provider_type] + if len(with_this_type) > 1: + # This should never happen! + raise ProviderSubtypeInUse + if len(with_this_type) == 1: + if with_this_type[0][0] != provider: + raise ProviderSubtypeInUse(with_this_type[0][0]) + else: + self.providers.extend([(provider, provider_type)]) + + option_name = '%s_provider' % provider_type + self.options[option_name] = provider + + # Add defaults for this provider + self.options.update(self.schema.get_defaults('provider/%s' % + provider)) + self.options.update(self.schema.get_defaults('provider/%s/%s' % + (provider, + provider_type))) + + def remove_provider(self, provider_type): + """ + Remove a provider from the domain. If the provider is not present, it + is ignored. + + provider_type: + Subtype of the backend type. (e.g. id, auth, chpass) + + === Returns === + No return value. + + === Errors === + No Errors + """ + + provider = None + for (provider, ptype) in self.providers: + if ptype == provider_type: + break + provider = None + + # Check whether the provider_type was found + if not provider: + return + + # Remove any unused options when removing the provider. + options = self.list_provider_options(provider, provider_type) + + # Trim any options that are used by other providers, + # if that provider is in use + for (prov, ptype) in self.providers: + # Ignore the one being removed + if (prov, ptype) == (provider, provider_type): + continue + + provider_options = self.list_provider_options(prov, ptype) + overlap = options_overlap(options.keys(), provider_options.keys()) + for opt in overlap: + del options[opt] + + # We should now have a list of options used only by this + # provider. So we remove them. + for option in options: + if self.options.has_key(option): + del self.options[option] + + self.providers.remove((provider, provider_type)) + +class SSSDConfig(SSSDChangeConf): + """ + class SSSDConfig + Primary class for operating on SSSD configurations + """ + def __init__(self, schemafile=None, schemaplugindir=None): + """ + Initialize the SSSD config parser/editor. This constructor does not + open or create a config file. If the schemafile and schemaplugindir + are not passed, it will use the system defaults. + + schemafile: + The path to the api schema config file. Usually + /etc/sssd/sssd.api.conf + schemaplugindir: + The path the directory containing the provider schema config files. + Usually /etc/sssd/sssd.api.d + + === Returns === + The newly-created SSSDConfig object. + + === Errors === + IOError: + Exception raised when the schema file could not be opened for + reading. + ParsingError: + The main schema file or one of those in the plugin directory could + not be parsed. + """ + SSSDChangeConf.__init__(self) + self.schema = SSSDConfigSchema(schemafile, schemaplugindir) + self.configfile = None + self.initialized = False + self.API_VERSION = 2 + + def import_config(self,configfile=None): + """ + Read in a config file, populating all of the service and domain + objects with the read values. + + configfile: + The path to the SSSD config file. If not specified, use the system + default, usually /etc/sssd/sssd.conf + + === Returns === + No return value + + === Errors === + IOError: + Exception raised when the file could not be opened for reading + ParsingError: + Exception raised when errors occur attempting to parse a file. + AlreadyInitializedError: + This SSSDConfig object was already initialized by a call to + import_config() or new_config() + """ + if self.initialized: + raise AlreadyInitializedError + + if not configfile: + #TODO: get this from a global setting + configfile = '/etc/sssd/sssd.conf' + # open will raise an IOError if it fails + fd = open(configfile, 'r') + + try: + self.readfp(fd) + except: + raise ParsingError + + fd.close() + self.configfile = configfile + self.initialized = True + + try: + if int(self.get('sssd', 'config_file_version')) != self.API_VERSION: + raise ParsingError("Wrong config_file_version") + except: + # Either the 'sssd' section or the 'config_file_version' was not + # present in the config file + raise ParsingError("File contains no config_file_version") + + def new_config(self): + """ + Initialize the SSSDConfig object with the defaults from the schema. + + === Returns === + No return value + + === Errors === + AlreadyInitializedError: + This SSSDConfig object was already initialized by a call to + import_config() or new_config() + """ + if self.initialized: + raise AlreadyInitializedError + + self.initialized = True + + #Initialize all services + for servicename in self.schema.get_services(): + service = self.new_service(servicename) + + def write(self, outputfile=None): + """ + Write out the configuration to a file. + + outputfile: + The path to write the new config file. If it is not specified, it + will use the path specified by the import() call. + === Returns === + No return value + + === Errors === + IOError: + Exception raised when the file could not be opened for writing + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + NoOutputFileError: + No outputfile was specified and this SSSDConfig object was not + initialized by import() + """ + if not self.initialized: + raise NotInitializedError + + if outputfile == None: + if(self.configfile == None): + raise NoOutputFileError + + outputfile = self.configfile + + # open() will raise IOError if it fails + of = open(outputfile, "wb") + output = self.dump(self.opts) + of.write(output) + of.close() + + def list_services(self): + """ + Retrieve a list of known services. + + === Returns === + The list of known services. + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + + service_list = [x['name'] for x in self.sections() + if not x['name'].startswith('domain') ] + return service_list + + def get_service(self, name): + """ + Get an SSSDService object to edit a service. + + name: + The name of the service to return. + + === Returns === + An SSSDService instance containing the current state of a service in + the SSSDConfig + + === Errors === + NoServiceError: + There is no such service with the specified name in the SSSDConfig. + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + if not self.has_section(name): + raise NoServiceError + + service = SSSDService(name, self.schema) + [service.set_option(opt['name'], opt['value']) + for opt in self.strip_comments_empty(self.options(name)) ] + + return service + + def new_service(self, name): + """ + Create a new service from the defaults and return the SSSDService + object for it. This function will also add this service to the list of + active services in the [SSSD] section. + + name: + The name of the service to create and return. + + === Returns === + The newly-created SSSDService object + + === Errors === + ServiceNotRecognizedError: + There is no such service in the schema. + ServiceAlreadyExistsError: + The service being created already exists in the SSSDConfig object. + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + if (self.has_section(name)): + raise ServiceAlreadyExists(name) + + service = SSSDService(name, self.schema) + self.save_service(service) + return service + + def delete_service(self, name): + """ + Remove a service from the SSSDConfig object. This function will also + remove this service from the list of active services in the [SSSD] + section. Has no effect if the service does not exist. + + === Returns === + No return value + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + self.delete_option('section', name) + + def save_service(self, service): + """ + Save the changes made to the service object back to the SSSDConfig + object. + + service_object: + The SSSDService object to save to the configuration. + + === Returns === + No return value + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + TypeError: + service_object was not of the type SSSDService + """ + if not self.initialized: + raise NotInitializedError + if not isinstance(service, SSSDService): + raise TypeError + + name = service.get_name() + # Ensure that the existing section is removed + # This way we ensure that we are getting a + # complete copy of the service. + # delete_option() is a noop if the section + # does not exist. + index = self.delete_option('section', name) + + addkw = [] + for option,value in service.get_all_options().items(): + if (type(value) == list): + value = ', '.join(value) + addkw.append( { 'type' : 'option', + 'name' : option, + 'value' : str(value) } ) + + self.add_section(name, addkw, index) + + def list_active_domains(self): + """ + Return a list of all active domains. + + === Returns === + The list of configured, active domains. + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + + if (self.has_option('sssd', 'domains')): + active_domains = striplist(self.get('sssd', 'domains').split(',')) + domain_dict = dict.fromkeys(active_domains) + if domain_dict.has_key(''): + del domain_dict[''] + + # Remove any entries in this list that don't + # correspond to an active domain, for integrity + configured_domains = self.list_domains() + for dom in domain_dict.keys(): + if dom not in configured_domains: + del domain_dict[dom] + + active_domains = domain_dict.keys() + else: + active_domains = [] + + return active_domains + + def list_inactive_domains(self): + """ + Return a list of all configured, but disabled domains. + + === Returns === + The list of configured, inactive domains. + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + + if (self.has_option('sssd', 'domains')): + active_domains = striplist(self.get('sssd', 'domains').split(',')) + else: + active_domains = [] + + domains = [x for x in self.list_domains() + if x not in active_domains] + return domains + + def list_domains(self): + """ + Return a list of all configured domains, including inactive domains. + + === Returns === + The list of configured domains, both active and inactive. + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + domains = [x['name'][7:] for x in self.sections() if x['name'].startswith('domain/')] + return domains + + def get_domain(self, name): + """ + Get an SSSDDomain object to edit a domain. + + name: + The name of the domain to return. + + === Returns === + An SSSDDomain instance containing the current state of a domain in the + SSSDConfig + + === Errors === + NoDomainError: + There is no such domain with the specified name in the SSSDConfig. + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + if not self.has_section('domain/%s' % name): + raise NoDomainError(name) + + domain = SSSDDomain(name, self.schema) + + # Read in the providers first or we may have type + # errors trying to read in their options + providers = [ (x['name'],x['value']) for x in self.strip_comments_empty(self.options('domain/%s' % name)) + if x['name'].rfind('_provider') > 0] + [domain.set_option(option, value) + for (option, value) in providers] + + [domain.set_option(opt['name'], opt['value']) + for opt in self.strip_comments_empty(self.options('domain/%s' % name)) + if opt not in providers] + + # Determine if this domain is currently active + domain.active = self.is_domain_active(name) + + return domain + + def new_domain(self, name): + """ + Create a new, empty domain and return the SSSDDomain object for it. + + name: + The name of the domain to create and return. + + === Returns === + The newly-created SSSDDomain object + + === Errors === + DomainAlreadyExistsError: + The service being created already exists in the SSSDConfig object. + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + if self.has_section('domain/%s' % name): + raise DomainAlreadyExistsError + + domain = SSSDDomain(name, self.schema) + self.save_domain(domain) + return domain + + def is_domain_active(self, name): + """ + Is a particular domain set active + + name: + The name of the configured domain to check + + === Returns === + True if the domain is active, False if it is inactive + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + NoDomainError: + No domain by this name is configured + """ + + if not self.initialized: + raise NotInitializedError + + if name not in self.list_domains(): + raise NoDomainError + + return name in self.list_active_domains() + + def activate_domain(self, name): + """ + Activate a configured domain + + name: + The name of the configured domain to activate + + === Returns === + No return value + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + NoDomainError: + No domain by this name is configured + """ + + if not self.initialized: + raise NotInitializedError + + if name not in self.list_domains(): + raise NoDomainError + + item = self.get_option_index('sssd', 'domains')[1] + if not item: + self.set('sssd','domains', name) + return + + # Turn the items into a set of dictionary keys + # This guarantees uniqueness and makes it easy + # to add a new value + domain_dict = dict.fromkeys(striplist(item['value'].split(','))) + if domain_dict.has_key(''): + del domain_dict[''] + + # Add a new key for the domain being activated + domain_dict[name] = None + + # Write out the joined keys + self.set('sssd','domains', ", ".join(domain_dict.keys())) + + def deactivate_domain(self, name): + """ + Deactivate a configured domain + + name: + The name of the configured domain to deactivate + + === Returns === + No return value + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + NoDomainError: + No domain by this name is configured + """ + + if not self.initialized: + raise NotInitializedError + + if name not in self.list_domains(): + raise NoDomainError + item = self.get_option_index('sssd', 'domains')[1] + if not item: + self.set('sssd','domains', '') + return + + # Turn the items into a set of dictionary keys + # This guarantees uniqueness and makes it easy + # to remove the one unwanted value. + domain_dict = dict.fromkeys(striplist(item['value'].split(','))) + if domain_dict.has_key(''): + del domain_dict[''] + + # Remove the unwanted domain from the lest + if domain_dict.has_key(name): + del domain_dict[name] + + # Write out the joined keys + self.set('sssd','domains', ", ".join(domain_dict.keys())) + + def delete_domain(self, name): + """ + Remove a domain from the SSSDConfig object. This function will also + remove this domain from the list of active domains in the [SSSD] + section, if it is there. + + === Returns === + No return value + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + """ + if not self.initialized: + raise NotInitializedError + + # Remove the domain from the active domains list if applicable + self.deactivate_domain(name) + self.delete_option('section', 'domain/%s' % name) + + def save_domain(self, domain): + """ + Save the changes made to the domain object back to the SSSDConfig + object. If this domain is marked active, ensure it is present in the + active domain list in the [SSSD] section + + domain_object: + The SSSDDomain object to save to the configuration. + + === Returns === + No return value + + === Errors === + NotInitializedError: + This SSSDConfig object has not had import_config() or new_config() + run on it yet. + TypeError: + domain_object was not of type SSSDDomain + """ + if not self.initialized: + raise NotInitializedError + if not isinstance(domain, SSSDDomain): + raise TypeError + + name = domain.get_name() + + oldindex = None + if domain.oldname and domain.oldname != name: + # We are renaming this domain + # Remove the old section + + self.deactivate_domain(domain.oldname) + oldindex = self.delete_option('section', 'domain/%s' % + domain.oldname) + + # Reset the oldname, in case we're not done with + # this domain object. + domain.oldname = None; + + sectionname = 'domain/%s' % name + # Ensure that the existing section is removed + # This way we ensure that we are getting a + # complete copy of the service. + # delete_option() is a noop if the section + # does not exist. + index = self.delete_option('section', sectionname) + addkw = [] + for option,value in domain.get_all_options().items(): + if (type(value) == list): + value = ', '.join(value) + addkw.append( { 'type' : 'option', + 'name' : option, + 'value' : str(value) } ) + if oldindex: + self.add_section(sectionname, addkw, oldindex) + else: + self.add_section(sectionname, addkw, index) + + if domain.active: + self.activate_domain(name) + else: + self.deactivate_domain(name) diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py new file mode 100755 index 00000000..153146f8 --- /dev/null +++ b/src/config/SSSDConfigTest.py @@ -0,0 +1,1521 @@ +#!/usr/bin/python +''' +Created on Sep 18, 2009 + +@author: sgallagh +''' +import unittest + +import SSSDConfig + +class SSSDConfigTestValid(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def testServices(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf") + + # Validate services + services = sssdconfig.list_services() + self.assertTrue('sssd' in services) + self.assertTrue('nss' in services) + self.assertTrue('pam' in services) + self.assertTrue('dp' in services) + + #Verify service attributes + sssd_service = sssdconfig.get_service('sssd') + service_opts = sssd_service.list_options() + + + self.assertTrue('services' in service_opts.keys()) + service_list = sssd_service.get_option('services') + self.assertTrue('nss' in service_list) + self.assertTrue('pam' in service_list) + + self.assertTrue('domains' in service_opts) + + self.assertTrue('reconnection_retries' in service_opts) + + del sssdconfig + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + sssdconfig.new_config() + sssdconfig.delete_service('sssd') + new_sssd_service = sssdconfig.new_service('sssd'); + new_options = new_sssd_service.list_options(); + + self.assertTrue('debug_level' in new_options) + self.assertEquals(new_options['debug_level'][0], int) + + self.assertTrue('command' in new_options) + self.assertEquals(new_options['command'][0], str) + + self.assertTrue('reconnection_retries' in new_options) + self.assertEquals(new_options['reconnection_retries'][0], int) + + self.assertTrue('services' in new_options) + self.assertEquals(new_options['debug_level'][0], int) + + self.assertTrue('domains' in new_options) + self.assertEquals(new_options['domains'][0], list) + self.assertEquals(new_options['domains'][1], str) + + self.assertTrue('sbus_timeout' in new_options) + self.assertEquals(new_options['sbus_timeout'][0], int) + + self.assertTrue('re_expression' in new_options) + self.assertEquals(new_options['re_expression'][0], str) + + self.assertTrue('full_name_format' in new_options) + self.assertEquals(new_options['full_name_format'][0], str) + + del sssdconfig + + def testDomains(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf") + + #Validate domain list + domains = sssdconfig.list_domains() + self.assertTrue('LOCAL' in domains) + self.assertTrue('LDAP' in domains) + self.assertTrue('PROXY' in domains) + self.assertTrue('IPA' in domains) + + #Verify domain attributes + ipa_domain = sssdconfig.get_domain('IPA') + domain_opts = ipa_domain.list_options() + self.assertTrue('debug_level' in domain_opts.keys()) + self.assertTrue('id_provider' in domain_opts.keys()) + self.assertTrue('auth_provider' in domain_opts.keys()) + + del sssdconfig + + def testListProviders(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + sssdconfig.new_config() + junk_domain = sssdconfig.new_domain('junk') + providers = junk_domain.list_providers() + self.assertTrue('ldap' in providers.keys()) + + def testCreateNewLocalConfig(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + sssdconfig.new_config() + + local_domain = sssdconfig.new_domain('LOCAL') + local_domain.add_provider('local', 'id') + local_domain.set_option('debug_level', 1) + local_domain.set_option('default_shell', '/bin/tcsh') + local_domain.set_active(True) + sssdconfig.save_domain(local_domain) + + sssdconfig.write('/tmp/testCreateNewLocalConfig.conf') + + def testCreateNewLDAPConfig(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + sssdconfig.new_config() + + ldap_domain = sssdconfig.new_domain('LDAP') + ldap_domain.add_provider('ldap', 'id') + ldap_domain.set_option('debug_level', 1) + ldap_domain.set_active(True) + sssdconfig.save_domain(ldap_domain) + + sssdconfig.write('/tmp/testCreateNewLDAPConfig.conf') + + def testModifyExistingConfig(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf") + + ldap_domain = sssdconfig.get_domain('LDAP') + ldap_domain.set_option('debug_level', 3) + + ldap_domain.remove_provider('auth') + ldap_domain.add_provider('krb5', 'auth') + ldap_domain.set_active(True) + sssdconfig.save_domain(ldap_domain) + + sssdconfig.write('/tmp/testModifyExistingConfig.conf') + + def testSpaces(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf") + ldap_domain = sssdconfig.get_domain('LDAP') + self.assertEqual(ldap_domain.get_option('auth_provider'), 'ldap') + self.assertEqual(ldap_domain.get_option('id_provider'), 'ldap') + +class SSSDConfigTestInvalid(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def testBadBool(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + sssdconfig.import_config(srcdir + "/testconfigs/sssd-invalid-badbool.conf") + self.assertRaises(TypeError, + sssdconfig.get_domain,'IPA') + +class SSSDConfigTestSSSDService(unittest.TestCase): + def setUp(self): + self.schema = SSSDConfig.SSSDConfigSchema(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + def tearDown(self): + pass + + def testInit(self): + # Positive test + service = SSSDConfig.SSSDService('sssd', self.schema) + + # Type Error test + # Name is not a string + self.assertRaises(TypeError, SSSDConfig.SSSDService, 3, self.schema) + + # TypeError test + # schema is not an SSSDSchema + self.assertRaises(TypeError, SSSDConfig.SSSDService, '3', self) + + # ServiceNotRecognizedError test + self.assertRaises(SSSDConfig.ServiceNotRecognizedError, + SSSDConfig.SSSDService, 'ssd', self.schema) + + def testListOptions(self): + service = SSSDConfig.SSSDService('sssd', self.schema) + + options = service.list_options() + control_list = [ + 'services', + 'domains', + 'timeout', + 'sbus_timeout', + 're_expression', + 'full_name_format', + 'debug_level', + 'debug_timestamps', + 'debug_to_files', + 'command', + 'reconnection_retries'] + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + self.assertTrue(type(options['reconnection_retries']) == tuple, + "Option values should be a tuple") + + self.assertTrue(options['reconnection_retries'][0] == int, + "reconnection_retries should require an int. " + + "list_options is requiring a %s" % + options['reconnection_retries'][0]) + + self.assertTrue(options['reconnection_retries'][1] == None, + "reconnection_retries should not require a subtype. " + + "list_options is requiring a %s" % + options['reconnection_retries'][1]) + + self.assertTrue(options['reconnection_retries'][3] == None, + "reconnection_retries should have no default") + + self.assertTrue(type(options['services']) == tuple, + "Option values should be a tuple") + + self.assertTrue(options['services'][0] == list, + "services should require an list. " + + "list_options is requiring a %s" % + options['services'][0]) + + self.assertTrue(options['services'][1] == str, + "services should require a subtype of str. " + + "list_options is requiring a %s" % + options['services'][1]) + + def testListMandatoryOptions(self): + service = SSSDConfig.SSSDService('sssd', self.schema) + + options = service.list_mandatory_options() + control_list = [ + 'services', + 'domains'] + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + self.assertTrue(type(options['services']) == tuple, + "Option values should be a tuple") + + self.assertTrue(options['services'][0] == list, + "services should require an list. " + + "list_options is requiring a %s" % + options['services'][0]) + + self.assertTrue(options['services'][1] == str, + "services should require a subtype of str. " + + "list_options is requiring a %s" % + options['services'][1]) + + def testSetOption(self): + service = SSSDConfig.SSSDService('sssd', self.schema) + + # Positive test - Exactly right + service.set_option('debug_level', 2) + self.assertEqual(service.get_option('debug_level'), 2) + + # Positive test - Allow converting "safe" values + service.set_option('debug_level', '2') + self.assertEqual(service.get_option('debug_level'), 2) + + # Positive test - Remove option if value is None + service.set_option('debug_level', None) + self.assertTrue('debug_level' not in service.options.keys()) + + # Negative test - Nonexistent Option + self.assertRaises(SSSDConfig.NoOptionError, service.set_option, 'nosuchoption', 1) + + # Negative test - Incorrect type + self.assertRaises(TypeError, service.set_option, 'debug_level', 'two') + + def testGetOption(self): + service = SSSDConfig.SSSDService('sssd', self.schema) + + # Positive test - Single-valued + self.assertEqual(service.get_option('config_file_version'), 2) + + # Positive test - List of values + self.assertEqual(service.get_option('services'), ['nss', 'pam']) + + # Negative Test - Bad Option + self.assertRaises(SSSDConfig.NoOptionError, service.get_option, 'nosuchoption') + + def testGetAllOptions(self): + service = SSSDConfig.SSSDService('sssd', self.schema) + + #Positive test + options = service.get_all_options() + control_list = [ + 'config_file_version', + 'services'] + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + def testRemoveOption(self): + service = SSSDConfig.SSSDService('sssd', self.schema) + + # Positive test - Remove an option that exists + self.assertEqual(service.get_option('services'), ['nss', 'pam']) + service.remove_option('services') + self.assertRaises(SSSDConfig.NoOptionError, service.get_option, 'debug_level') + + # Positive test - Remove an option that doesn't exist + self.assertRaises(SSSDConfig.NoOptionError, service.get_option, 'nosuchentry') + service.remove_option('nosuchentry') + +class SSSDConfigTestSSSDDomain(unittest.TestCase): + def setUp(self): + self.schema = SSSDConfig.SSSDConfigSchema(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + def tearDown(self): + pass + + def testInit(self): + # Positive Test + domain = SSSDConfig.SSSDDomain('mydomain', self.schema) + + # Negative Test - Name not a string + self.assertRaises(TypeError, SSSDConfig.SSSDDomain, 2, self.schema) + + # Negative Test - Schema is not an SSSDSchema + self.assertRaises(TypeError, SSSDConfig.SSSDDomain, 'mydomain', self) + + def testGetName(self): + # Positive Test + domain = SSSDConfig.SSSDDomain('mydomain', self.schema) + + self.assertEqual(domain.get_name(), 'mydomain') + + def testSetActive(self): + #Positive Test + domain = SSSDConfig.SSSDDomain('mydomain', self.schema) + + # Should default to inactive + self.assertFalse(domain.active) + domain.set_active(True) + self.assertTrue(domain.active) + domain.set_active(False) + self.assertFalse(domain.active) + + def testListOptions(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + # First test default options + options = domain.list_options() + control_list = [ + 'debug_level', + 'debug_timestamps', + 'min_id', + 'max_id', + 'timeout', + 'command', + 'enumerate', + 'cache_credentials', + 'store_legacy_passwords', + 'use_fully_qualified_names', + 'entry_cache_timeout', + 'id_provider', + 'auth_provider', + 'access_provider', + 'chpass_provider'] + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + self.assertTrue(type(options['max_id']) == tuple, + "Option values should be a tuple") + + self.assertTrue(options['max_id'][0] == int, + "max_id should require an int. " + + "list_options is requiring a %s" % + options['max_id'][0]) + + self.assertTrue(options['max_id'][1] == None, + "max_id should not require a subtype. " + + "list_options is requiring a %s" % + options['max_id'][1]) + + # Add a provider and verify that the new options appear + domain.add_provider('local', 'id') + control_list.extend( + ['default_shell', + 'base_directory']) + + options = domain.list_options() + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + # Add a provider that has global options and verify that + # The new options appear. + domain.add_provider('krb5', 'auth') + + backup_list = control_list[:] + control_list.extend( + ['krb5_kdcip', + 'krb5_realm', + 'krb5_ccachedir', + 'krb5_ccname_template', + 'krb5_keytab', + 'krb5_validate', + 'krb5_auth_timeout']) + + options = domain.list_options() + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + # Remove the auth domain and verify that the options + # revert to the backup_list + domain.remove_provider('auth') + options = domain.list_options() + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in backup_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in backup_list, + 'Option [%s] unexpectedly found' % + option) + + def testListMandatoryOptions(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + # First test default options + options = domain.list_mandatory_options() + control_list = [ + 'cache_credentials', + 'min_id', + 'id_provider', + 'auth_provider'] + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + # Add a provider and verify that the new options appear + domain.add_provider('local', 'id') + control_list.extend( + ['default_shell', + 'base_directory']) + + options = domain.list_mandatory_options() + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + # Add a provider that has global options and verify that + # The new options appear. + domain.add_provider('krb5', 'auth') + + backup_list = control_list[:] + control_list.extend( + ['krb5_kdcip', + 'krb5_realm']) + + options = domain.list_mandatory_options() + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + # Remove the auth domain and verify that the options + # revert to the backup_list + domain.remove_provider('auth') + options = domain.list_mandatory_options() + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in backup_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in backup_list, + 'Option [%s] unexpectedly found' % + option) + + def testListProviders(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + control_provider_dict = { + 'ipa': ['id', 'auth', 'access', 'chpass'], + 'local': ['id', 'auth', 'chpass'], + 'ldap': ['id', 'auth', 'chpass'], + 'krb5': ['auth', 'chpass'], + 'proxy': ['id', 'auth'], + 'permit': ['access'], + 'deny': ['access']} + + providers = domain.list_providers() + + # Ensure that all of the expected defaults are there + for provider in control_provider_dict.keys(): + for ptype in control_provider_dict[provider]: + self.assertTrue(providers.has_key(provider)) + self.assertTrue(ptype in providers[provider]) + + for provider in providers.keys(): + for ptype in providers[provider]: + self.assertTrue(control_provider_dict.has_key(provider)) + self.assertTrue(ptype in control_provider_dict[provider]) + + def testListProviderOptions(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + # Test looking up a specific provider type + options = domain.list_provider_options('krb5', 'auth') + control_list = [ + 'krb5_kdcip', + 'krb5_realm', + 'krb5_ccachedir', + 'krb5_ccname_template', + 'krb5_keytab', + 'krb5_validate', + 'krb5_auth_timeout'] + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + #Test looking up all provider values + options = domain.list_provider_options('krb5') + control_list.extend(['krb5_changepw_principal']) + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + def testAddProvider(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + # Positive Test + domain.add_provider('local', 'id') + + # Negative Test - No such backend type + self.assertRaises(SSSDConfig.NoSuchProviderError, + domain.add_provider, 'nosuchbackend', 'auth') + + # Negative Test - No such backend subtype + self.assertRaises(SSSDConfig.NoSuchProviderSubtypeError, + domain.add_provider, 'ldap', 'nosuchsubtype') + + # Negative Test - Try to add a second provider of the same type + self.assertRaises(SSSDConfig.ProviderSubtypeInUse, + domain.add_provider, 'ldap', 'id') + + def testRemoveProvider(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + # First test default options + options = domain.list_options() + control_list = [ + 'debug_level', + 'debug_timestamps', + 'min_id', + 'max_id', + 'timeout', + 'command', + 'enumerate', + 'cache_credentials', + 'store_legacy_passwords', + 'use_fully_qualified_names', + 'entry_cache_timeout', + 'id_provider', + 'auth_provider', + 'access_provider', + 'chpass_provider'] + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + self.assertTrue(type(options['max_id']) == tuple, + "Option values should be a tuple") + + self.assertTrue(options['max_id'][0] == int, + "config_file_version should require an int. " + + "list_options is requiring a %s" % + options['max_id'][0]) + + self.assertTrue(options['max_id'][1] == None, + "config_file_version should not require a subtype. " + + "list_options is requiring a %s" % + options['max_id'][1]) + + # Add a provider and verify that the new options appear + domain.add_provider('local', 'id') + control_list.extend( + ['default_shell', + 'base_directory']) + + options = domain.list_options() + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + # Add a provider that has global options and verify that + # The new options appear. + domain.add_provider('krb5', 'auth') + + backup_list = control_list[:] + control_list.extend( + ['krb5_kdcip', + 'krb5_realm', + 'krb5_ccachedir', + 'krb5_ccname_template', + 'krb5_keytab', + 'krb5_validate', + 'krb5_auth_timeout']) + + options = domain.list_options() + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in control_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in control_list, + 'Option [%s] unexpectedly found' % + option) + + # Remove the local ID provider and add an LDAP one + # LDAP ID providers can also use the krb5_realm + domain.remove_provider('id') + + domain.add_provider('ldap', 'id') + + # Set the krb5_realm option and the ldap_uri option + domain.set_option('krb5_realm', 'EXAMPLE.COM') + domain.set_option('ldap_uri', 'ldap://ldap.example.com') + + self.assertEquals(domain.get_option('krb5_realm'), + 'EXAMPLE.COM') + self.assertEquals(domain.get_option('ldap_uri'), + 'ldap://ldap.example.com') + + # Remove the LDAP provider and verify that krb5_realm remains + domain.remove_provider('id') + self.assertEquals(domain.get_option('krb5_realm'), + 'EXAMPLE.COM') + self.assertFalse(domain.options.has_key('ldap_uri')) + + # Put the LOCAL provider back + domain.add_provider('local', 'id') + + # Remove the auth domain and verify that the options + # revert to the backup_list + domain.remove_provider('auth') + options = domain.list_options() + + self.assertTrue(type(options) == dict, + "Options should be a dictionary") + + # Ensure that all of the expected defaults are there + for option in backup_list: + self.assertTrue(option in options.keys(), + "Option [%s] missing" % + option) + + # Ensure that there aren't any unexpected options listed + for option in options.keys(): + self.assertTrue(option in backup_list, + 'Option [%s] unexpectedly found' % + option) + + # Ensure that the krb5_realm option is now gone + self.assertFalse(domain.options.has_key('krb5_realm')) + + # Test removing nonexistent provider - Real + domain.remove_provider('id') + + # Test removing nonexistent provider - Bad backend type + # Should pass without complaint + domain.remove_provider('id') + + # Test removing nonexistent provider - Bad provider type + # Should pass without complaint + domain.remove_provider('nosuchprovider') + + def testGetOption(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + # Positive Test - Ensure that we can get a valid option + self.assertEqual(domain.get_option('debug_level'), 0) + + # Negative Test - Try to get valid option that is not set + self.assertRaises(SSSDConfig.NoOptionError, domain.get_option, 'max_id') + + # Positive Test - Set the above option and get it + domain.set_option('max_id', 10000) + self.assertEqual(domain.get_option('max_id'), 10000) + + # Negative Test - Try yo get invalid option + self.assertRaises(SSSDConfig.NoOptionError, domain.get_option, 'nosuchoption') + + def testSetOption(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + # Positive Test + domain.set_option('max_id', 10000) + self.assertEqual(domain.get_option('max_id'), 10000) + + # Positive Test - Remove option if value is None + domain.set_option('max_id', None) + self.assertTrue('max_id' not in domain.get_all_options().keys()) + + # Negative Test - invalid option + self.assertRaises(SSSDConfig.NoOptionError, domain.set_option, 'nosuchoption', 1) + + # Negative Test - incorrect type + self.assertRaises(TypeError, domain.set_option, 'max_id', 'a string') + + # Positive Test - Coax options to appropriate type + domain.set_option('max_id', '10000') + self.assertEqual(domain.get_option('max_id'), 10000) + + domain.set_option('max_id', 30.2) + self.assertEqual(domain.get_option('max_id'), 30) + + def testRemoveOption(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + # Positive test - Remove existing option + self.assertTrue('min_id' in domain.get_all_options().keys()) + domain.remove_option('min_id') + self.assertFalse('min_id' in domain.get_all_options().keys()) + + # Positive test - Remove unset but valid option + self.assertFalse('max_id' in domain.get_all_options().keys()) + domain.remove_option('max_id') + self.assertFalse('max_id' in domain.get_all_options().keys()) + + # Positive test - Remove unset and unknown option + self.assertFalse('nosuchoption' in domain.get_all_options().keys()) + domain.remove_option('nosuchoption') + self.assertFalse('nosuchoption' in domain.get_all_options().keys()) + + def testSetName(self): + domain = SSSDConfig.SSSDDomain('sssd', self.schema) + + # Positive test - Change the name once + domain.set_name('sssd2'); + self.assertEqual(domain.get_name(), 'sssd2') + self.assertEqual(domain.oldname, 'sssd') + + # Positive test - Change the name a second time + domain.set_name('sssd3') + self.assertEqual(domain.get_name(), 'sssd3') + self.assertEqual(domain.oldname, 'sssd') + + # Negative test - try setting the name to a non-string + self.assertRaises(TypeError, + domain.set_name, 4) + +class SSSDConfigTestSSSDConfig(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def testInit(self): + # Positive test + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - No Such File + self.assertRaises(IOError, + SSSDConfig.SSSDConfig, "nosuchfile.api.conf", srcdir + "/etc/sssd.api.d") + + # Negative Test - Schema is not parsable + self.assertRaises(SSSDConfig.ParsingError, + SSSDConfig.SSSDConfig, srcdir + "/testconfigs/noparse.api.conf", srcdir + "/etc/sssd.api.d") + + def testImportConfig(self): + # Positive Test + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf") + + # Verify that all sections were imported + control_list = [ + 'sssd', + 'nss', + 'pam', + 'dp', + 'domain/PROXY', + 'domain/IPA', + 'domain/LOCAL', + 'domain/LDAP', + ] + + for section in control_list: + self.assertTrue(sssdconfig.has_section(section), + "Section [%s] missing" % + section) + for section in sssdconfig.sections(): + self.assertTrue(section['name'] in control_list) + + # Verify that all options were imported for a section + control_list = [ + 'services', + 'reconnection_retries', + 'domains', + 'debug_timestamps', + 'config_file_version'] + + for option in control_list: + self.assertTrue(sssdconfig.has_option('sssd', option), + "Option [%s] missing from [sssd]" % + option) + for option in sssdconfig.options('sssd'): + if option['type'] in ('empty', 'comment'): + continue + self.assertTrue(option['name'] in control_list, + "Option [%s] unexpectedly found" % + option) + + #TODO: Check the types and values of the settings + + # Negative Test - Missing config file + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + self.assertRaises(IOError, sssdconfig.import_config, "nosuchfile.conf") + + # Negative Test - Invalid config file + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + self.assertRaises(SSSDConfig.ParsingError, sssdconfig.import_config, srcdir + "/testconfigs/sssd-invalid.conf") + + # Negative Test - Invalid config file version + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + self.assertRaises(SSSDConfig.ParsingError, sssdconfig.import_config, srcdir + "/testconfigs/sssd-badversion.conf") + + # Negative Test - No config file version + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + self.assertRaises(SSSDConfig.ParsingError, sssdconfig.import_config, srcdir + "/testconfigs/sssd-noversion.conf") + + # Negative Test - Already initialized + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf") + self.assertRaises(SSSDConfig.AlreadyInitializedError, + sssdconfig.import_config, srcdir + "/testconfigs/sssd-valid.conf") + + def testNewConfig(self): + # Positive Test + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + sssdconfig.new_config() + + # Check that the defaults were set + control_list = [ + 'sssd', + 'nss', + 'pam'] + for section in control_list: + self.assertTrue(sssdconfig.has_section(section), + "Section [%s] missing" % + section) + for section in sssdconfig.sections(): + self.assertTrue(section['name'] in control_list) + + control_list = [ + 'config_file_version', + 'services'] + for option in control_list: + self.assertTrue(sssdconfig.has_option('sssd', option), + "Option [%s] missing from [sssd]" % + option) + for option in sssdconfig.options('sssd'): + if option['type'] in ('empty', 'comment'): + continue + self.assertTrue(option['name'] in control_list, + "Option [%s] unexpectedly found" % + option) + + # Negative Test - Already Initialized + self.assertRaises(SSSDConfig.AlreadyInitializedError, sssdconfig.new_config) + + def testWrite(self): + #TODO Write tests to compare output files + pass + + def testListServices(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - sssdconfig not initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_services) + + sssdconfig.new_config() + + control_list = [ + 'sssd', + 'pam', + 'nss'] + service_list = sssdconfig.list_services() + for service in control_list: + self.assertTrue(service in service_list, + "Service [%s] missing" % + service) + for service in service_list: + self.assertTrue(service in control_list, + "Service [%s] unexpectedly found" % + service) + + def testGetService(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.get_service, 'sssd') + + sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf') + + service = sssdconfig.get_service('sssd') + self.assertTrue(isinstance(service, SSSDConfig.SSSDService)) + + # Verify the contents of this service + self.assertEqual(type(service.get_option('debug_timestamps')), bool) + self.assertFalse(service.get_option('debug_timestamps')) + + # Negative Test - No such service + self.assertRaises(SSSDConfig.NoServiceError, sssdconfig.get_service, 'nosuchservice') + + def testNewService(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.new_service, 'sssd') + + sssdconfig.new_config() + + # Positive Test + # First need to remove the existing service + sssdconfig.delete_service('sssd') + service = sssdconfig.new_service('sssd') + self.failUnless(service.get_name() in sssdconfig.list_services()) + + # TODO: check that the values of this new service + # are set to the defaults from the schema + + def testDeleteService(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.delete_service, 'sssd') + + sssdconfig.new_config() + + # Positive Test + service = sssdconfig.delete_service('sssd') + + def testSaveService(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + new_service = SSSDConfig.SSSDService('sssd', sssdconfig.schema) + + # Negative Test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.save_service, new_service) + + # Positive Test + sssdconfig.new_config() + sssdconfig.save_service(new_service) + + # TODO: check that all entries were saved correctly (change a few) + + # Negative Test - Type Error + self.assertRaises(TypeError, sssdconfig.save_service, self) + + def testListActiveDomains(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - Not Initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_active_domains) + + # Positive Test + sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf') + + control_list = [ + 'IPA', + 'LOCAL'] + active_domains = sssdconfig.list_active_domains() + + for domain in control_list: + self.assertTrue(domain in active_domains, + "Domain [%s] missing" % + domain) + for domain in active_domains: + self.assertTrue(domain in control_list, + "Domain [%s] unexpectedly found" % + domain) + + def testListInactiveDomains(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - Not Initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_inactive_domains) + + # Positive Test + sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf') + + control_list = [ + 'PROXY', + 'LDAP'] + inactive_domains = sssdconfig.list_inactive_domains() + + for domain in control_list: + self.assertTrue(domain in inactive_domains, + "Domain [%s] missing" % + domain) + for domain in inactive_domains: + self.assertTrue(domain in control_list, + "Domain [%s] unexpectedly found" % + domain) + + def testListDomains(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - Not Initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.list_domains) + + # Positive Test + sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf') + + control_list = [ + 'IPA', + 'LOCAL', + 'PROXY', + 'LDAP'] + domains = sssdconfig.list_domains() + + for domain in control_list: + self.assertTrue(domain in domains, + "Domain [%s] missing" % + domain) + for domain in domains: + self.assertTrue(domain in control_list, + "Domain [%s] unexpectedly found" % + domain) + + def testGetDomain(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.get_domain, 'sssd') + + sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf') + + domain = sssdconfig.get_domain('IPA') + self.assertTrue(isinstance(domain, SSSDConfig.SSSDDomain)) + self.assertTrue(domain.active) + + # TODO verify the contents of this domain + + # Negative Test - No such domain + self.assertRaises(SSSDConfig.NoDomainError, sssdconfig.get_domain, 'nosuchdomain') + + def testNewDomain(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.new_domain, 'example.com') + + sssdconfig.new_config() + + # Positive Test + domain = sssdconfig.new_domain('example.com') + self.assertTrue(isinstance(domain, SSSDConfig.SSSDDomain)) + self.failUnless(domain.get_name() in sssdconfig.list_domains()) + self.failUnless(domain.get_name() in sssdconfig.list_inactive_domains()) + + # TODO: check that the values of this new domain + # are set to the defaults from the schema + + def testDeleteDomain(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + # Negative Test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.delete_domain, 'IPA') + + # Positive Test + sssdconfig.import_config(srcdir + '/testconfigs/sssd-valid.conf') + + self.assertTrue('IPA' in sssdconfig.list_domains()) + self.assertTrue('IPA' in sssdconfig.list_active_domains()) + self.assertTrue(sssdconfig.has_section('domain/IPA')) + sssdconfig.delete_domain('IPA') + self.assertFalse('IPA' in sssdconfig.list_domains()) + self.assertFalse('IPA' in sssdconfig.list_active_domains()) + self.assertFalse(sssdconfig.has_section('domain/IPA')) + + def testSaveDomain(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + # Negative Test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, sssdconfig.save_domain, 'IPA') + + # Positive Test + sssdconfig.new_config() + domain = sssdconfig.new_domain('example.com') + domain.add_provider('ldap', 'id') + domain.set_option('ldap_uri', 'ldap://ldap.example.com') + domain.set_active(True) + sssdconfig.save_domain(domain) + + self.assertTrue('example.com' in sssdconfig.list_domains()) + self.assertTrue('example.com' in sssdconfig.list_active_domains()) + self.assertEqual(sssdconfig.get('domain/example.com', 'ldap_uri'), + 'ldap://ldap.example.com') + + # Negative Test - Type Error + self.assertRaises(TypeError, sssdconfig.save_domain, self) + + # Positive test - Change the domain name and save it + domain.set_name('example.com2') + self.assertEqual(domain.name,'example.com2') + self.assertEqual(domain.oldname,'example.com') + sssdconfig.save_domain(domain) + + self.assertTrue('example.com2' in sssdconfig.list_domains()) + self.assertTrue('example.com2' in sssdconfig.list_active_domains()) + self.assertTrue(sssdconfig.has_section('domain/example.com2')) + self.assertEqual(sssdconfig.get('domain/example.com2', + 'ldap_uri'), + 'ldap://ldap.example.com') + self.assertFalse('example.com' in sssdconfig.list_domains()) + self.assertFalse('example.com' in sssdconfig.list_active_domains()) + self.assertFalse('example.com' in sssdconfig.list_inactive_domains()) + self.assertFalse(sssdconfig.has_section('domain/example.com')) + self.assertEquals(domain.oldname, None) + + # Positive test - Set the domain inactive and save it + activelist = sssdconfig.list_active_domains() + inactivelist = sssdconfig.list_inactive_domains() + + domain.set_active(False) + sssdconfig.save_domain(domain) + + self.assertFalse('example.com2' in sssdconfig.list_active_domains()) + self.assertTrue('example.com2' in sssdconfig.list_inactive_domains()) + + self.assertEquals(len(sssdconfig.list_active_domains()), + len(activelist)-1) + self.assertEquals(len(sssdconfig.list_inactive_domains()), + len(inactivelist)+1) + + # Positive test - Set the domain active and save it + activelist = sssdconfig.list_active_domains() + inactivelist = sssdconfig.list_inactive_domains() + domain.set_active(True) + sssdconfig.save_domain(domain) + + self.assertTrue('example.com2' in sssdconfig.list_active_domains()) + self.assertFalse('example.com2' in sssdconfig.list_inactive_domains()) + + self.assertEquals(len(sssdconfig.list_active_domains()), + len(activelist)+1) + self.assertEquals(len(sssdconfig.list_inactive_domains()), + len(inactivelist)-1) + + # Positive test - Set the domain inactive and save it + activelist = sssdconfig.list_active_domains() + inactivelist = sssdconfig.list_inactive_domains() + + sssdconfig.deactivate_domain(domain.get_name()) + + self.assertFalse('example.com2' in sssdconfig.list_active_domains()) + self.assertTrue('example.com2' in sssdconfig.list_inactive_domains()) + + self.assertEquals(len(sssdconfig.list_active_domains()), + len(activelist)-1) + self.assertEquals(len(sssdconfig.list_inactive_domains()), + len(inactivelist)+1) + + # Positive test - Set the domain active and save it + activelist = sssdconfig.list_active_domains() + inactivelist = sssdconfig.list_inactive_domains() + + sssdconfig.activate_domain(domain.get_name()) + + self.assertTrue('example.com2' in sssdconfig.list_active_domains()) + self.assertFalse('example.com2' in sssdconfig.list_inactive_domains()) + + self.assertEquals(len(sssdconfig.list_active_domains()), + len(activelist)+1) + self.assertEquals(len(sssdconfig.list_inactive_domains()), + len(inactivelist)-1) + + # Positive test - Ensure that saved domains retain values + domain.set_option('ldap_krb5_init_creds', True) + domain.set_option('ldap_id_use_start_tls', False) + domain.set_option('ldap_user_search_base', + 'cn=accounts, dc=example, dc=com') + self.assertTrue(domain.get_option('ldap_krb5_init_creds')) + self.assertFalse(domain.get_option('ldap_id_use_start_tls')) + self.assertEqual(domain.get_option('ldap_user_search_base'), + 'cn=accounts, dc=example, dc=com') + + sssdconfig.save_domain(domain) + sssdconfig.write('/tmp/testSaveDomain.out') + + domain2 = sssdconfig.get_domain('example.com2') + self.assertTrue(domain2.get_option('ldap_krb5_init_creds')) + self.assertFalse(domain2.get_option('ldap_id_use_start_tls')) + + def testActivateDomain(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + domain_name = 'PROXY' + + # Negative test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, + sssdconfig.activate_domain, domain_name) + + sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf") + + # Positive test - Activate an inactive domain + self.assertTrue(domain_name in sssdconfig.list_domains()) + self.assertFalse(domain_name in sssdconfig.list_active_domains()) + self.assertTrue(domain_name in sssdconfig.list_inactive_domains()) + + sssdconfig.activate_domain('PROXY') + self.assertTrue(domain_name in sssdconfig.list_domains()) + self.assertTrue(domain_name in sssdconfig.list_active_domains()) + self.assertFalse(domain_name in sssdconfig.list_inactive_domains()) + + # Positive test - Activate an active domain + # This should succeed + sssdconfig.activate_domain('PROXY') + self.assertTrue(domain_name in sssdconfig.list_domains()) + self.assertTrue(domain_name in sssdconfig.list_active_domains()) + self.assertFalse(domain_name in sssdconfig.list_inactive_domains()) + + # Negative test - Invalid domain name + self.assertRaises(SSSDConfig.NoDomainError, + sssdconfig.activate_domain, 'nosuchdomain') + + # Negative test - Invalid domain name type + self.assertRaises(SSSDConfig.NoDomainError, + sssdconfig.activate_domain, self) + + def testDeactivateDomain(self): + sssdconfig = SSSDConfig.SSSDConfig(srcdir + "/etc/sssd.api.conf", + srcdir + "/etc/sssd.api.d") + + domain_name = 'IPA' + + # Negative test - Not initialized + self.assertRaises(SSSDConfig.NotInitializedError, + sssdconfig.activate_domain, domain_name) + + sssdconfig.import_config(srcdir + "/testconfigs/sssd-valid.conf") + + # Positive test -Deactivate an active domain + self.assertTrue(domain_name in sssdconfig.list_domains()) + self.assertTrue(domain_name in sssdconfig.list_active_domains()) + self.assertFalse(domain_name in sssdconfig.list_inactive_domains()) + + sssdconfig.deactivate_domain(domain_name) + self.assertTrue(domain_name in sssdconfig.list_domains()) + self.assertFalse(domain_name in sssdconfig.list_active_domains()) + self.assertTrue(domain_name in sssdconfig.list_inactive_domains()) + + # Positive test - Deactivate an inactive domain + # This should succeed + sssdconfig.deactivate_domain(domain_name) + self.assertTrue(domain_name in sssdconfig.list_domains()) + self.assertFalse(domain_name in sssdconfig.list_active_domains()) + self.assertTrue(domain_name in sssdconfig.list_inactive_domains()) + + # Negative test - Invalid domain name + self.assertRaises(SSSDConfig.NoDomainError, + sssdconfig.activate_domain, 'nosuchdomain') + + # Negative test - Invalid domain name type + self.assertRaises(SSSDConfig.NoDomainError, + sssdconfig.activate_domain, self) + +if __name__ == "__main__": + error = 0 + + import os + import sys + srcdir = os.getenv('srcdir') + if srcdir: + srcdir = srcdir + "/config" + else: + srcdir = "." + + suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestSSSDService) + res = unittest.TextTestRunner().run(suite) + if not res.wasSuccessful(): + error |= 0x1 + + suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestSSSDDomain) + res = unittest.TextTestRunner().run(suite) + if not res.wasSuccessful(): + error |= 0x2 + + suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestSSSDConfig) + res = unittest.TextTestRunner().run(suite) + if not res.wasSuccessful(): + error |= 0x4 + + suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestValid) + res = unittest.TextTestRunner().run(suite) + if not res.wasSuccessful(): + error |= 0x8 + + suite = unittest.TestLoader().loadTestsFromTestCase(SSSDConfigTestInvalid) + res = unittest.TextTestRunner().run(suite) + if not res.wasSuccessful(): + error |= 0x10 + + sys.exit(error) diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf new file mode 100644 index 00000000..19053538 --- /dev/null +++ b/src/config/etc/sssd.api.conf @@ -0,0 +1,66 @@ +# Format: +# option = type, subtype, mandatory[, default] + +[service] +# Options available to all services +debug_level = int, None, false +debug_timestamps = bool, None, false +debug_to_files = bool, None, false +command = str, None, false +reconnection_retries = int, None, false + +[sssd] +# Monitor service +services = list, str, true, nss, pam +domains = list, str, true +timeout = int, None, false +sbus_timeout = int, None, false +re_expression = str, None, false +full_name_format = str, None, false + +[nss] +# Name service +enum_cache_timeout = int, None, false +entry_cache_no_wait_percentage = int, None, false +entry_negative_timeout = int, None, false +filter_users = list, str, false +filter_groups = list, str, false +filter_users_in_groups = bool, None, false +pwfield = str, None, false + +[pam] +# Authentication service +offline_credentials_expiration = int, None, false +offline_failed_login_attempts = int, None, false +offline_failed_login_delay = int, None, false + +[provider] +#Available provider types +id_provider = str, None, true +auth_provider = str, None, true +access_provider = str, None, false +chpass_provider = str, None, false + +[domain] +# Options available to all domains +debug_level = int, None, false, 0 +debug_timestamps = bool, None, false +command = str, None, false +min_id = int, None, true, 1000 +max_id = int, None, false +timeout = int, None, false +enumerate = bool, None, false +cache_credentials = bool, None, true, false +store_legacy_passwords = bool, None, false +use_fully_qualified_names = bool, None, false +entry_cache_timeout = int, None, false + +# Special providers +[provider/permit] + +[provider/permit/access] + +[provider/deny] + +[provider/deny/access] + diff --git a/src/config/etc/sssd.api.d/sssd-ipa.conf b/src/config/etc/sssd.api.d/sssd-ipa.conf new file mode 100644 index 00000000..c2a12d5a --- /dev/null +++ b/src/config/etc/sssd.api.d/sssd-ipa.conf @@ -0,0 +1,77 @@ +[provider/ipa] +ipa_domain = str, None, true +ipa_server = str, None, true +ipa_hostname = str, None, false +ldap_uri = str, None, false +ldap_search_base = str, None, false +ldap_schema = str, None, false +ldap_default_bind_dn = str, None, false +ldap_default_authtok_type = str, None, false +ldap_default_authtok = str, None, false +ldap_network_timeout = int, None, false +ldap_opt_timeout = int, None, false +ldap_offline_timeout = int, None, false +ldap_tls_cacert = str, None, false +ldap_tls_reqcert = str, None, false +ldap_sasl_mech = str, None, false +ldap_sasl_authid = str, None, false +krb5_kdcip = str, None, false +krb5_realm = str, None, false +krb5_auth_timeout = int, None, false +ldap_krb5_keytab = str, None, false +ldap_krb5_init_creds = bool, None, false +ldap_entry_usn = str, None, false +ldap_rootdse_last_usn = str, None, false +ldap_referrals = bool, None, false + +[provider/ipa/id] +ldap_search_timeout = int, None, false +ldap_enumeration_refresh_timeout = int, None, false +ldap_purge_cache_timeout = int, None, false +ldap_id_use_start_tls = bool, None, false +ldap_user_search_base = str, None, false +ldap_user_search_scope = str, None, false +ldap_user_search_filter = str, None, false +ldap_user_object_class = str, None, false +ldap_user_name = str, None, false +ldap_user_uid_number = str, None, false +ldap_user_gid_number = str, None, false +ldap_user_gecos = str, None, false +ldap_user_homedir = str, None, false +ldap_user_shell = str, None, false +ldap_user_uuid = str, None, false +ldap_user_principal = str, None, false +ldap_user_fullname = str, None, false +ldap_user_member_of = str, None, false +ldap_user_modify_timestamp = str, None, false +ldap_user_shadow_last_change = str, None, false +ldap_user_shadow_min = str, None, false +ldap_user_shadow_max = str, None, false +ldap_user_shadow_warning = str, None, false +ldap_user_shadow_inactive = str, None, false +ldap_user_shadow_expire = str, None, false +ldap_user_shadow_flag = str, None, false +ldap_user_krb_last_pwd_change = str, None, false +ldap_user_krb_password_expiration = str, None, false +ldap_pwd_attribute = str, None, false +ldap_group_search_base = str, None, false +ldap_group_search_scope = str, None, false +ldap_group_search_filter = str, None, false +ldap_group_object_class = str, None, false +ldap_group_name = str, None, false +ldap_group_gid_number = str, None, false +ldap_group_member = str, None, false +ldap_group_uuid = str, None, false +ldap_group_modify_timestamp = str, None, false +ldap_force_upper_case_realm = bool, None, false + +[provider/ipa/auth] +krb5_ccachedir = str, None, false +krb5_ccname_template = str, None, false +krb5_keytab = str, None, false +krb5_validate = bool, None, false + +[provider/ipa/access] + +[provider/ipa/chpass] +krb5_changepw_principal = str, None, false diff --git a/src/config/etc/sssd.api.d/sssd-krb5.conf b/src/config/etc/sssd.api.d/sssd-krb5.conf new file mode 100644 index 00000000..7ba0ab32 --- /dev/null +++ b/src/config/etc/sssd.api.d/sssd-krb5.conf @@ -0,0 +1,13 @@ +[provider/krb5] +krb5_kdcip = str, None, true +krb5_realm = str, None, true +krb5_auth_timeout = int, None, false + +[provider/krb5/auth] +krb5_ccachedir = str, None, false +krb5_ccname_template = str, None, false +krb5_keytab = str, None, false +krb5_validate = bool, None, false + +[provider/krb5/chpass] +krb5_changepw_principal = str, None, false diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf new file mode 100644 index 00000000..6758ab49 --- /dev/null +++ b/src/config/etc/sssd.api.d/sssd-ldap.conf @@ -0,0 +1,68 @@ +[provider/ldap] +ldap_uri = str, None, true +ldap_search_base = str, None, true +ldap_schema = str, None, true, rfc2307 +ldap_default_bind_dn = str, None, false +ldap_default_authtok_type = str, None, false +ldap_default_authtok = str, None, false +ldap_network_timeout = int, None, false +ldap_opt_timeout = int, None, false +ldap_offline_timeout = int, None, false +ldap_tls_cacert = str, None, false +ldap_tls_reqcert = str, None, false +ldap_sasl_mech = str, None, false +ldap_sasl_authid = str, None, false +krb5_kdcip = str, None, false +krb5_realm = str, None, false +ldap_krb5_keytab = str, None, false +ldap_krb5_init_creds = bool, None, false +ldap_entry_usn = str, None, false +ldap_rootdse_last_usn = str, None, false +ldap_referrals = bool, None, false + +[provider/ldap/id] +ldap_search_timeout = int, None, false +ldap_enumeration_refresh_timeout = int, None, false +ldap_purge_cache_timeout = int, None, false +ldap_id_use_start_tls = bool, None, true, false +ldap_user_search_base = str, None, false +ldap_user_search_scope = str, None, false +ldap_user_search_filter = str, None, false +ldap_user_object_class = str, None, false +ldap_user_name = str, None, false +ldap_user_uid_number = str, None, false +ldap_user_gid_number = str, None, false +ldap_user_gecos = str, None, false +ldap_user_homedir = str, None, false +ldap_user_shell = str, None, false +ldap_user_uuid = str, None, false +ldap_user_principal = str, None, false +ldap_user_fullname = str, None, false +ldap_user_member_of = str, None, false +ldap_user_modify_timestamp = str, None, false +ldap_user_shadow_last_change = str, None, false +ldap_user_shadow_min = str, None, false +ldap_user_shadow_max = str, None, false +ldap_user_shadow_warning = str, None, false +ldap_user_shadow_inactive = str, None, false +ldap_user_shadow_expire = str, None, false +ldap_user_shadow_flag = str, None, false +ldap_user_krb_last_pwd_change = str, None, false +ldap_user_krb_password_expiration = str, None, false +ldap_pwd_attribute = str, None, false +ldap_group_search_base = str, None, false +ldap_group_search_scope = str, None, false +ldap_group_search_filter = str, None, false +ldap_group_object_class = str, None, false +ldap_group_name = str, None, false +ldap_group_gid_number = str, None, false +ldap_group_member = str, None, false +ldap_group_uuid = str, None, false +ldap_group_modify_timestamp = str, None, false +ldap_force_upper_case_realm = bool, None, false + +[provider/ldap/auth] +ldap_pwd_policy = str, None, false + +[provider/ldap/chpass] + diff --git a/src/config/etc/sssd.api.d/sssd-local.conf b/src/config/etc/sssd.api.d/sssd-local.conf new file mode 100644 index 00000000..0686f082 --- /dev/null +++ b/src/config/etc/sssd.api.d/sssd-local.conf @@ -0,0 +1,10 @@ +[provider/local] + +[provider/local/id] +default_shell = str, None, true, /bin/bash +base_directory = str, None, true, /home + +[provider/local/auth] + +[provider/local/chpass] + diff --git a/src/config/etc/sssd.api.d/sssd-proxy.conf b/src/config/etc/sssd.api.d/sssd-proxy.conf new file mode 100644 index 00000000..7ecf6b33 --- /dev/null +++ b/src/config/etc/sssd.api.d/sssd-proxy.conf @@ -0,0 +1,7 @@ +[provider/proxy] + +[provider/proxy/id] +proxy_lib_name = str, None, true + +[provider/proxy/auth] +proxy_pam_target = str, None, true diff --git a/src/config/ipachangeconf.py b/src/config/ipachangeconf.py new file mode 100644 index 00000000..ea73a9b9 --- /dev/null +++ b/src/config/ipachangeconf.py @@ -0,0 +1,588 @@ +# +# ipachangeconf - configuration file manipulation classes and functions +# partially based on authconfig code +# Copyright (c) 1999-2007 Red Hat, Inc. +# Author: Simo Sorce +# +# This 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import fcntl +import os +import string +import time +import shutil +import re + +def openLocked(filename, perms, create = True): + fd = -1 + + flags = os.O_RDWR + if create: + flags = flags | os.O_CREAT + + try: + fd = os.open(filename, flags, perms) + fcntl.lockf(fd, fcntl.LOCK_EX) + except OSError, (errno, strerr): + if fd != -1: + try: + os.close(fd) + except OSError: + pass + raise IOError(errno, strerr) + return os.fdopen(fd, "r+") + + + #TODO: add subsection as a concept + # (ex. REALM.NAME = { foo = x bar = y } ) + #TODO: put section delimiters as separating element of the list + # so that we can process multiple sections in one go + #TODO: add a comment all but provided options as a section option +class IPAChangeConf: + + def __init__(self, name): + self.progname = name + self.indent = ("","","") + self.assign = (" = ","=") + self.dassign = self.assign[0] + self.comment = ("#",) + self.dcomment = self.comment[0] + self.eol = ("\n",) + self.deol = self.eol[0] + self.sectnamdel = ("[","]") + self.subsectdel = ("{","}") + self.backup_suffix = ".ipabkp" + + def setProgName(self, name): + self.progname = name + + def setIndent(self, indent): + if type(indent) is tuple: + self.indent = indent + elif type(indent) is str: + self.indent = (indent, ) + else: + raise ValueError, 'Indent must be a list of strings' + + def setOptionAssignment(self, assign): + if type(assign) is tuple: + self.assign = assign + else: + self.assign = (assign, ) + self.dassign = self.assign[0] + + def setCommentPrefix(self, comment): + if type(comment) is tuple: + self.comment = comment + else: + self.comment = (comment, ) + self.dcomment = self.comment[0] + + def setEndLine(self, eol): + if type(eol) is tuple: + self.eol = eol + else: + self.eol = (eol, ) + self.deol = self.eol[0] + + def setSectionNameDelimiters(self, delims): + self.sectnamdel = delims + + def setSubSectionDelimiters(self, delims): + self.subsectdel = delims + + def matchComment(self, line): + for v in self.comment: + if line.lstrip().startswith(v): + return line.lstrip()[len(v):] + return False + + def matchEmpty(self, line): + if line.strip() == "": + return True + return False + + def matchSection(self, line): + cl = "".join(line.strip().split()) + if len(self.sectnamdel) != 2: + return False + if not cl.startswith(self.sectnamdel[0]): + return False + if not cl.endswith(self.sectnamdel[1]): + return False + return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])] + + def matchSubSection(self, line): + if self.matchComment(line): + return False + + parts = line.split(self.dassign, 1) + if len(parts) < 2: + return False + + if parts[1].strip() == self.subsectdel[0]: + return parts[0].strip() + + return False + + def matchSubSectionEnd(self, line): + if self.matchComment(line): + return False + + if line.strip() == self.subsectdel[1]: + return True + + return False + + def getSectionLine(self, section): + if len(self.sectnamdel) != 2: + return section + return self.sectnamdel[0]+section+self.sectnamdel[1]+self.deol + + def dump(self, options, level=0): + output = "" + if level >= len(self.indent): + level = len(self.indent)-1 + + for o in options: + if o['type'] == "section": + output += self.sectnamdel[0]+o['name']+self.sectnamdel[1]+self.deol + output += self.dump(o['value'], level+1) + continue + if o['type'] == "subsection": + output += self.indent[level]+o['name']+self.dassign+self.subsectdel[0]+self.deol + output += self.dump(o['value'], level+1) + output += self.indent[level]+self.subsectdel[1]+self.deol + continue + if o['type'] == "option": + output += self.indent[level]+o['name']+self.dassign+o['value']+self.deol + continue + if o['type'] == "comment": + output += self.dcomment+o['value']+self.deol + continue + if o['type'] == "empty": + output += self.deol + continue + raise SyntaxError, 'Unknown type: ['+o['type']+']' + + return output + + def parseLine(self, line): + + if self.matchEmpty(line): + return {'name':'empty', 'type':'empty'} + + value = self.matchComment(line) + if value: + return {'name':'comment', 'type':'comment', 'value':value.rstrip()} + + parts = line.split(self.dassign, 1) + if len(parts) < 2: + raise SyntaxError, 'Syntax Error: Unknown line format' + + return {'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()} + + def findOpts(self, opts, type, name, exclude_sections=False): + + num = 0 + for o in opts: + if o['type'] == type and o['name'] == name: + return (num, o) + if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"): + return (num, None) + num += 1 + return (num, None) + + def commentOpts(self, inopts, level = 0): + + opts = [] + + if level >= len(self.indent): + level = len(self.indent)-1 + + for o in inopts: + if o['type'] == 'section': + no = self.commentOpts(o['value'], level+1) + val = self.dcomment+self.sectnamdel[0]+o['name']+self.sectnamdel[1] + opts.append({'name':'comment', 'type':'comment', 'value':val}) + for n in no: + opts.append(n) + continue + if o['type'] == 'subsection': + no = self.commentOpts(o['value'], level+1) + val = self.indent[level]+o['name']+self.dassign+self.subsectdel[0] + opts.append({'name':'comment', 'type':'comment', 'value':val}) + for n in no: + opts.append(n) + val = self.indent[level]+self.subsectdel[1] + opts.append({'name':'comment', 'type':'comment', 'value':val}) + continue + if o['type'] == 'option': + val = self.indent[level]+o['name']+self.dassign+o['value'] + opts.append({'name':'comment', 'type':'comment', 'value':val}) + continue + if o['type'] == 'comment': + opts.append(o) + continue + if o['type'] == 'empty': + opts.append({'name':'comment', 'type':'comment', 'value':''}) + continue + raise SyntaxError, 'Unknown type: ['+o['type']+']' + + return opts + + def mergeOld(self, oldopts, newopts): + + opts = [] + + for o in oldopts: + if o['type'] == "section" or o['type'] == "subsection": + (num, no) = self.findOpts(newopts, o['type'], o['name']) + if not no: + opts.append(o) + continue + if no['action'] == "set": + mo = self.mergeOld(o['value'], no['value']) + opts.append({'name':o['name'], 'type':o['type'], 'value':mo}) + continue + if no['action'] == "comment": + co = self.commentOpts(o['value']) + for c in co: + opts.append(c) + continue + if no['action'] == "remove": + continue + raise SyntaxError, 'Unknown action: ['+no['action']+']' + + if o['type'] == "comment" or o['type'] == "empty": + opts.append(o) + continue + + if o['type'] == "option": + (num, no) = self.findOpts(newopts, 'option', o['name'], True) + if not no: + opts.append(o) + continue + if no['action'] == 'comment' or no['action'] == 'remove': + if no['value'] != None and o['value'] != no['value']: + opts.append(o) + continue + if no['action'] == 'comment': + opts.append({'name':'comment', 'type':'comment', + 'value':self.dcomment+o['name']+self.dassign+o['value']}) + continue + if no['action'] == 'set': + opts.append(no) + continue + raise SyntaxError, 'Unknown action: ['+o['action']+']' + + raise SyntaxError, 'Unknown type: ['+o['type']+']' + + return opts + + def mergeNew(self, opts, newopts): + + cline = 0 + + for no in newopts: + + if no['type'] == "section" or no['type'] == "subsection": + (num, o) = self.findOpts(opts, no['type'], no['name']) + if not o: + if no['action'] == 'set': + opts.append(no) + continue + if no['action'] == "set": + self.mergeNew(o['value'], no['value']) + continue + cline = num+1 + continue + + if no['type'] == "option": + (num, o) = self.findOpts(opts, no['type'], no['name'], True) + if not o: + if no['action'] == 'set': + opts.append(no) + continue + cline = num+1 + continue + + if no['type'] == "comment" or no['type'] == "empty": + opts.insert(cline, no) + cline += 1 + continue + + raise SyntaxError, 'Unknown type: ['+no['type']+']' + + + def merge(self, oldopts, newopts): + + #Use a two pass strategy + #First we create a new opts tree from oldopts removing/commenting + # the options as indicated by the contents of newopts + #Second we fill in the new opts tree with options as indicated + # in the newopts tree (this is becaus eentire (sub)sections may + # exist in the newopts that do not exist in oldopts) + + opts = self.mergeOld(oldopts, newopts) + self.mergeNew(opts, newopts) + return opts + + #TODO: Make parse() recursive? + def parse(self, f): + + opts = [] + sectopts = [] + section = None + subsectopts = [] + subsection = None + curopts = opts + fatheropts = opts + + # Read in the old file. + for line in f: + + # It's a section start. + value = self.matchSection(line) + if value: + if section is not None: + opts.append({'name':section, 'type':'section', 'value':sectopts}) + sectopts = [] + curopts = sectopts + fatheropts = sectopts + section = value + continue + + # It's a subsection start. + value = self.matchSubSection(line) + if value: + if subsection is not None: + raise SyntaxError, 'nested subsections are not supported yet' + subsectopts = [] + curopts = subsectopts + subsection = value + continue + + value = self.matchSubSectionEnd(line) + if value: + if subsection is None: + raise SyntaxError, 'Unmatched end subsection terminator found' + fatheropts.append({'name':subsection, 'type':'subsection', 'value':subsectopts}) + subsection = None + curopts = fatheropts + continue + + # Copy anything else as is. + curopts.append(self.parseLine(line)) + + #Add last section if any + if len(sectopts) is not 0: + opts.append({'name':section, 'type':'section', 'value':sectopts}) + + return opts + + # Write settings to configuration file + # file is a path + # options is a set of dictionaries in the form: + # [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}] + # section is a section name like 'global' + def changeConf(self, file, newopts): + autosection = False + savedsection = None + done = False + output = "" + f = None + try: + #Do not catch an unexisting file error, we want to fail in that case + shutil.copy2(file, file+self.backup_suffix) + + f = openLocked(file, 0644) + + oldopts = self.parse(f) + + options = self.merge(oldopts, newopts) + + output = self.dump(options) + + # Write it out and close it. + f.seek(0) + f.truncate(0) + f.write(output) + finally: + try: + if f: + f.close() + except IOError: + pass + return True + + # Write settings to new file, backup old + # file is a path + # options is a set of dictionaries in the form: + # [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}] + # section is a section name like 'global' + def newConf(self, file, options): + autosection = False + savedsection = None + done = False + output = "" + f = None + try: + try: + shutil.copy2(file, file+self.backup_suffix) + except IOError, err: + if err.errno == 2: + # The orign file did not exist + pass + + f = openLocked(file, 0644) + + # Trunkate + f.seek(0) + f.truncate(0) + + output = self.dump(options) + + f.write(output) + finally: + try: + if f: + f.close() + except IOError: + pass + return True + +# A SSSD-specific subclass of IPAChangeConf +class SSSDChangeConf(IPAChangeConf): + OPTCRE = re.compile( + r'(?P