summaryrefslogtreecommitdiffstats
path: root/src/tests/intg
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
commit74aa0bc6779af38018a03fd2cf4419fe85917904 (patch)
tree9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/tests/intg
parentInitial commit. (diff)
downloadsssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz
sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tests/intg')
-rw-r--r--src/tests/intg/Makefile.am243
-rw-r--r--src/tests/intg/Makefile.in1347
-rw-r--r--src/tests/intg/__init__.py13
-rw-r--r--src/tests/intg/config.py.m425
-rw-r--r--src/tests/intg/conftest.py1
-rw-r--r--src/tests/intg/cwrap-dbus-system.conf83
-rw-r--r--src/tests/intg/data/ad_data.ldif815
-rw-r--r--src/tests/intg/data/ad_schema.ldif42
-rw-r--r--src/tests/intg/data/cert_schema.ldif11
-rw-r--r--src/tests/intg/data/cwrap-dbus-system.conf.in83
-rw-r--r--src/tests/intg/data/ssh_schema.ldif11
-rw-r--r--src/tests/intg/data/sudo_schema.ldif11
-rw-r--r--src/tests/intg/ds.py59
-rw-r--r--src/tests/intg/ds_openldap.py398
-rw-r--r--src/tests/intg/ent.py506
-rw-r--r--src/tests/intg/ent_test.py429
-rw-r--r--src/tests/intg/files_ops.py173
-rw-r--r--src/tests/intg/getsockopt_wrapper.c94
-rw-r--r--src/tests/intg/kdc.py178
-rw-r--r--src/tests/intg/krb5utils.py174
-rw-r--r--src/tests/intg/ldap_ent.py235
-rw-r--r--src/tests/intg/ldap_local_override_test.py1193
-rw-r--r--src/tests/intg/nss_call.c166
-rw-r--r--src/tests/intg/sss_netgroup_thread_test.c81
-rw-r--r--src/tests/intg/sssd_group.py132
-rw-r--r--src/tests/intg/sssd_hosts.py142
-rw-r--r--src/tests/intg/sssd_id.py129
-rw-r--r--src/tests/intg/sssd_ldb.py96
-rw-r--r--src/tests/intg/sssd_netgroup.py247
-rw-r--r--src/tests/intg/sssd_nets.py147
-rw-r--r--src/tests/intg/sssd_nss.py78
-rw-r--r--src/tests/intg/sssd_passwd.py210
-rw-r--r--src/tests/intg/test_confdb.py158
-rw-r--r--src/tests/intg/test_enumeration.py792
-rw-r--r--src/tests/intg/test_files_ops.py85
-rw-r--r--src/tests/intg/test_files_provider.py1341
-rw-r--r--src/tests/intg/test_infopipe.py855
-rw-r--r--src/tests/intg/test_kcm.py559
-rw-r--r--src/tests/intg/test_ldap.py2106
-rw-r--r--src/tests/intg/test_memory_cache.py1215
-rw-r--r--src/tests/intg/test_netgroup.py538
-rw-r--r--src/tests/intg/test_pac_responder.py129
-rw-r--r--src/tests/intg/test_pam_responder.py926
-rw-r--r--src/tests/intg/test_pysss_nss_idmap.py360
-rw-r--r--src/tests/intg/test_resolver.py326
-rw-r--r--src/tests/intg/test_session_recording.py1165
-rw-r--r--src/tests/intg/test_ssh_pubkey.py340
-rw-r--r--src/tests/intg/test_sss_cache.py34
-rw-r--r--src/tests/intg/test_sssctl.py532
-rw-r--r--src/tests/intg/test_sudo.py284
-rw-r--r--src/tests/intg/test_ts_cache.py679
-rw-r--r--src/tests/intg/util.py106
52 files changed, 20082 insertions, 0 deletions
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
new file mode 100644
index 0000000..3866d3c
--- /dev/null
+++ b/src/tests/intg/Makefile.am
@@ -0,0 +1,243 @@
+dist_noinst_DATA = \
+ __init__.py \
+ config.py.m4 \
+ util.py \
+ sssd_nss.py \
+ sssd_id.py \
+ sssd_ldb.py \
+ sssd_netgroup.py \
+ sssd_passwd.py \
+ sssd_group.py \
+ ds.py \
+ ds_openldap.py \
+ ent.py \
+ ent_test.py \
+ ldap_ent.py \
+ ldap_local_override_test.py \
+ util.py \
+ test_enumeration.py \
+ test_ldap.py \
+ test_memory_cache.py \
+ test_session_recording.py \
+ test_ts_cache.py \
+ test_netgroup.py \
+ test_sssctl.py \
+ files_ops.py \
+ test_files_ops.py \
+ test_files_provider.py \
+ kdc.py \
+ krb5utils.py \
+ test_kcm.py \
+ test_pac_responder.py \
+ data/ad_data.ldif \
+ data/ad_schema.ldif \
+ data/cert_schema.ldif \
+ data/ssh_schema.ldif \
+ data/sudo_schema.ldif \
+ test_pysss_nss_idmap.py \
+ test_infopipe.py \
+ test_ssh_pubkey.py \
+ test_pam_responder.py \
+ test_sudo.py \
+ test_resolver.py \
+ conftest.py \
+ sssd_hosts.py \
+ sssd_nets.py \
+ test_confdb.py \
+ test_sss_cache.py \
+ $(NULL)
+
+EXTRA_DIST = data/cwrap-dbus-system.conf.in
+
+dbussysconfdir = $(sysconfdir)/dbus-1
+dbusservicedir = $(datadir)/dbus-1/system-services
+
+if INTG_BUILD
+lib_LTLIBRARIES = getsockopt_wrapper.la
+
+getsockopt_wrapper_la_SOURCES = \
+ getsockopt_wrapper.c
+getsockopt_wrapper_la_CFLAGS = \
+ $(AM_CFLAGS)
+getsockopt_wrapper_la_LIBADD = \
+ $(LIBADD_DL) \
+ $(NULL)
+getsockopt_wrapper_la_LDFLAGS = \
+ -avoid-version \
+ -module
+
+bin_PROGRAMS = sss_netgroup_thread_test
+
+sss_netgroup_thread_test_SOURCES = \
+ sss_netgroup_thread_test.c \
+ $(NULL)
+sss_netgroup_thread_test_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(NULL)
+sss_netgroup_thread_test_LDADD = \
+ -lpthread \
+ $(NULL)
+
+nsslib_LTLIBRARIES = libnss_call.la
+libnss_call_la_SOURCES = \
+ nss_call.c \
+ $(NULL)
+libnss_call_la_LDFLAGS = \
+ -module \
+ -version-info 2:0:0 \
+ $(NULL)
+
+dist_dbussysconf_DATA = cwrap-dbus-system.conf
+
+install-data-hook:
+ $(MKDIR_P) $(DESTDIR)$(runstatedir)/dbus
+ $(MKDIR_P) $(DESTDIR)$(sysconfdir)/session.d
+
+endif
+
+if BUILD_KCM_RENEWAL
+KCM_RENEW = "enabled"
+else
+KCM_RENEW = "disabled"
+endif
+
+if BUILD_FILES_PROVIDER
+FILES_PROVIDER = "enabled"
+else
+FILES_PROVIDER = "disabled"
+endif
+
+
+cwrap-dbus-system.conf: data/cwrap-dbus-system.conf.in Makefile
+ $(SED) -e "s!@runstatedir[@]!$(runstatedir)!" \
+ -e "s!@dbusservicedir[@]!$(dbusservicedir)!" \
+ $< > $@
+
+config.py: config.py.m4
+ m4 -D "prefix=\`$(prefix)'" \
+ -D "sysconfdir=\`$(sysconfdir)'" \
+ -D "nsslibdir=\`$(nsslibdir)'" \
+ -D "dbpath=\`$(dbpath)'" \
+ -D "pubconfpath=\`$(pubconfpath)'" \
+ -D "pidpath=\`$(pidpath)'" \
+ -D "logpath=\`$(logpath)'" \
+ -D "mcpath=\`$(mcpath)'" \
+ -D "secdbpath=\`$(secdbpath)'" \
+ -D "libexecpath=\`$(libexecdir)'" \
+ -D "runstatedir=\`$(runstatedir)'" \
+ -D "abs_builddir=\`$(abs_builddir)'" \
+ -D "session_recording_shell=\`$(session_recording_shell)'" \
+ -D "py2execdir=\`$(py2execdir)'" \
+ -D "py3execdir=\`$(py3execdir)'" \
+ -D "python2dir=\`$(python2dir)'" \
+ -D "python3dir=\`$(python3dir)'" \
+ $< > $@
+
+root:
+ : "Create directory for emulated root's D-Bus cookies."
+ : "See http://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms"
+ $(MKDIR_P) -m 0700 root/.dbus-keyrings
+
+passwd: root
+ echo "root:x:0:0:root:$(abs_builddir)/root:/bin/bash" > $@
+
+group:
+ echo "root:x:0:" > $@
+
+PAM_SERVICE_DIR=pam_service_dir
+pam_sss_service:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_alt_service:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_sc_required:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so require_cert_auth retry=1" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_try_sc:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so try_cert_auth" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_allow_missing_name:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so allow_missing_name" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_domains:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom1" > $(PAM_SERVICE_DIR)/$@
+ echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom2" >> $(PAM_SERVICE_DIR)/$@
+ echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom3" >> $(PAM_SERVICE_DIR)/$@
+ echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=krb5_auth" >> $(PAM_SERVICE_DIR)/$@
+ echo "auth required pam_deny.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+CLEANFILES=config.py config.pyc passwd group
+
+clean-local:
+ rm -Rf root
+ rm -f $(builddir)/cwrap-dbus-system.conf
+
+PAM_CERT_DB_PATH="$(abs_builddir)/../test_CA/SSSD_test_CA.pem"
+SOFTHSM2_CONF="$(abs_builddir)/../test_CA/softhsm2_one.conf"
+
+intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service pam_sss_sc_required pam_sss_try_sc pam_sss_allow_missing_name pam_sss_domains sss_netgroup_thread_test
+ pipepath="$(DESTDIR)$(pipepath)"; \
+ if test $${#pipepath} -gt 80; then \
+ echo "error: Pipe directory path too long," \
+ "D-Bus won't be able to open sockets" >&2; \
+ exit 1; \
+ fi
+ set -e; \
+ cd "$(abs_srcdir)"; \
+ nss_wrapper=$$(pkg-config --libs nss_wrapper); \
+ uid_wrapper=$$(pkg-config --libs uid_wrapper); \
+ unset HOME; \
+ PATH="$$(dirname -- $(SLAPD)):$$PATH" \
+ PATH="$(DESTDIR)$(sbindir):$(DESTDIR)$(bindir):$$PATH" \
+ PATH="$$PATH:$(abs_builddir):$(abs_srcdir)" \
+ LANG=C \
+ PYTHONPATH="$(abs_builddir):$(abs_srcdir)" \
+ LDB_MODULES_PATH="$(DESTDIR)$(ldblibdir)" \
+ NON_WRAPPED_UID=$$(id -u) \
+ LD_PRELOAD="$(libdir)/getsockopt_wrapper.so:$$nss_wrapper:$$uid_wrapper" \
+ LD_LIBRARY_PATH="$$LD_LIBRARY_PATH:$(DESTDIR)$(nsslibdir)" \
+ NSS_WRAPPER_PASSWD="$(abs_builddir)/passwd" \
+ NSS_WRAPPER_GROUP="$(abs_builddir)/group" \
+ NSS_WRAPPER_MODULE_SO_PATH="$(DESTDIR)$(nsslibdir)/libnss_sss.so.2" \
+ NSS_WRAPPER_MODULE_FN_PREFIX="sss" \
+ UID_WRAPPER=1 \
+ UID_WRAPPER_ROOT=1 \
+ PAM_WRAPPER=0 \
+ PAM_WRAPPER_SERVICE_DIR="$(abs_builddir)/$(PAM_SERVICE_DIR)" \
+ PAM_WRAPPER_PATH=$$(pkg-config --libs pam_wrapper) \
+ PAM_CERT_DB_PATH=$(PAM_CERT_DB_PATH) \
+ ABS_SRCDIR=$(abs_srcdir) \
+ SOFTHSM2_CONF=$(SOFTHSM2_CONF) \
+ KCM_RENEW=$(KCM_RENEW) \
+ FILES_PROVIDER=$(FILES_PROVIDER) \
+ DBUS_SOCK_DIR="$(DESTDIR)$(runstatedir)/dbus/" \
+ DBUS_SESSION_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/fake_socket" \
+ DBUS_SYSTEM_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/system_bus_socket" \
+ DBUS_SYSTEM_BUS_DEFAULT_ADDRESS="$$DBUS_SYSTEM_BUS_ADDRESS" \
+ fakeroot $(PYTHON_EXEC_INTG) -m pytest -v -r a --tb=native $(INTGCHECK_PYTEST_ARGS) .
+ rm -f $(DESTDIR)$(logpath)/*
diff --git a/src/tests/intg/Makefile.in b/src/tests/intg/Makefile.in
new file mode 100644
index 0000000..32df7c7
--- /dev/null
+++ b/src/tests/intg/Makefile.in
@@ -0,0 +1,1347 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@INTG_BUILD_TRUE@bin_PROGRAMS = sss_netgroup_thread_test$(EXEEXT)
+subdir = src/tests/intg
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/version.m4 $(top_srcdir)/src/build_macros.m4 \
+ $(top_srcdir)/src/external/platform.m4 \
+ $(top_srcdir)/src/conf_macros.m4 \
+ $(top_srcdir)/src/external/pkg.m4 \
+ $(top_srcdir)/src/external/libpopt.m4 \
+ $(top_srcdir)/src/external/libtalloc.m4 \
+ $(top_srcdir)/src/external/libtdb.m4 \
+ $(top_srcdir)/src/external/libtevent.m4 \
+ $(top_srcdir)/src/external/libldb.m4 \
+ $(top_srcdir)/src/external/libdhash.m4 \
+ $(top_srcdir)/src/external/libini_config.m4 \
+ $(top_srcdir)/src/external/libgssapi_krb5.m4 \
+ $(top_srcdir)/src/external/pam.m4 \
+ $(top_srcdir)/src/external/ldap.m4 \
+ $(top_srcdir)/src/external/libpcre.m4 \
+ $(top_srcdir)/src/external/krb5.m4 \
+ $(top_srcdir)/src/external/libcares.m4 \
+ $(top_srcdir)/src/external/libcmocka.m4 \
+ $(top_srcdir)/src/external/docbook.m4 \
+ $(top_srcdir)/src/external/sizes.m4 \
+ $(top_srcdir)/src/external/python.m4 \
+ $(top_srcdir)/src/external/selinux.m4 \
+ $(top_srcdir)/src/external/crypto.m4 \
+ $(top_srcdir)/src/external/nsupdate.m4 \
+ $(top_srcdir)/src/external/libkeyutils.m4 \
+ $(top_srcdir)/src/external/libkrad.m4 \
+ $(top_srcdir)/src/external/libnl.m4 \
+ $(top_srcdir)/src/external/systemd.m4 \
+ $(top_srcdir)/src/external/pac_responder.m4 \
+ $(top_srcdir)/src/external/cifsidmap.m4 \
+ $(top_srcdir)/src/external/signal.m4 \
+ $(top_srcdir)/src/external/inotify.m4 \
+ $(top_srcdir)/src/external/samba.m4 \
+ $(top_srcdir)/src/external/sasl.m4 \
+ $(top_srcdir)/src/external/libnfsidmap.m4 \
+ $(top_srcdir)/src/external/cwrap.m4 \
+ $(top_srcdir)/src/external/libresolv.m4 \
+ $(top_srcdir)/src/external/intgcheck.m4 \
+ $(top_srcdir)/src/external/systemtap.m4 \
+ $(top_srcdir)/src/external/service.m4 \
+ $(top_srcdir)/src/external/test_ca.m4 \
+ $(top_srcdir)/src/external/ax_valgrind_check.m4 \
+ $(top_srcdir)/src/external/libjansson.m4 \
+ $(top_srcdir)/src/external/libcurl.m4 \
+ $(top_srcdir)/src/external/libjose.m4 \
+ $(top_srcdir)/src/external/libuuid.m4 \
+ $(top_srcdir)/src/external/libunistring.m4 \
+ $(top_srcdir)/src/external/libpasskey.m4 \
+ $(top_srcdir)/src/external/p11-kit.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__dist_dbussysconf_DATA_DIST) \
+ $(dist_noinst_DATA) $(am__DIST_COMMON)
+mkinstalldirs = $(SHELL) $(top_srcdir)/build/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libdir)" \
+ "$(DESTDIR)$(nsslibdir)" "$(DESTDIR)$(dbussysconfdir)"
+PROGRAMS = $(bin_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+LTLIBRARIES = $(lib_LTLIBRARIES) $(nsslib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@INTG_BUILD_TRUE@getsockopt_wrapper_la_DEPENDENCIES = \
+@INTG_BUILD_TRUE@ $(am__DEPENDENCIES_1)
+am__getsockopt_wrapper_la_SOURCES_DIST = getsockopt_wrapper.c
+@INTG_BUILD_TRUE@am_getsockopt_wrapper_la_OBJECTS = \
+@INTG_BUILD_TRUE@ getsockopt_wrapper_la-getsockopt_wrapper.lo
+getsockopt_wrapper_la_OBJECTS = $(am_getsockopt_wrapper_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+getsockopt_wrapper_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(getsockopt_wrapper_la_CFLAGS) $(CFLAGS) \
+ $(getsockopt_wrapper_la_LDFLAGS) $(LDFLAGS) -o $@
+@INTG_BUILD_TRUE@am_getsockopt_wrapper_la_rpath = -rpath $(libdir)
+libnss_call_la_LIBADD =
+am__libnss_call_la_SOURCES_DIST = nss_call.c
+@INTG_BUILD_TRUE@am_libnss_call_la_OBJECTS = nss_call.lo
+libnss_call_la_OBJECTS = $(am_libnss_call_la_OBJECTS)
+libnss_call_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libnss_call_la_LDFLAGS) $(LDFLAGS) -o \
+ $@
+@INTG_BUILD_TRUE@am_libnss_call_la_rpath = -rpath $(nsslibdir)
+am__sss_netgroup_thread_test_SOURCES_DIST = \
+ sss_netgroup_thread_test.c
+@INTG_BUILD_TRUE@am_sss_netgroup_thread_test_OBJECTS = sss_netgroup_thread_test-sss_netgroup_thread_test.$(OBJEXT)
+sss_netgroup_thread_test_OBJECTS = \
+ $(am_sss_netgroup_thread_test_OBJECTS)
+sss_netgroup_thread_test_DEPENDENCIES =
+sss_netgroup_thread_test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/build/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = \
+ ./$(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo \
+ ./$(DEPDIR)/nss_call.Plo \
+ ./$(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(getsockopt_wrapper_la_SOURCES) $(libnss_call_la_SOURCES) \
+ $(sss_netgroup_thread_test_SOURCES)
+DIST_SOURCES = $(am__getsockopt_wrapper_la_SOURCES_DIST) \
+ $(am__libnss_call_la_SOURCES_DIST) \
+ $(am__sss_netgroup_thread_test_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__dist_dbussysconf_DATA_DIST = cwrap-dbus-system.conf
+DATA = $(dist_dbussysconf_DATA) $(dist_noinst_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/build/depcomp \
+ $(top_srcdir)/build/mkinstalldirs
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CARES_CFLAGS = @CARES_CFLAGS@
+CARES_LIBS = @CARES_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+CMOCKA_CFLAGS = @CMOCKA_CFLAGS@
+CMOCKA_LIBS = @CMOCKA_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CURL_CFLAGS = @CURL_CFLAGS@
+CURL_LIBS = @CURL_LIBS@
+CYGPATH_W = @CYGPATH_W@
+DBUS_CFLAGS = @DBUS_CFLAGS@
+DBUS_LIBS = @DBUS_LIBS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DHASH_CFLAGS = @DHASH_CFLAGS@
+DHASH_LIBS = @DHASH_LIBS@
+DLLTOOL = @DLLTOOL@
+DOCBOOK_XSLT = @DOCBOOK_XSLT@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DTRACE = @DTRACE@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ENABLE_VALGRIND_drd = @ENABLE_VALGRIND_drd@
+ENABLE_VALGRIND_helgrind = @ENABLE_VALGRIND_helgrind@
+ENABLE_VALGRIND_memcheck = @ENABLE_VALGRIND_memcheck@
+ENABLE_VALGRIND_sgcheck = @ENABLE_VALGRIND_sgcheck@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FAKETIME = @FAKETIME@
+FGREP = @FGREP@
+FIDO2_CFLAGS = @FIDO2_CFLAGS@
+FIDO2_LIBS = @FIDO2_LIBS@
+FILECMD = @FILECMD@
+GDM_PAM_EXTENSIONS_CFLAGS = @GDM_PAM_EXTENSIONS_CFLAGS@
+GDM_PAM_EXTENSIONS_LIBS = @GDM_PAM_EXTENSIONS_LIBS@
+GMSGFMT = @GMSGFMT@
+GPO_DEFAULT = @GPO_DEFAULT@
+GREP = @GREP@
+GSSAPI_KRB5_CFLAGS = @GSSAPI_KRB5_CFLAGS@
+GSSAPI_KRB5_LIBS = @GSSAPI_KRB5_LIBS@
+HAVE_FAKEROOT = @HAVE_FAKEROOT@
+HAVE_LDAPMODIFY = @HAVE_LDAPMODIFY@
+HAVE_MANPAGES = @HAVE_MANPAGES@
+HAVE_NSS_WRAPPER = @HAVE_NSS_WRAPPER@
+HAVE_PAM_WRAPPER = @HAVE_PAM_WRAPPER@
+HAVE_PYTHON2 = @HAVE_PYTHON2@
+HAVE_PYTHON2_BINDINGS = @HAVE_PYTHON2_BINDINGS@
+HAVE_PYTHON3 = @HAVE_PYTHON3@
+HAVE_PYTHON3_BINDINGS = @HAVE_PYTHON3_BINDINGS@
+HAVE_SELINUX = @HAVE_SELINUX@
+HAVE_SEMANAGE = @HAVE_SEMANAGE@
+HAVE_UID_WRAPPER = @HAVE_UID_WRAPPER@
+INI_CONFIG_CFLAGS = @INI_CONFIG_CFLAGS@
+INI_CONFIG_LIBS = @INI_CONFIG_LIBS@
+INI_CONFIG_V0_CFLAGS = @INI_CONFIG_V0_CFLAGS@
+INI_CONFIG_V0_LIBS = @INI_CONFIG_V0_LIBS@
+INI_CONFIG_V1_1_CFLAGS = @INI_CONFIG_V1_1_CFLAGS@
+INI_CONFIG_V1_1_LIBS = @INI_CONFIG_V1_1_LIBS@
+INI_CONFIG_V1_3_CFLAGS = @INI_CONFIG_V1_3_CFLAGS@
+INI_CONFIG_V1_3_LIBS = @INI_CONFIG_V1_3_LIBS@
+INI_CONFIG_V1_CFLAGS = @INI_CONFIG_V1_CFLAGS@
+INI_CONFIG_V1_LIBS = @INI_CONFIG_V1_LIBS@
+INOTIFY_LIBS = @INOTIFY_LIBS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
+JOSE_CFLAGS = @JOSE_CFLAGS@
+JOSE_LIBS = @JOSE_LIBS@
+JOURNALD_CFLAGS = @JOURNALD_CFLAGS@
+JOURNALD_LIBS = @JOURNALD_LIBS@
+KEYUTILS_LIBS = @KEYUTILS_LIBS@
+KRAD_LIBS = @KRAD_LIBS@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_CONFIG = @KRB5_CONFIG@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDB_CFLAGS = @LDB_CFLAGS@
+LDB_LIBS = @LDB_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBADD_DL = @LIBADD_DL@
+LIBADD_DLD_LINK = @LIBADD_DLD_LINK@
+LIBADD_DLOPEN = @LIBADD_DLOPEN@
+LIBADD_SHL_LOAD = @LIBADD_SHL_LOAD@
+LIBADD_TIMER = @LIBADD_TIMER@
+LIBCLOCK_GETTIME = @LIBCLOCK_GETTIME@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBNL1_CFLAGS = @LIBNL1_CFLAGS@
+LIBNL1_LIBS = @LIBNL1_LIBS@
+LIBNL3_CFLAGS = @LIBNL3_CFLAGS@
+LIBNL3_LIBS = @LIBNL3_LIBS@
+LIBNL_CFLAGS = @LIBNL_CFLAGS@
+LIBNL_LIBS = @LIBNL_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_DLLOADERS = @LT_DLLOADERS@
+LT_DLPREOPEN = @LT_DLPREOPEN@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MSGFMT = @MSGFMT@
+MSGMERGE = @MSGMERGE@
+NDR_KRB5PAC_CFLAGS = @NDR_KRB5PAC_CFLAGS@
+NDR_KRB5PAC_LIBS = @NDR_KRB5PAC_LIBS@
+NDR_NBT_CFLAGS = @NDR_NBT_CFLAGS@
+NDR_NBT_LIBS = @NDR_NBT_LIBS@
+NFSIDMAP_CFLAGS = @NFSIDMAP_CFLAGS@
+NFSIDMAP_LIBS = @NFSIDMAP_LIBS@
+NFSIDMAP_OBJ = @NFSIDMAP_OBJ@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NSUPDATE = @NSUPDATE@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENLDAP_CFLAGS = @OPENLDAP_CFLAGS@
+OPENLDAP_LIBS = @OPENLDAP_LIBS@
+OPENSSL = @OPENSSL@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+P11TOOL = @P11TOOL@
+P11_KIT_CFLAGS = @P11_KIT_CFLAGS@
+P11_KIT_LIBS = @P11_KIT_LIBS@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PAM_LIBS = @PAM_LIBS@
+PAM_MISC_LIBS = @PAM_MISC_LIBS@
+PASSKEY_CFLAGS = @PASSKEY_CFLAGS@
+PASSKEY_LIBS = @PASSKEY_LIBS@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PKG_CONFIG = @PKG_CONFIG@
+PO4A = @PO4A@
+POPT_CFLAGS = @POPT_CFLAGS@
+POPT_LIBS = @POPT_LIBS@
+POSUB = @POSUB@
+PRERELEASE_VERSION = @PRERELEASE_VERSION@
+PYTHON = @PYTHON@
+PYTHON2 = @PYTHON2@
+PYTHON2_CFLAGS = @PYTHON2_CFLAGS@
+PYTHON2_EXEC_PREFIX = @PYTHON2_EXEC_PREFIX@
+PYTHON2_INCLUDES = @PYTHON2_INCLUDES@
+PYTHON2_LIBS = @PYTHON2_LIBS@
+PYTHON2_PREFIX = @PYTHON2_PREFIX@
+PYTHON2_VERSION = @PYTHON2_VERSION@
+PYTHON3 = @PYTHON3@
+PYTHON3_CFLAGS = @PYTHON3_CFLAGS@
+PYTHON3_EXEC_PREFIX = @PYTHON3_EXEC_PREFIX@
+PYTHON3_INCLUDES = @PYTHON3_INCLUDES@
+PYTHON3_LIBS = @PYTHON3_LIBS@
+PYTHON3_PREFIX = @PYTHON3_PREFIX@
+PYTHON3_VERSION = @PYTHON3_VERSION@
+PYTHON_CONFIG = @PYTHON_CONFIG@
+PYTHON_EXEC = @PYTHON_EXEC@
+PYTHON_EXEC_INTG = @PYTHON_EXEC_INTG@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+RESOLV_CFLAGS = @RESOLV_CFLAGS@
+RESOLV_LIBS = @RESOLV_LIBS@
+SAMBA_UTIL_CFLAGS = @SAMBA_UTIL_CFLAGS@
+SAMBA_UTIL_LIBS = @SAMBA_UTIL_LIBS@
+SASL_CFLAGS = @SASL_CFLAGS@
+SASL_LIBS = @SASL_LIBS@
+SED = @SED@
+SELINUX_LIBS = @SELINUX_LIBS@
+SEMANAGE_LIBS = @SEMANAGE_LIBS@
+SERVICE = @SERVICE@
+SET_MAKE = @SET_MAKE@
+SGML_CATALOG_FILES = @SGML_CATALOG_FILES@
+SHELL = @SHELL@
+SLAPD = @SLAPD@
+SMBCLIENT_CFLAGS = @SMBCLIENT_CFLAGS@
+SMBCLIENT_LIBS = @SMBCLIENT_LIBS@
+SOFTHSM2_PATH = @SOFTHSM2_PATH@
+SOFTHSM2_UTIL = @SOFTHSM2_UTIL@
+SSH_KEYGEN = @SSH_KEYGEN@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+SSSD_USER = @SSSD_USER@
+STRIP = @STRIP@
+SYSTEMD_DAEMON_CFLAGS = @SYSTEMD_DAEMON_CFLAGS@
+SYSTEMD_DAEMON_LIBS = @SYSTEMD_DAEMON_LIBS@
+SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@
+SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@
+TALLOC_CFLAGS = @TALLOC_CFLAGS@
+TALLOC_LIBS = @TALLOC_LIBS@
+TDB_CFLAGS = @TDB_CFLAGS@
+TDB_LIBS = @TDB_LIBS@
+TEST_DIR = @TEST_DIR@
+TEVENT_CFLAGS = @TEVENT_CFLAGS@
+TEVENT_LIBS = @TEVENT_LIBS@
+UNICODE_LIBS = @UNICODE_LIBS@
+USE_NLS = @USE_NLS@
+UUID_CFLAGS = @UUID_CFLAGS@
+UUID_LIBS = @UUID_LIBS@
+VALGRIND = @VALGRIND@
+VALGRIND_ENABLED = @VALGRIND_ENABLED@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+XMLLINT = @XMLLINT@
+XSLTPROC = @XSLTPROC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+appmodpath = @appmodpath@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+cifspluginpath = @cifspluginpath@
+config_def_ccache_dir = @config_def_ccache_dir@
+config_def_ccname_template = @config_def_ccname_template@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dbpath = @dbpath@
+docdir = @docdir@
+dvidir = @dvidir@
+environment_file = @environment_file@
+exec_prefix = @exec_prefix@
+gpocachepath = @gpocachepath@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+initdir = @initdir@
+install_sh = @install_sh@
+krb5authdatapluginpath = @krb5authdatapluginpath@
+krb5pluginpath = @krb5pluginpath@
+krb5rcachedir = @krb5rcachedir@
+ldblibdir = @ldblibdir@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+logpath = @logpath@
+mandir = @mandir@
+mcpath = @mcpath@
+mkdir_p = @mkdir_p@
+nfsidmaplibdir = @nfsidmaplibdir@
+nfslibpath = @nfslibpath@
+nsslibdir = @nsslibdir@
+oldincludedir = @oldincludedir@
+pammoddir = @pammoddir@
+pdfdir = @pdfdir@
+pidpath = @pidpath@
+pipepath = @pipepath@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+pluginpath = @pluginpath@
+polkitdir = @polkitdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pubconfpath = @pubconfpath@
+py2execdir = @py2execdir@
+py3execdir = @py3execdir@
+pyexecdir = @pyexecdir@
+python2dir = @python2dir@
+python3dir = @python3dir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+secdbpath = @secdbpath@
+session_recording_shell = @session_recording_shell@
+sharedbuilddir = @sharedbuilddir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+subidlibpath = @subidlibpath@
+sudolibpath = @sudolibpath@
+sysconfdir = @sysconfdir@
+systemdconfdir = @systemdconfdir@
+systemdunitdir = @systemdunitdir@
+tapset_dir = @tapset_dir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+valgrind_enabled_tools = @valgrind_enabled_tools@
+valgrind_tools = @valgrind_tools@
+winbindpluginpath = @winbindpluginpath@
+dist_noinst_DATA = \
+ __init__.py \
+ config.py.m4 \
+ util.py \
+ sssd_nss.py \
+ sssd_id.py \
+ sssd_ldb.py \
+ sssd_netgroup.py \
+ sssd_passwd.py \
+ sssd_group.py \
+ ds.py \
+ ds_openldap.py \
+ ent.py \
+ ent_test.py \
+ ldap_ent.py \
+ ldap_local_override_test.py \
+ util.py \
+ test_enumeration.py \
+ test_ldap.py \
+ test_memory_cache.py \
+ test_session_recording.py \
+ test_ts_cache.py \
+ test_netgroup.py \
+ test_sssctl.py \
+ files_ops.py \
+ test_files_ops.py \
+ test_files_provider.py \
+ kdc.py \
+ krb5utils.py \
+ test_kcm.py \
+ test_pac_responder.py \
+ data/ad_data.ldif \
+ data/ad_schema.ldif \
+ data/cert_schema.ldif \
+ data/ssh_schema.ldif \
+ data/sudo_schema.ldif \
+ test_pysss_nss_idmap.py \
+ test_infopipe.py \
+ test_ssh_pubkey.py \
+ test_pam_responder.py \
+ test_sudo.py \
+ test_resolver.py \
+ conftest.py \
+ sssd_hosts.py \
+ sssd_nets.py \
+ test_confdb.py \
+ test_sss_cache.py \
+ $(NULL)
+
+EXTRA_DIST = data/cwrap-dbus-system.conf.in
+dbussysconfdir = $(sysconfdir)/dbus-1
+dbusservicedir = $(datadir)/dbus-1/system-services
+@INTG_BUILD_TRUE@lib_LTLIBRARIES = getsockopt_wrapper.la
+@INTG_BUILD_TRUE@getsockopt_wrapper_la_SOURCES = \
+@INTG_BUILD_TRUE@ getsockopt_wrapper.c
+
+@INTG_BUILD_TRUE@getsockopt_wrapper_la_CFLAGS = \
+@INTG_BUILD_TRUE@ $(AM_CFLAGS)
+
+@INTG_BUILD_TRUE@getsockopt_wrapper_la_LIBADD = \
+@INTG_BUILD_TRUE@ $(LIBADD_DL) \
+@INTG_BUILD_TRUE@ $(NULL)
+
+@INTG_BUILD_TRUE@getsockopt_wrapper_la_LDFLAGS = \
+@INTG_BUILD_TRUE@ -avoid-version \
+@INTG_BUILD_TRUE@ -module
+
+@INTG_BUILD_TRUE@sss_netgroup_thread_test_SOURCES = \
+@INTG_BUILD_TRUE@ sss_netgroup_thread_test.c \
+@INTG_BUILD_TRUE@ $(NULL)
+
+@INTG_BUILD_TRUE@sss_netgroup_thread_test_CFLAGS = \
+@INTG_BUILD_TRUE@ $(AM_CFLAGS) \
+@INTG_BUILD_TRUE@ $(NULL)
+
+@INTG_BUILD_TRUE@sss_netgroup_thread_test_LDADD = \
+@INTG_BUILD_TRUE@ -lpthread \
+@INTG_BUILD_TRUE@ $(NULL)
+
+@INTG_BUILD_TRUE@nsslib_LTLIBRARIES = libnss_call.la
+@INTG_BUILD_TRUE@libnss_call_la_SOURCES = \
+@INTG_BUILD_TRUE@ nss_call.c \
+@INTG_BUILD_TRUE@ $(NULL)
+
+@INTG_BUILD_TRUE@libnss_call_la_LDFLAGS = \
+@INTG_BUILD_TRUE@ -module \
+@INTG_BUILD_TRUE@ -version-info 2:0:0 \
+@INTG_BUILD_TRUE@ $(NULL)
+
+@INTG_BUILD_TRUE@dist_dbussysconf_DATA = cwrap-dbus-system.conf
+@BUILD_KCM_RENEWAL_FALSE@KCM_RENEW = "disabled"
+@BUILD_KCM_RENEWAL_TRUE@KCM_RENEW = "enabled"
+@BUILD_FILES_PROVIDER_FALSE@FILES_PROVIDER = "disabled"
+@BUILD_FILES_PROVIDER_TRUE@FILES_PROVIDER = "enabled"
+PAM_SERVICE_DIR = pam_service_dir
+CLEANFILES = config.py config.pyc passwd group
+PAM_CERT_DB_PATH = "$(abs_builddir)/../test_CA/SSSD_test_CA.pem"
+SOFTHSM2_CONF = "$(abs_builddir)/../test_CA/softhsm2_one.conf"
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/tests/intg/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/tests/intg/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-nsslibLTLIBRARIES: $(nsslib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(nsslib_LTLIBRARIES)'; test -n "$(nsslibdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(nsslibdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(nsslibdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(nsslibdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(nsslibdir)"; \
+ }
+
+uninstall-nsslibLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(nsslib_LTLIBRARIES)'; test -n "$(nsslibdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(nsslibdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(nsslibdir)/$$f"; \
+ done
+
+clean-nsslibLTLIBRARIES:
+ -test -z "$(nsslib_LTLIBRARIES)" || rm -f $(nsslib_LTLIBRARIES)
+ @list='$(nsslib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+getsockopt_wrapper.la: $(getsockopt_wrapper_la_OBJECTS) $(getsockopt_wrapper_la_DEPENDENCIES) $(EXTRA_getsockopt_wrapper_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(getsockopt_wrapper_la_LINK) $(am_getsockopt_wrapper_la_rpath) $(getsockopt_wrapper_la_OBJECTS) $(getsockopt_wrapper_la_LIBADD) $(LIBS)
+
+libnss_call.la: $(libnss_call_la_OBJECTS) $(libnss_call_la_DEPENDENCIES) $(EXTRA_libnss_call_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libnss_call_la_LINK) $(am_libnss_call_la_rpath) $(libnss_call_la_OBJECTS) $(libnss_call_la_LIBADD) $(LIBS)
+
+sss_netgroup_thread_test$(EXEEXT): $(sss_netgroup_thread_test_OBJECTS) $(sss_netgroup_thread_test_DEPENDENCIES) $(EXTRA_sss_netgroup_thread_test_DEPENDENCIES)
+ @rm -f sss_netgroup_thread_test$(EXEEXT)
+ $(AM_V_CCLD)$(sss_netgroup_thread_test_LINK) $(sss_netgroup_thread_test_OBJECTS) $(sss_netgroup_thread_test_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nss_call.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+getsockopt_wrapper_la-getsockopt_wrapper.lo: getsockopt_wrapper.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(getsockopt_wrapper_la_CFLAGS) $(CFLAGS) -MT getsockopt_wrapper_la-getsockopt_wrapper.lo -MD -MP -MF $(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Tpo -c -o getsockopt_wrapper_la-getsockopt_wrapper.lo `test -f 'getsockopt_wrapper.c' || echo '$(srcdir)/'`getsockopt_wrapper.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Tpo $(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='getsockopt_wrapper.c' object='getsockopt_wrapper_la-getsockopt_wrapper.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(getsockopt_wrapper_la_CFLAGS) $(CFLAGS) -c -o getsockopt_wrapper_la-getsockopt_wrapper.lo `test -f 'getsockopt_wrapper.c' || echo '$(srcdir)/'`getsockopt_wrapper.c
+
+sss_netgroup_thread_test-sss_netgroup_thread_test.o: sss_netgroup_thread_test.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) -MT sss_netgroup_thread_test-sss_netgroup_thread_test.o -MD -MP -MF $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Tpo -c -o sss_netgroup_thread_test-sss_netgroup_thread_test.o `test -f 'sss_netgroup_thread_test.c' || echo '$(srcdir)/'`sss_netgroup_thread_test.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Tpo $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sss_netgroup_thread_test.c' object='sss_netgroup_thread_test-sss_netgroup_thread_test.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) -c -o sss_netgroup_thread_test-sss_netgroup_thread_test.o `test -f 'sss_netgroup_thread_test.c' || echo '$(srcdir)/'`sss_netgroup_thread_test.c
+
+sss_netgroup_thread_test-sss_netgroup_thread_test.obj: sss_netgroup_thread_test.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) -MT sss_netgroup_thread_test-sss_netgroup_thread_test.obj -MD -MP -MF $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Tpo -c -o sss_netgroup_thread_test-sss_netgroup_thread_test.obj `if test -f 'sss_netgroup_thread_test.c'; then $(CYGPATH_W) 'sss_netgroup_thread_test.c'; else $(CYGPATH_W) '$(srcdir)/sss_netgroup_thread_test.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Tpo $(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sss_netgroup_thread_test.c' object='sss_netgroup_thread_test-sss_netgroup_thread_test.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(sss_netgroup_thread_test_CFLAGS) $(CFLAGS) -c -o sss_netgroup_thread_test-sss_netgroup_thread_test.obj `if test -f 'sss_netgroup_thread_test.c'; then $(CYGPATH_W) 'sss_netgroup_thread_test.c'; else $(CYGPATH_W) '$(srcdir)/sss_netgroup_thread_test.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-dist_dbussysconfDATA: $(dist_dbussysconf_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(dist_dbussysconf_DATA)'; test -n "$(dbussysconfdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(dbussysconfdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(dbussysconfdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(dbussysconfdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(dbussysconfdir)" || exit $$?; \
+ done
+
+uninstall-dist_dbussysconfDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(dist_dbussysconf_DATA)'; test -n "$(dbussysconfdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(dbussysconfdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(DATA)
+install-binPROGRAMS: install-libLTLIBRARIES
+
+install-nsslibLTLIBRARIES: install-libLTLIBRARIES
+
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libdir)" "$(DESTDIR)$(nsslibdir)" "$(DESTDIR)$(dbussysconfdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+@INTG_BUILD_FALSE@install-data-hook:
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool clean-local clean-nsslibLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo
+ -rm -f ./$(DEPDIR)/nss_call.Plo
+ -rm -f ./$(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-dist_dbussysconfDATA \
+ install-nsslibLTLIBRARIES
+ @$(NORMAL_INSTALL)
+ $(MAKE) $(AM_MAKEFLAGS) install-data-hook
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/getsockopt_wrapper_la-getsockopt_wrapper.Plo
+ -rm -f ./$(DEPDIR)/nss_call.Plo
+ -rm -f ./$(DEPDIR)/sss_netgroup_thread_test-sss_netgroup_thread_test.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS uninstall-dist_dbussysconfDATA \
+ uninstall-libLTLIBRARIES uninstall-nsslibLTLIBRARIES
+
+.MAKE: install-am install-data-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool clean-local clean-nsslibLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-binPROGRAMS install-data install-data-am \
+ install-data-hook install-dist_dbussysconfDATA install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am \
+ install-libLTLIBRARIES install-man install-nsslibLTLIBRARIES \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-binPROGRAMS uninstall-dist_dbussysconfDATA \
+ uninstall-libLTLIBRARIES uninstall-nsslibLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+@INTG_BUILD_TRUE@install-data-hook:
+@INTG_BUILD_TRUE@ $(MKDIR_P) $(DESTDIR)$(runstatedir)/dbus
+@INTG_BUILD_TRUE@ $(MKDIR_P) $(DESTDIR)$(sysconfdir)/session.d
+
+cwrap-dbus-system.conf: data/cwrap-dbus-system.conf.in Makefile
+ $(SED) -e "s!@runstatedir[@]!$(runstatedir)!" \
+ -e "s!@dbusservicedir[@]!$(dbusservicedir)!" \
+ $< > $@
+
+config.py: config.py.m4
+ m4 -D "prefix=\`$(prefix)'" \
+ -D "sysconfdir=\`$(sysconfdir)'" \
+ -D "nsslibdir=\`$(nsslibdir)'" \
+ -D "dbpath=\`$(dbpath)'" \
+ -D "pubconfpath=\`$(pubconfpath)'" \
+ -D "pidpath=\`$(pidpath)'" \
+ -D "logpath=\`$(logpath)'" \
+ -D "mcpath=\`$(mcpath)'" \
+ -D "secdbpath=\`$(secdbpath)'" \
+ -D "libexecpath=\`$(libexecdir)'" \
+ -D "runstatedir=\`$(runstatedir)'" \
+ -D "abs_builddir=\`$(abs_builddir)'" \
+ -D "session_recording_shell=\`$(session_recording_shell)'" \
+ -D "py2execdir=\`$(py2execdir)'" \
+ -D "py3execdir=\`$(py3execdir)'" \
+ -D "python2dir=\`$(python2dir)'" \
+ -D "python3dir=\`$(python3dir)'" \
+ $< > $@
+
+root:
+ : "Create directory for emulated root's D-Bus cookies."
+ : "See http://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms"
+ $(MKDIR_P) -m 0700 root/.dbus-keyrings
+
+passwd: root
+ echo "root:x:0:0:root:$(abs_builddir)/root:/bin/bash" > $@
+
+group:
+ echo "root:x:0:" > $@
+pam_sss_service:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_alt_service:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_sc_required:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so require_cert_auth retry=1" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_try_sc:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so try_cert_auth" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_allow_missing_name:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth required $(DESTDIR)$(pammoddir)/pam_sss.so allow_missing_name" > $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+pam_sss_domains:
+ $(MKDIR_P) $(PAM_SERVICE_DIR)
+ echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom1" > $(PAM_SERVICE_DIR)/$@
+ echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom2" >> $(PAM_SERVICE_DIR)/$@
+ echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=wrong.dom3" >> $(PAM_SERVICE_DIR)/$@
+ echo "auth sufficient $(DESTDIR)$(pammoddir)/pam_sss.so forward_pass domains=krb5_auth" >> $(PAM_SERVICE_DIR)/$@
+ echo "auth required pam_deny.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "account required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "password required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+ echo "session required $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
+clean-local:
+ rm -Rf root
+ rm -f $(builddir)/cwrap-dbus-system.conf
+
+intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service pam_sss_sc_required pam_sss_try_sc pam_sss_allow_missing_name pam_sss_domains sss_netgroup_thread_test
+ pipepath="$(DESTDIR)$(pipepath)"; \
+ if test $${#pipepath} -gt 80; then \
+ echo "error: Pipe directory path too long," \
+ "D-Bus won't be able to open sockets" >&2; \
+ exit 1; \
+ fi
+ set -e; \
+ cd "$(abs_srcdir)"; \
+ nss_wrapper=$$(pkg-config --libs nss_wrapper); \
+ uid_wrapper=$$(pkg-config --libs uid_wrapper); \
+ unset HOME; \
+ PATH="$$(dirname -- $(SLAPD)):$$PATH" \
+ PATH="$(DESTDIR)$(sbindir):$(DESTDIR)$(bindir):$$PATH" \
+ PATH="$$PATH:$(abs_builddir):$(abs_srcdir)" \
+ LANG=C \
+ PYTHONPATH="$(abs_builddir):$(abs_srcdir)" \
+ LDB_MODULES_PATH="$(DESTDIR)$(ldblibdir)" \
+ NON_WRAPPED_UID=$$(id -u) \
+ LD_PRELOAD="$(libdir)/getsockopt_wrapper.so:$$nss_wrapper:$$uid_wrapper" \
+ LD_LIBRARY_PATH="$$LD_LIBRARY_PATH:$(DESTDIR)$(nsslibdir)" \
+ NSS_WRAPPER_PASSWD="$(abs_builddir)/passwd" \
+ NSS_WRAPPER_GROUP="$(abs_builddir)/group" \
+ NSS_WRAPPER_MODULE_SO_PATH="$(DESTDIR)$(nsslibdir)/libnss_sss.so.2" \
+ NSS_WRAPPER_MODULE_FN_PREFIX="sss" \
+ UID_WRAPPER=1 \
+ UID_WRAPPER_ROOT=1 \
+ PAM_WRAPPER=0 \
+ PAM_WRAPPER_SERVICE_DIR="$(abs_builddir)/$(PAM_SERVICE_DIR)" \
+ PAM_WRAPPER_PATH=$$(pkg-config --libs pam_wrapper) \
+ PAM_CERT_DB_PATH=$(PAM_CERT_DB_PATH) \
+ ABS_SRCDIR=$(abs_srcdir) \
+ SOFTHSM2_CONF=$(SOFTHSM2_CONF) \
+ KCM_RENEW=$(KCM_RENEW) \
+ FILES_PROVIDER=$(FILES_PROVIDER) \
+ DBUS_SOCK_DIR="$(DESTDIR)$(runstatedir)/dbus/" \
+ DBUS_SESSION_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/fake_socket" \
+ DBUS_SYSTEM_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/system_bus_socket" \
+ DBUS_SYSTEM_BUS_DEFAULT_ADDRESS="$$DBUS_SYSTEM_BUS_ADDRESS" \
+ fakeroot $(PYTHON_EXEC_INTG) -m pytest -v -r a --tb=native $(INTGCHECK_PYTEST_ARGS) .
+ rm -f $(DESTDIR)$(logpath)/*
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/tests/intg/__init__.py b/src/tests/intg/__init__.py
new file mode 100644
index 0000000..542f4d1
--- /dev/null
+++ b/src/tests/intg/__init__.py
@@ -0,0 +1,13 @@
+import sys
+import config
+
+if sys.version_info[0] > 2:
+ LOCAL_PYEXECDIR = config.PY3EXECDIR
+ LOCAL_PYDIR = config.PY3DIR
+else:
+ LOCAL_PYEXECDIR = config.PY2EXECDIR
+ LOCAL_PYDIR = config.PY2DIR
+
+for path in [LOCAL_PYEXECDIR, LOCAL_PYDIR]:
+ if path not in sys.path:
+ sys.path.insert(0, path)
diff --git a/src/tests/intg/config.py.m4 b/src/tests/intg/config.py.m4
new file mode 100644
index 0000000..ba9b1e8
--- /dev/null
+++ b/src/tests/intg/config.py.m4
@@ -0,0 +1,25 @@
+"""
+Build configuration variables.
+"""
+
+PREFIX = "prefix"
+SYSCONFDIR = "sysconfdir"
+NSS_MODULE_DIR = "nsslibdir"
+SSSDCONFDIR = SYSCONFDIR + "/sssd"
+CONF_PATH = SSSDCONFDIR + "/sssd.conf"
+CONF_SNIPPET_PATH = SSSDCONFDIR + "/conf.d/01.conf"
+DB_PATH = "dbpath"
+PID_PATH = "pidpath"
+PUBCONF_PATH = "pubconfpath"
+PIDFILE_PATH = PID_PATH + "/sssd.pid"
+LOG_PATH = "logpath"
+MCACHE_PATH = "mcpath"
+SECDB_PATH = "secdbpath"
+LIBEXEC_PATH = "libexecpath"
+RUNSTATEDIR = "runstatedir"
+ABS_BUILDDIR = "abs_builddir"
+SESSION_RECORDING_SHELL = "session_recording_shell"
+PY2EXECDIR = "py2execdir"
+PY2DIR = "python2dir"
+PY3EXECDIR = "py3execdir"
+PY3DIR = "python3dir"
diff --git a/src/tests/intg/conftest.py b/src/tests/intg/conftest.py
new file mode 100644
index 0000000..f07a410
--- /dev/null
+++ b/src/tests/intg/conftest.py
@@ -0,0 +1 @@
+from files_ops import passwd_ops_setup, group_ops_setup # noqa
diff --git a/src/tests/intg/cwrap-dbus-system.conf b/src/tests/intg/cwrap-dbus-system.conf
new file mode 100644
index 0000000..1410491
--- /dev/null
+++ b/src/tests/intg/cwrap-dbus-system.conf
@@ -0,0 +1,83 @@
+<!-- This configuration file controls the systemwide message bus.
+ Add a system-local.conf and edit that rather than changing this
+ file directly. -->
+
+<!-- Note that there are any number of ways you can hose yourself
+ security-wise by screwing up this file; in particular, you
+ probably don't want to listen on any more addresses, add any more
+ auth mechanisms, run as a different user, etc. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+ <!-- Our well-known bus type, do not change this -->
+ <type>system</type>
+
+ <!-- If we fork, keep the user's original umask to avoid affecting
+ the behavior of child processes. -->
+ <keep_umask/>
+
+
+ <!-- Fork into daemon mode -->
+ <fork/>
+
+ <!-- We use system service launching using a helper -->
+ <standard_system_servicedirs/>
+ <servicedir>/usr/local/share/dbus-1/system-services</servicedir>
+
+
+ <!-- Write a pid file -->
+ <pidfile>/usr/local/var/run/dbus/messagebus.pid</pidfile>
+
+ <!-- On Unix systems, the most secure authentication mechanism is
+ EXTERNAL, which uses credential-passing over Unix sockets.
+
+ This authentication mechanism is not available on Windows,
+ is not suitable for use with the tcp: or nonce-tcp: transports,
+ and will not work on obscure flavours of Unix that do not have
+ a supported credentials-passing mechanism. On those platforms/transports,
+ comment out the <auth> element to allow fallback to DBUS_COOKIE_SHA1. -->
+ <auth>EXTERNAL</auth>
+
+ <!-- Only listen on a local socket. (abstract=/path/to/socket
+ means use abstract namespace, don't really create filesystem
+ file; only Linux supports this. Use path=/whatever on other
+ systems.) -->
+ <listen>unix:path=/usr/local/var/run/dbus/system_bus_socket</listen>
+ <policy context="default">
+ <!-- Allow everything to be sent -->
+ <allow send_destination="*" eavesdrop="true"/>
+ <!-- Allow everything to be received -->
+ <allow eavesdrop="true"/>
+ <!-- Allow anyone to own anything -->
+ <allow own="*"/>
+ </policy>
+
+ <!-- Config files are placed here that among other things, punch
+ holes in the above policy for specific services. -->
+ <includedir>system.d</includedir>
+
+ <!--
+ <includedir>/etc/dbus-1/system.d</includedir>
+ -->
+
+ <!-- This is included last so local configuration can override what's
+ in this standard file -->
+ <include ignore_missing="yes">/etc/dbus-1/system-local.conf</include>
+
+ <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>
+
+ <!-- For the session bus, override the default relatively-low limits
+ with essentially infinite limits, since the bus is just running
+ as the user anyway, using up bus resources is not something we need
+ to worry about. In some cases, we do set the limits lower than
+ "all available memory" if exceeding the limit is almost certainly a bug,
+ having the bus enforce a limit is nicer than a huge memory leak. But the
+ intent is that these limits should never be hit. -->
+
+ <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max -->
+ <!-- We do not override max_message_unix_fds here since the in-kernel
+ limit is also relatively low -->
+
+</busconfig>
diff --git a/src/tests/intg/data/ad_data.ldif b/src/tests/intg/data/ad_data.ldif
new file mode 100644
index 0000000..0d2ec44
--- /dev/null
+++ b/src/tests/intg/data/ad_data.ldif
@@ -0,0 +1,815 @@
+dn: cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: container
+cn: Users
+description: Default container for upgraded user accounts
+distinguishedName: cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923233930.0Z
+whenChanged: 20140923233930.0Z
+uSNCreated: 5696
+uSNChanged: 5696
+showInAdvancedViewOnly: FALSE
+name: Users
+objectGUID:: 6Gd2SrsmeEiT3Hmh/5hTqw==
+systemFlags: -1946157056
+objectCategory: cn=Container,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=CHILD1$,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: CHILD1$
+distinguishedName: cn=CHILD1$,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923224256.0Z
+whenChanged: 20160423221800.0Z
+uSNCreated: 20732
+uSNChanged: 2181674
+name: CHILD1$
+objectGUID:: ACE60RcYu0iZv4CMYPK+eg==
+userAccountControl: 2080
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 0
+pwdLastSet: 131059234804699243
+primaryGroupID: 513
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EUAQAAA==
+accountExpires: 9223372036854775807
+logonCount: 0
+sAMAccountName: CHILD1$
+sAMAccountType: 805306370
+objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=krbtgt,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: krbtgt
+description: Key Distribution Center Service Account
+distinguishedName: cn=krbtgt,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923185530.0Z
+uSNCreated: 12324
+memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+uSNChanged: 12723
+showInAdvancedViewOnly: TRUE
+name: krbtgt
+objectGUID:: F/Yrx8X81ESM6t14mMxcxA==
+userAccountControl: 514
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 0
+pwdLastSet: 130559892182968750
+primaryGroupID: 513
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8E9gEAAA==
+adminCount: 1
+accountExpires: 9223372036854775807
+logonCount: 0
+sAMAccountName: krbtgt
+sAMAccountType: 805306368
+servicePrincipalName: kadmin/changepw
+objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 20140923185530.0Z
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Domain Computers,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Domain Computers
+description: All workstations and servers joined to the domain
+distinguishedName: cn=Domain Computers,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923234018.0Z
+uSNCreated: 12330
+uSNChanged: 12332
+name: Domain Computers
+objectGUID:: 09VIVs7CDkOMTnLtMkZMUA==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EAwIAAA==
+sAMAccountName: Domain Computers
+sAMAccountType: 268435456
+groupType: -2147483646
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Domain Controllers,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Domain Controllers
+description: All domain controllers in the domain
+distinguishedName: cn=Domain Controllers,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923185530.0Z
+uSNCreated: 12333
+memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+uSNChanged: 12726
+name: Domain Controllers
+objectGUID:: a6OG+FLmnECf3fAe0a8o6w==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EBAIAAA==
+adminCount: 1
+sAMAccountName: Domain Controllers
+sAMAccountType: 268435456
+groupType: -2147483646
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 20140923185530.0Z
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Schema Admins,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Schema Admins
+description: Designated administrators of the schema
+member: cn=Administrator,cn=Users,dc=example,dc=com
+distinguishedName: cn=Schema Admins,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923185530.0Z
+uSNCreated: 12336
+memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+uSNChanged: 12708
+name: Schema Admins
+objectGUID:: ONs7cn0OF0uEip0yMnLv2Q==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EBgIAAA==
+adminCount: 1
+sAMAccountName: Schema Admins
+sAMAccountType: 268435456
+groupType: -2147483640
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 20140923185530.0Z
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Enterprise Admins,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Enterprise Admins
+description: Designated administrators of the enterprise
+member: cn=Administrator,cn=Users,dc=example,dc=com
+distinguishedName: cn=Enterprise Admins,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923185530.0Z
+uSNCreated: 12339
+memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+memberOf: cn=Administrators,cn=Builtin,dc=example,dc=com
+uSNChanged: 12712
+name: Enterprise Admins
+objectGUID:: rD6jEoiL8U6huv7c/OJPwg==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EBwIAAA==
+adminCount: 1
+sAMAccountName: Enterprise Admins
+sAMAccountType: 268435456
+groupType: -2147483640
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 20140923185530.0Z
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Cert Publishers,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Cert Publishers
+description: Members of this group are permitted to publish certificates to the directory
+member: cn=PLUTO,OU=Domain Controllers,dc=example,dc=com
+distinguishedName: cn=Cert Publishers,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923191508.0Z
+uSNCreated: 12342
+memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+uSNChanged: 12749
+name: Cert Publishers
+objectGUID:: zWTUMdl6tEWA1J0QnPLkRQ==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EBQIAAA==
+sAMAccountName: Cert Publishers
+sAMAccountType: 536870912
+groupType: -2147483644
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Domain Admins,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Domain Admins
+description: Designated administrators of the domain
+member: cn=Administrator,cn=Users,dc=example,dc=com
+distinguishedName: cn=Domain Admins,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923185530.0Z
+uSNCreated: 12345
+memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+memberOf: cn=Administrators,cn=Builtin,dc=example,dc=com
+uSNChanged: 12711
+name: Domain Admins
+objectGUID:: YxI+YLrC3UeNNsmMnXGTlg==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EAAIAAA==
+adminCount: 1
+sAMAccountName: Domain Admins
+sAMAccountType: 268435456
+groupType: -2147483646
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 20140923185530.0Z
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Domain Users,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Domain Users
+description: All domain users
+distinguishedName: cn=Domain Users,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20150202222731.0Z
+uSNCreated: 12348
+memberOf: cn=Users,cn=Builtin,dc=example,dc=com
+uSNChanged: 213433
+name: Domain Users
+objectGUID:: JRHvlJXoU0+LOYXs3vESow==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EAQIAAA==
+sAMAccountName: Domain Users
+sAMAccountType: 268435456
+groupType: -2147483646
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+msSFU30NisDomain: example
+gidNumber: 100000
+
+dn: cn=Domain Guests,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Domain Guests
+description: All domain guests
+distinguishedName: cn=Domain Guests,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923234018.0Z
+uSNCreated: 12351
+memberOf: cn=Guests,cn=Builtin,dc=example,dc=com
+uSNChanged: 12353
+name: Domain Guests
+objectGUID:: Rx/t/vuPwUGOMoprY1KFog==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EAgIAAA==
+sAMAccountName: Domain Guests
+sAMAccountType: 268435456
+groupType: -2147483646
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Group Policy Creator Owners,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Group Policy Creator Owners
+description: Members in this group can modify group policy for the domain
+member: cn=Administrator,cn=Users,dc=example,dc=com
+distinguishedName: cn=Group Policy Creator Owners,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923234018.0Z
+uSNCreated: 12354
+memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+uSNChanged: 12391
+name: Group Policy Creator Owners
+objectGUID:: V3HfwcWfZ0yv1br3tRP6bA==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ECAIAAA==
+sAMAccountName: Group Policy Creator Owners
+sAMAccountType: 268435456
+groupType: -2147483646
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=RAS and IAS Servers,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: RAS and IAS Servers
+description: Servers in this group can access remote access properties of users
+distinguishedName: cn=RAS and IAS Servers,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923234018.0Z
+uSNCreated: 12357
+uSNChanged: 12359
+name: RAS and IAS Servers
+objectGUID:: PHyDebZK7UKVG9HG+mT8ng==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EKQIAAA==
+sAMAccountName: RAS and IAS Servers
+sAMAccountType: 536870912
+groupType: -2147483644
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Allowed ROdc Password Replication Group,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Allowed ROdc Password Replication Group
+description: Members in this group can have their passwords replicated to all read-only domain controllers in the domain
+distinguishedName: cn=Allowed ROdc Password Replication Group,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923234018.0Z
+uSNCreated: 12402
+uSNChanged: 12404
+name: Allowed ROdc Password Replication Group
+objectGUID:: pKN3Txn0SUenHm8Z58ZQYA==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EOwIAAA==
+sAMAccountName: Allowed ROdc Password Replication Group
+sAMAccountType: 536870912
+groupType: -2147483644
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Denied ROdc Password Replication Group
+description: Members in this group cannot have their passwords replicated to any read-only domain controllers in the domain
+member: cn=Read-only Domain Controllers,cn=Users,dc=example,dc=com
+member: cn=Group Policy Creator Owners,cn=Users,dc=example,dc=com
+member: cn=Domain Admins,cn=Users,dc=example,dc=com
+member: cn=Cert Publishers,cn=Users,dc=example,dc=com
+member: cn=Enterprise Admins,cn=Users,dc=example,dc=com
+member: cn=Schema Admins,cn=Users,dc=example,dc=com
+member: cn=Domain Controllers,cn=Users,dc=example,dc=com
+member: cn=krbtgt,cn=Users,dc=example,dc=com
+distinguishedName: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923234018.0Z
+uSNCreated: 12405
+uSNChanged: 12433
+name: Denied ROdc Password Replication Group
+objectGUID:: OoOtLxLbXUSdCGKeGvzc7Q==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EPAIAAA==
+sAMAccountName: Denied ROdc Password Replication Group
+sAMAccountType: 536870912
+groupType: -2147483644
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Read-only Domain Controllers,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Read-only Domain Controllers
+description: Members of this group are Read-Only Domain Controllers in the domain
+distinguishedName: cn=Read-only Domain Controllers,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923185530.0Z
+uSNCreated: 12419
+memberOf: cn=Denied ROdc Password Replication Group,cn=Users,dc=example,dc=com
+uSNChanged: 12725
+name: Read-only Domain Controllers
+objectGUID:: GoeeiCJ87UqBN3C9MhqQ3w==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ECQIAAA==
+adminCount: 1
+sAMAccountName: Read-only Domain Controllers
+sAMAccountType: 268435456
+groupType: -2147483646
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 20140923185530.0Z
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Enterprise Read-only Domain Controllers,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: Enterprise Read-only Domain Controllers
+description: Members of this group are Read-Only Domain Controllers in the enterprise
+distinguishedName: cn=Enterprise Read-only Domain Controllers,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234018.0Z
+whenChanged: 20140923234018.0Z
+uSNCreated: 12429
+uSNChanged: 12431
+name: Enterprise Read-only Domain Controllers
+objectGUID:: qHRH+tAgFUy7660VnrFpTA==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8E8gEAAA==
+sAMAccountName: Enterprise Read-only Domain Controllers
+sAMAccountType: 268435456
+groupType: -2147483640
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=DnsAdmins,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: DnsAdmins
+description: DNS Administrators Group
+distinguishedName: cn=DnsAdmins,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234058.0Z
+whenChanged: 20140923234058.0Z
+uSNCreated: 12459
+uSNChanged: 12461
+name: DnsAdmins
+objectGUID:: w4cyv6dWNEGQao3mL5RpTA==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ETQQAAA==
+sAMAccountName: DnsAdmins
+sAMAccountType: 536870912
+groupType: -2147483644
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=DnsUpdateProxy,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: DnsUpdateProxy
+description: DNS clients who are permitted to perform dynamic updates on behalf of some other clients (such as DHCP servers).
+distinguishedName: cn=DnsUpdateProxy,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923234058.0Z
+whenChanged: 20140923234058.0Z
+uSNCreated: 12464
+uSNChanged: 12464
+name: DnsUpdateProxy
+objectGUID:: LMyHGT2RuEG+IGrGL80qMg==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ETgQAAA==
+sAMAccountName: DnsUpdateProxy
+sAMAccountType: 268435456
+groupType: -2147483646
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=user1_dom1-19661,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: user1_dom1-19661
+givenName: user1_dom1-19661
+distinguishedName: cn=user1_dom1-19661,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20160517121016.0Z
+whenChanged: 20160517121017.0Z
+displayName: user1_dom1-19661
+uSNCreated: 2223663
+memberOf: cn=group1_dom1-19661,cn=Users,dc=example,dc=com
+uSNChanged: 2223667
+name: user1_dom1-19661
+objectGUID:: qyJVkvQrRUyig6rpPsXNUw==
+userAccountControl: 512
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 0
+pwdLastSet: 131079606172284326
+primaryGroupID: 513
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EeUMBAA==
+accountExpires: 0
+logonCount: 0
+sAMAccountName: user1_dom1-19661
+sAMAccountType: 805306368
+userPrincipalName: user1_dom1-19661@example.com
+objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+uid: user1_dom1-19661
+msSFU30Name: user1_dom1-19661
+
+dn: cn=group1_dom1-19661,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: group1_dom1-19661
+member: cn=user1_dom1-19661,cn=Users,dc=example,dc=com
+distinguishedName: cn=group1_dom1-19661,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20160517121017.0Z
+whenChanged: 20160517121018.0Z
+uSNCreated: 2223669
+uSNChanged: 2223673
+name: group1_dom1-19661
+objectGUID:: 8BulXIrOCkmlc6HgV+PAvw==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EekMBAA==
+sAMAccountName: group1_dom1-19661
+sAMAccountType: 268435456
+groupType: -2147483640
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=user2_dom1-19661,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: user2_dom1-19661
+givenName: user2_dom1-19661
+distinguishedName: cn=user2_dom1-19661,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20160517121018.0Z
+whenChanged: 20160517121019.0Z
+displayName: user2_dom1-19661
+uSNCreated: 2223676
+memberOf: cn=group2_dom2-19661,cn=Users,dc=example_tree,dc=com
+uSNChanged: 2223680
+name: user2_dom1-19661
+objectGUID:: YSnhUKGpFUC+SqxUvvXugA==
+userAccountControl: 512
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 0
+pwdLastSet: 131079606188221826
+primaryGroupID: 513
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8Ee0MBAA==
+accountExpires: 0
+logonCount: 0
+sAMAccountName: user2_dom1-19661
+sAMAccountType: 805306368
+userPrincipalName: user2_dom1-19661@example.com
+objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+uid: user2_dom1-19661
+msSFU30Name: user2_dom1-19661
+
+dn: cn=group3_dom1-19661,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: group3_dom1-19661
+member: cn=user3_dom3-19661,cn=Users,dc=child1,dc=example,dc=com
+distinguishedName: cn=group3_dom1-19661,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20160517121145.0Z
+whenChanged: 20160517121146.0Z
+uSNCreated: 2223750
+uSNChanged: 2223754
+name: group3_dom1-19661
+objectGUID:: 7bIPzON/JEKmGsVlRmhU3g==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EfEMBAA==
+sAMAccountName: group3_dom1-19661
+sAMAccountType: 268435456
+groupType: -2147483640
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=TelnetClients,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: TelnetClients
+distinguishedName: cn=TelnetClients,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923184913.0Z
+whenChanged: 20140923184913.0Z
+uSNCreated: 12704
+uSNChanged: 12706
+name: TelnetClients
+objectGUID:: pen22ZTevU2Rb+8+krexQA==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ETwQAAA==
+sAMAccountName: TelnetClients
+sAMAccountType: 536870912
+groupType: -2147483644
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=SSSDAD_TREE$,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: SSSDAD_TREE$
+distinguishedName: cn=SSSDAD_TREE$,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20141002150546.0Z
+whenChanged: 20160504032042.0Z
+uSNCreated: 31148
+uSNChanged: 2196300
+name: SSSDAD_TREE$
+objectGUID:: SYm5qEjtH0SySg5aQw6XNA==
+userAccountControl: 2080
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 0
+pwdLastSet: 131068056421414345
+primaryGroupID: 513
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8EUQQAAA==
+accountExpires: 9223372036854775807
+logonCount: 0
+sAMAccountName: SSSDAD_TREE$
+sAMAccountType: 805306370
+objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=user1_dom1-17775,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: user1_dom1-17775
+givenName: user1_dom1-17775
+distinguishedName: cn=user1_dom1-17775,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20160517104141.0Z
+whenChanged: 20160517105245.0Z
+displayName: user1_dom1-17775
+uSNCreated: 2220148
+memberOf: cn=group1_dom1-17775,cn=Users,dc=example,dc=com
+uSNChanged: 2220869
+name: user1_dom1-17775
+objectGUID:: dCwgefPZTEaA5Gq7fuH9eQ==
+userAccountControl: 512
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 131079562057827406
+pwdLastSet: 131079557906733656
+primaryGroupID: 513
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ESUMBAA==
+accountExpires: 0
+logonCount: 46
+sAMAccountName: user1_dom1-17775
+sAMAccountType: 805306368
+userPrincipalName: user1_dom1-17775@example.com
+objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+lastLogonTimestamp: 131079557817046156
+uid: user1_dom1-17775
+msSFU30Name: user1_dom1-17775
+
+dn: cn=group1_dom1-17775,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: group1_dom1-17775
+member: cn=user1_dom1-17775,cn=Users,dc=example,dc=com
+distinguishedName: cn=group1_dom1-17775,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20160517104143.0Z
+whenChanged: 20160517104143.0Z
+uSNCreated: 2220154
+uSNChanged: 2220158
+name: group1_dom1-17775
+objectGUID:: UfJpBGL6gE2d5hqzqNlRGQ==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ESkMBAA==
+sAMAccountName: group1_dom1-17775
+sAMAccountType: 268435456
+groupType: -2147483640
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=user2_dom1-17775,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: user2_dom1-17775
+givenName: user2_dom1-17775
+distinguishedName: cn=user2_dom1-17775,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20160517104143.0Z
+whenChanged: 20160517105302.0Z
+displayName: user2_dom1-17775
+uSNCreated: 2220161
+memberOf: cn=group2_dom2-17775,cn=Users,dc=example_tree,dc=com
+uSNChanged: 2220886
+name: user2_dom1-17775
+objectGUID:: r22lHyI8Y0eMVzeTH2dzoQ==
+userAccountControl: 512
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 131079561237671156
+pwdLastSet: 131079553041264906
+primaryGroupID: 513
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ES0MBAA==
+accountExpires: 0
+logonCount: 14
+sAMAccountName: user2_dom1-17775
+sAMAccountType: 805306368
+userPrincipalName: user2_dom1-17775@example.com
+objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+lastLogonTimestamp: 131079559824702406
+uid: user2_dom1-17775
+msSFU30Name: user2_dom1-17775
+
+dn: cn=group3_dom1-17775,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: group
+cn: group3_dom1-17775
+member: cn=user3_dom3-17775,cn=Users,dc=child1,dc=example,dc=com
+distinguishedName: cn=group3_dom1-17775,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20160517104312.0Z
+whenChanged: 20160517104312.0Z
+uSNCreated: 2220239
+uSNChanged: 2220243
+name: group3_dom1-17775
+objectGUID:: jkkwGJCVb0K4OCjHZVDmdQ==
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8ETEMBAA==
+sAMAccountName: group3_dom1-17775
+sAMAccountType: 268435456
+groupType: -2147483640
+objectCategory: cn=Group,cn=Schema,cn=Configuration,dc=example,dc=com
+dSCorePropagationData: 16010101000000.0Z
+
+dn: cn=Administrator,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: Administrator
+description: Built-in account for administering the computer/domain
+distinguishedName: cn=Administrator,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923233931.0Z
+whenChanged: 20160510092815.0Z
+uSNCreated: 8196
+memberOf: cn=Group Policy Creator Owners,cn=Users,dc=example,dc=com
+memberOf: cn=Domain Admins,cn=Users,dc=example,dc=com
+memberOf: cn=Enterprise Admins,cn=Users,dc=example,dc=com
+memberOf: cn=Schema Admins,cn=Users,dc=example,dc=com
+memberOf: cn=Administrators,cn=Builtin,dc=example,dc=com
+uSNChanged: 2204950
+name: Administrator
+objectGUID:: QeHMqu/QPEyjJ+KQEqcKFw==
+userAccountControl: 66048
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 131074379403763791
+lastLogoff: 0
+lastLogon: 131079606125409326
+logonHours:: ////////////////////////////
+pwdLastSet: 130553133586093750
+primaryGroupID: 513
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8E9AEAAA==
+adminCount: 1
+accountExpires: 0
+logonCount: 7477
+sAMAccountName: Administrator
+sAMAccountType: 805306368
+objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 20140923185530.0Z
+dSCorePropagationData: 16010101000000.0Z
+lastLogonTimestamp: 131073460951421705
+
+dn: cn=Guest,cn=Users,dc=example,dc=com
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+cn: Guest
+description: Built-in account for guest access to the computer/domain
+distinguishedName: cn=Guest,cn=Users,dc=example,dc=com
+instanceType: 4
+whenCreated: 20140923233931.0Z
+whenChanged: 20140923233931.0Z
+uSNCreated: 8197
+memberOf: cn=Guests,cn=Builtin,dc=example,dc=com
+uSNChanged: 8197
+name: Guest
+objectGUID:: pZVy9Q6Eh02XuYDEXDE9Cg==
+userAccountControl: 66082
+badPwdCount: 0
+codePage: 0
+countryCode: 0
+badPasswordTime: 0
+lastLogoff: 0
+lastLogon: 0
+pwdLastSet: 0
+primaryGroupID: 514
+objectSid:: AQUAAAAAAAUVAAAADcfLTVzC66zo0l8E9QEAAA==
+accountExpires: 9223372036854775807
+logonCount: 0
+sAMAccountName: Guest
+sAMAccountType: 805306368
+objectCategory: cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com
+isCriticalSystemObject: TRUE
+dSCorePropagationData: 16010101000000.0Z
diff --git a/src/tests/intg/data/ad_schema.ldif b/src/tests/intg/data/ad_schema.ldif
new file mode 100644
index 0000000..6ae533a
--- /dev/null
+++ b/src/tests/intg/data/ad_schema.ldif
@@ -0,0 +1,42 @@
+dn: cn=ad,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: ad
+structuralObjectClass: olcSchemaConfig
+olcAttributeTypes: {0}( 1.2.840.113556.1.4.750 NAME 'groupType' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {1}( 1.2.840.113556.1.4.221 NAME 'sAMAccountName' EQUALITY caseIgnoreMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: {2}( 1.2.840.113556.1.4.35 NAME 'employeeID' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {3}( 1.2.840.113556.1.2.1 NAME 'instanceType' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {4}( 1.2.840.113556.1.4.782 NAME 'objectCategory' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: {5}( 1.2.840.113556.1.2.2 NAME 'whenCreated' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: {6}( 1.2.840.113556.1.2.3 NAME 'whenChanged' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: {7}( 1.2.840.113556.1.2.19 NAME 'uSNCreated' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {8}( 1.2.840.113556.1.2.120 NAME 'uSNChanged' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {9}( 1.2.840.113556.1.2.169 NAME 'showInAdvancedViewOnly' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
+olcAttributeTypes: {10}( 1.2.840.113556.1.4.2 NAME 'objectGUID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE)
+olcAttributeTypes: {11}( 1.2.840.113556.1.4.375 NAME 'systemFlags' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {12}( 1.2.840.113556.1.4.868 NAME 'isCriticalSystemObject' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
+olcAttributeTypes: {13}( 1.2.840.113556.1.4.1357 NAME 'dSCorePropagationData' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+olcAttributeTypes: {14}( 1.2.840.113556.1.4.8 NAME 'userAccountControl' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {15}( 1.2.840.113556.1.4.12 NAME 'badPwdCount' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {16}( 1.2.840.113556.1.4.146 NAME 'objectSid' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE)
+olcAttributeTypes: {17}( 1.2.840.113556.1.2.102 NAME 'memberOf' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
+olcAttributeTypes: {18}( 1.2.840.113556.1.4.16 NAME 'codePage' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {19}( 1.2.840.113556.1.4.302 NAME 'sAMAccountType' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {20}( 1.2.840.113556.1.4.150 NAME 'adminCount' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {21}( 1.2.840.113556.1.4.25 NAME 'countryCode' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {22}( 1.2.840.113556.1.4.49 NAME 'badPasswordTime' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: {23}( 1.2.840.113556.1.6.18.1.339 NAME 'msSFU30NisDomain' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: {24}( 1.2.840.113556.1.4.51 NAME 'lastLogoff' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {25}( 1.2.840.113556.1.4.52 NAME 'lastLogon' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {26}( 1.2.840.113556.1.4.96 NAME 'pwdLastSet' EQUALITY numericStringMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.36' SINGLE-VALUE )
+olcAttributeTypes: {27}( 1.2.840.113556.1.4.64 NAME 'logonHours' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE)
+olcAttributeTypes: {28}( 1.2.840.113556.1.4.98 NAME 'primaryGroupID' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {29}( 1.2.840.113556.1.4.159 NAME 'accountExpires' EQUALITY numericStringMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.36' SINGLE-VALUE )
+olcAttributeTypes: {30}( 1.2.840.113556.1.4.169 NAME 'logonCount' EQUALITY integerMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: {31}( 1.2.840.113556.1.4.771 NAME 'servicePrincipalName' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: {31}( 1.2.840.113556.1.4.656 NAME 'userPrincipalName' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: {32}( 1.2.840.113556.1.6.18.1.309 NAME 'msSFU30Name' EQUALITY caseExactMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: {33}( 1.2.840.113556.1.4.1696 NAME 'lastLogonTimestamp' EQUALITY numericStringMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.36' SINGLE-VALUE )
+olcObjectClasses: {1}( 1.2.840.113556.1.5.9 NAME 'user' DESC 'a user' SUP organizationalPerson STRUCTURAL MUST ( cn $ objectSid $ instanceType $ sAMAccountName $ objectCategory ) MAY ( userPassword $ description $ distinguishedName $ name $ userAccountControl $ badPwdCount $ memberOf $ codePage $ sAMAccountType $ adminCount $ countryCode $ dSCorePropagationData $ whenCreated $ whenChanged $ uSNCreated $ uSNChanged $ badPasswordTime $ msSFU30NisDomain $ lastLogoff $ lastLogon $ objectGUID $ pwdLastSet $ logonCount $ logonHours $ primaryGroupID $ accountExpires $ isCriticalSystemObject $ servicePrincipalName $ userPrincipalName $ msSFU30Name $ lastLogonTimestamp $ showInAdvancedViewOnly $ givenName $ displayName $ uid ) )
+olcObjectClasses: {2}( 1.2.840.113556.1.5.8 NAME 'group' DESC 'a group of users' SUP top STRUCTURAL MUST ( groupType $ cn $ objectSid $ instanceType $ sAMAccountName $ objectCategory ) MAY ( member $ description $ distinguishedName $ name $ memberOf $ sAMAccountType $ adminCount $ dSCorePropagationData $ whenCreated $ whenChanged $ uSNCreated $ uSNChanged $ msSFU30NisDomain $ objectGUID $ isCriticalSystemObject $ gidNumber ) )
+olcObjectClasses: {3}( 1.2.840.113556.1.3.23 NAME 'container' DESC 'asdasd' SUP top STRUCTURAL MUST ( cn $ instanceType $ objectCategory ) MAY ( whenCreated $ whenChanged $ uSNCreated $ uSNChanged $ showInAdvancedViewOnly $ objectGUID $ systemFlags $ isCriticalSystemObject $ dSCorePropagationData $ description $ distinguishedName $ name ) )
diff --git a/src/tests/intg/data/cert_schema.ldif b/src/tests/intg/data/cert_schema.ldif
new file mode 100644
index 0000000..0003c66
--- /dev/null
+++ b/src/tests/intg/data/cert_schema.ldif
@@ -0,0 +1,11 @@
+dn: cn=cert,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: cert
+olcAttributeTypes: ( 1.2.840.113556.1.4.645 NAME 'userCert'
+ DESC 'MANDATORY: X.509 user certificate'
+ EQUALITY octetStringMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
+olcObjectClasses: ( 1.2.840.113556.1.3.46 NAME 'mailRecipient' SUP top AUXILIARY
+ DESC 'MANDATORY: X.509 objectclass'
+ MAY ( userCert $ uid )
+ )
diff --git a/src/tests/intg/data/cwrap-dbus-system.conf.in b/src/tests/intg/data/cwrap-dbus-system.conf.in
new file mode 100644
index 0000000..7369054
--- /dev/null
+++ b/src/tests/intg/data/cwrap-dbus-system.conf.in
@@ -0,0 +1,83 @@
+<!-- This configuration file controls the systemwide message bus.
+ Add a system-local.conf and edit that rather than changing this
+ file directly. -->
+
+<!-- Note that there are any number of ways you can hose yourself
+ security-wise by screwing up this file; in particular, you
+ probably don't want to listen on any more addresses, add any more
+ auth mechanisms, run as a different user, etc. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+ <!-- Our well-known bus type, do not change this -->
+ <type>system</type>
+
+ <!-- If we fork, keep the user's original umask to avoid affecting
+ the behavior of child processes. -->
+ <keep_umask/>
+
+
+ <!-- Fork into daemon mode -->
+ <fork/>
+
+ <!-- We use system service launching using a helper -->
+ <standard_system_servicedirs/>
+ <servicedir>@dbusservicedir@</servicedir>
+
+
+ <!-- Write a pid file -->
+ <pidfile>@runstatedir@/dbus/messagebus.pid</pidfile>
+
+ <!-- On Unix systems, the most secure authentication mechanism is
+ EXTERNAL, which uses credential-passing over Unix sockets.
+
+ This authentication mechanism is not available on Windows,
+ is not suitable for use with the tcp: or nonce-tcp: transports,
+ and will not work on obscure flavours of Unix that do not have
+ a supported credentials-passing mechanism. On those platforms/transports,
+ comment out the <auth> element to allow fallback to DBUS_COOKIE_SHA1. -->
+ <auth>EXTERNAL</auth>
+
+ <!-- Only listen on a local socket. (abstract=/path/to/socket
+ means use abstract namespace, don't really create filesystem
+ file; only Linux supports this. Use path=/whatever on other
+ systems.) -->
+ <listen>unix:path=@runstatedir@/dbus/system_bus_socket</listen>
+ <policy context="default">
+ <!-- Allow everything to be sent -->
+ <allow send_destination="*" eavesdrop="true"/>
+ <!-- Allow everything to be received -->
+ <allow eavesdrop="true"/>
+ <!-- Allow anyone to own anything -->
+ <allow own="*"/>
+ </policy>
+
+ <!-- Config files are placed here that among other things, punch
+ holes in the above policy for specific services. -->
+ <includedir>system.d</includedir>
+
+ <!--
+ <includedir>/etc/dbus-1/system.d</includedir>
+ -->
+
+ <!-- This is included last so local configuration can override what's
+ in this standard file -->
+ <include ignore_missing="yes">/etc/dbus-1/system-local.conf</include>
+
+ <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>
+
+ <!-- For the session bus, override the default relatively-low limits
+ with essentially infinite limits, since the bus is just running
+ as the user anyway, using up bus resources is not something we need
+ to worry about. In some cases, we do set the limits lower than
+ "all available memory" if exceeding the limit is almost certainly a bug,
+ having the bus enforce a limit is nicer than a huge memory leak. But the
+ intent is that these limits should never be hit. -->
+
+ <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max -->
+ <!-- We do not override max_message_unix_fds here since the in-kernel
+ limit is also relatively low -->
+
+</busconfig>
diff --git a/src/tests/intg/data/ssh_schema.ldif b/src/tests/intg/data/ssh_schema.ldif
new file mode 100644
index 0000000..efe0570
--- /dev/null
+++ b/src/tests/intg/data/ssh_schema.ldif
@@ -0,0 +1,11 @@
+dn: cn=openssh-lpk,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: openssh-lpk
+olcAttributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
+ DESC 'MANDATORY: OpenSSH Public key'
+ EQUALITY octetStringMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
+olcObjectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
+ DESC 'MANDATORY: OpenSSH LPK objectclass'
+ MAY ( sshPublicKey $ uid )
+ )
diff --git a/src/tests/intg/data/sudo_schema.ldif b/src/tests/intg/data/sudo_schema.ldif
new file mode 100644
index 0000000..8c1f4e3
--- /dev/null
+++ b/src/tests/intg/data/sudo_schema.ldif
@@ -0,0 +1,11 @@
+dn: cn=sudo,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: sudo
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.1 NAME 'sudoUser' DESC 'User(s) who may run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.2 NAME 'sudoHost' DESC 'Host(s) who may run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.3 NAME 'sudoCommand' DESC 'Command(s) to be executed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.4 NAME 'sudoRunAs' DESC 'User(s) impersonated by sudo (deprecated)' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.5 NAME 'sudoOption' DESC 'Options(s) followed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.6 NAME 'sudoRunAsUser' DESC 'User(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.7 NAME 'sudoRunAsGroup' DESC 'Group(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcObjectClasses: ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL DESC 'Sudoer Entries' MUST ( cn ) MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoRunAsUser $ sudoRunAsGroup $ sudoOption $ description ) )
diff --git a/src/tests/intg/ds.py b/src/tests/intg/ds.py
new file mode 100644
index 0000000..f4631b2
--- /dev/null
+++ b/src/tests/intg/ds.py
@@ -0,0 +1,59 @@
+#
+# Abstract directory server instance class
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import ldap
+
+
+class DS(object):
+ """Abstract directory server instance."""
+
+ def __init__(self, dir, port, base_dn, admin_rdn, admin_pw):
+ """
+ Initialize the instance.
+
+ Arguments:
+ dir Path to the root of the filesystem hierarchy to create
+ the instance under.
+ port TCP port on localhost to bind the server to.
+ base_dn Base DN.
+ admin_rdn Administrator DN, relative to BASE_DN.
+ admin_pw Administrator password.
+ """
+ self.dir = dir
+ self.port = port
+ self.ldap_url = "ldap://localhost:" + str(self.port)
+ self.base_dn = base_dn
+ self.admin_rdn = admin_rdn
+ self.admin_dn = admin_rdn + "," + base_dn
+ self.admin_pw = admin_pw
+
+ def setup(self):
+ """Setup the instance"""
+ raise NotImplementedError()
+
+ def teardown(self):
+ """Teardown the instance"""
+ raise NotImplementedError()
+
+ def bind(self):
+ """Connect to the server and bind as admin, return connection."""
+ conn = ldap.initialize(self.ldap_url)
+ conn.simple_bind_s(self.admin_dn, self.admin_pw)
+ return conn
diff --git a/src/tests/intg/ds_openldap.py b/src/tests/intg/ds_openldap.py
new file mode 100644
index 0000000..1581343
--- /dev/null
+++ b/src/tests/intg/ds_openldap.py
@@ -0,0 +1,398 @@
+#
+# OpenLDAP directory server instance class
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+# Author: Lukas Slebodnik <lslebodn@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import hashlib
+import base64
+import time
+import ldap
+import os
+import errno
+import signal
+import shutil
+import subprocess
+from util import unindent, first_dir
+from ds import DS
+
+try:
+ from urllib import quote as url_quote
+except ImportError:
+ from urllib.parse import quote as url_quote
+
+
+def hash_password(password):
+ """Generate userPassword value for a password."""
+ salt = os.urandom(4)
+ hash = hashlib.sha1(password.encode('utf-8'))
+ hash.update(salt)
+ hash_base64 = base64.standard_b64encode(hash.digest() + salt)
+ return "{SSHA}" + hash_base64.decode('utf-8')
+
+
+class DSOpenLDAP(DS):
+ """OpenLDAP directory server instance."""
+
+ def __init__(self, dir, port, base_dn, admin_rdn, admin_pw):
+ """
+ Initialize the instance.
+
+ Arguments:
+ dir Path to the root of the filesystem hierarchy to create
+ the instance under.
+ port TCP port on localhost to bind the server to.
+ base_dn Base DN.
+ admin_rdn Administrator DN, relative to BASE_DN.
+ admin_pw Administrator password.
+ """
+ DS.__init__(self, dir, port, base_dn, admin_rdn, admin_pw)
+ self.run_dir = self.dir + "/var/run/ldap"
+ self.pid_path = self.run_dir + "/slapd.pid"
+ self.conf_dir = self.dir + "/etc/ldap"
+ self.conf_slapd_d_dir = self.conf_dir + "/slapd.d"
+ self.data_dir = self.dir + "/var/lib/ldap"
+
+ def _setup_config(self):
+ """Setup the instance initial configuration."""
+ dist_lib_dir = first_dir("/usr/lib64/openldap",
+ "/usr/lib/openldap",
+ "/usr/lib/ldap")
+ dist_conf_dir = first_dir("/etc/ldap",
+ "/etc/openldap")
+ args_file = self.run_dir + "/slapd.args"
+ admin_pw_hash = hash_password(self.admin_pw)
+ uid = os.geteuid()
+ gid = os.getegid()
+
+ #
+ # Add configuration
+ #
+ config = unindent("""
+ dn: cn=config
+ objectClass: olcGlobal
+ cn: config
+ olcPidFile: {self.pid_path}
+ olcArgsFile: {args_file}
+ # Read slapd.conf(5) for possible values
+ olcLogLevel: none
+
+ # Frontend settings
+ dn: olcDatabase={{-1}}frontend,cn=config
+ objectClass: olcDatabaseConfig
+ objectClass: olcFrontendConfig
+ olcDatabase: {{-1}}frontend
+ # The maximum number of entries that is returned for
+ # a search operation
+ olcSizeLimit: 500
+ # Allow unlimited access to local connection from the local root
+ olcAccess: {{0}}to * by dn.exact=gidNumber={gid}+uidNumber={uid},
+ cn=peercred,cn=external,cn=auth manage by * break
+ # Allow unauthenticated read access for schema and
+ # base DN autodiscovery
+ olcAccess: {{1}}to dn.exact="" by * read
+ olcAccess: {{2}}to dn.base="cn=Subschema" by * read
+
+ # Config db settings
+ dn: olcDatabase=config,cn=config
+ objectClass: olcDatabaseConfig
+ olcDatabase: config
+ # Allow unlimited access to local connection from the local root
+ olcAccess: to * by dn.exact=gidNumber={gid}+uidNumber={uid},
+ cn=peercred,cn=external,cn=auth manage by * break
+ olcRootDN: {self.admin_rdn},cn=config
+ olcRootPW: {admin_pw_hash}
+
+ # Load schemas
+ dn: cn=schema,cn=config
+ objectClass: olcSchemaConfig
+ cn: schema
+
+ include: file://{dist_conf_dir}/schema/core.ldif
+ include: file://{dist_conf_dir}/schema/cosine.ldif
+ include: file://{dist_conf_dir}/schema/nis.ldif
+ include: file://{dist_conf_dir}/schema/inetorgperson.ldif
+
+ # Load module
+ dn: cn=module{{0}},cn=config
+ objectClass: olcModuleList
+ cn: module{{0}}
+ olcModulePath: {dist_lib_dir}
+ olcModuleLoad: back_mdb
+
+ # Set defaults for the backend
+ dn: olcBackend=mdb,cn=config
+ objectClass: olcBackendConfig
+ olcBackend: mdb
+
+ # The database definition.
+ dn: olcDatabase=mdb,cn=config
+ objectClass: olcDatabaseConfig
+ objectClass: olcMdbConfig
+ olcDatabase: mdb
+ olcDbCheckpoint: 512 30
+ olcLastMod: TRUE
+ olcSuffix: {self.base_dn}
+ olcDbDirectory: {self.data_dir}
+ olcRootDN: {self.admin_dn}
+ olcRootPW: {admin_pw_hash}
+ olcDbIndex: objectClass eq
+ olcDbIndex: cn,uid eq
+ olcDbIndex: uidNumber,gidNumber eq
+ olcDbIndex: member,memberUid eq
+ olcAccess: to attrs=userPassword,shadowLastChange
+ by self write
+ by anonymous auth
+ by * none
+ olcAccess: to dn.base="" by * read
+ olcAccess: to *
+ by * read
+ """).format(**locals())
+
+ slapadd = subprocess.Popen(
+ ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config"],
+ stdin=subprocess.PIPE, close_fds=True
+ )
+ slapadd.communicate(config.encode('utf-8'))
+ if slapadd.returncode != 0:
+ raise Exception("Failed to add configuration with slapadd")
+
+ #
+ # Add database config (example from distribution)
+ #
+ db_config = unindent("""
+ # One 0.25 GB cache
+ set_cachesize 0 268435456 1
+
+ # Transaction Log settings
+ set_lg_regionmax 262144
+ set_lg_bsize 2097152
+ """)
+ db_config_file = open(self.data_dir + "/DB_CONFIG", "w")
+ db_config_file.write(db_config)
+ db_config_file.close()
+
+ # Import ad schema
+ subprocess.check_call(
+ ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config",
+ "-l", "data/ssh_schema.ldif"],
+ )
+
+ # Import sudo schema
+ subprocess.check_call(
+ ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config",
+ "-l", "data/sudo_schema.ldif"],
+ )
+
+ # Import cert schema
+ subprocess.check_call(
+ ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config",
+ "-l", "data/cert_schema.ldif"],
+ )
+
+ def _start_daemon(self):
+ """Start the instance."""
+ if subprocess.call(["slapd", "-F", self.conf_slapd_d_dir,
+ "-h", self.url_list]) != 0:
+ raise Exception("Failed to start slapd")
+
+ #
+ # Wait until it is available
+ #
+ attempt = 0
+ while True:
+ try:
+ ldap_conn = ldap.initialize(self.ldapi_url)
+ ldap_conn.simple_bind_s(self.admin_rdn + ",cn=config",
+ self.admin_pw)
+ ldap_conn.unbind_s()
+ ldap_conn = ldap.initialize(self.ldap_url)
+ ldap_conn.simple_bind_s(self.admin_dn, self.admin_pw)
+ ldap_conn.unbind_s()
+ break
+ except ldap.SERVER_DOWN:
+ pass
+ attempt = attempt + 1
+ if attempt > 30:
+ raise Exception("Failed to start slapd")
+ time.sleep(1)
+
+ def setup(self):
+ """Setup the instance."""
+ ldapi_socket = self.run_dir + "/ldapi"
+ self.ldapi_url = "ldapi://" + url_quote(ldapi_socket, "")
+ self.url_list = self.ldapi_url + " " + self.ldap_url
+
+ os.makedirs(self.conf_slapd_d_dir)
+ os.makedirs(self.run_dir)
+ os.makedirs(self.data_dir)
+
+ #
+ # Setup initial configuration
+ #
+ self._setup_config()
+
+ self._start_daemon()
+
+ #
+ # Relax requirement of member attribute presence in groupOfNames
+ #
+ modlist = [
+ (ldap.MOD_DELETE, "olcObjectClasses",
+ b"{7}( 2.5.6.9 NAME 'groupOfNames' "
+ b"DESC 'RFC2256: a group of names (DNs)' SUP top "
+ b"STRUCTURAL MUST ( member $ cn ) MAY ( businessCategory $ "
+ b"seeAlso $ owner $ ou $ o $ description ) )"),
+ (ldap.MOD_ADD, "olcObjectClasses",
+ b"{7}( 2.5.6.9 NAME 'groupOfNames' "
+ b"DESC 'RFC2256: a group of names (DNs)' SUP top "
+ b"STRUCTURAL MUST ( cn ) MAY ( member $ businessCategory $ "
+ b"seeAlso $ owner $ ou $ o $ description ) )"),
+ ]
+ ldap_conn = ldap.initialize(self.ldapi_url)
+ ldap_conn.simple_bind_s(self.admin_rdn + ",cn=config", self.admin_pw)
+ ldap_conn.modify_s("cn={0}core,cn=schema,cn=config", modlist)
+ ldap_conn.unbind_s()
+
+ #
+ # Add data
+ #
+ ldap_conn = ldap.initialize(self.ldap_url)
+ ldap_conn.simple_bind_s(self.admin_dn, self.admin_pw)
+ ldap_conn.add_s(self.base_dn, [
+ ("objectClass", [b"dcObject", b"organization"]),
+ ("o", b"Example Company"),
+ ])
+ ldap_conn.add_s("cn=Manager," + self.base_dn, [
+ ("objectClass", b"organizationalRole"),
+ ])
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies",
+ "Hosts", "Networks"):
+ ldap_conn.add_s("ou=" + ou + "," + self.base_dn, [
+ ("objectClass", [b"top", b"organizationalUnit"]),
+ ])
+ ldap_conn.add_s("ou=sudoers," + self.base_dn, [
+ ("objectClass", [b"top", b"organizationalUnit"]),
+ ])
+ ldap_conn.add_s("cn=testrule,ou=sudoers," + self.base_dn, [
+ ("objectClass", [b"top", b"sudoRole"]),
+ ("sudoUser", [b"tuser"]),
+ ])
+ ldap_conn.unbind_s()
+
+ def _stop_daemon(self):
+ """Stop the instance."""
+ # Wait for slapd to stop
+ try:
+ pid_file = open(self.pid_path, "r")
+ try:
+ os.kill(int(pid_file.read()), signal.SIGTERM)
+ finally:
+ pid_file.close()
+ attempt = 0
+ while os.path.isfile(self.pid_path):
+ attempt = attempt + 1
+ if attempt > 30:
+ raise Exception("Failed to stop slapd")
+ time.sleep(1)
+ except IOError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+ def teardown(self):
+ """Teardown the instance."""
+ self._stop_daemon()
+
+ for path in (self.conf_slapd_d_dir, self.run_dir, self.data_dir):
+ shutil.rmtree(path, True)
+
+
+class FakeAD(DSOpenLDAP):
+ """Fake Active Directory based on OpenLDAP directory server."""
+
+ def _setup_config(self):
+ """Setup the instance initial configuration."""
+
+ # Import ad schema
+ subprocess.check_call(
+ ["slapadd", "-F", self.conf_slapd_d_dir, "-b", "cn=config",
+ "-l", "data/ad_schema.ldif"],
+ )
+
+ def setup(self):
+ """Setup the instance."""
+ ldapi_socket = self.run_dir + "/ldapi"
+ self.ldapi_url = "ldapi://" + url_quote(ldapi_socket, "")
+ self.url_list = self.ldapi_url + " " + self.ldap_url
+
+ os.makedirs(self.conf_slapd_d_dir)
+ os.makedirs(self.run_dir)
+ os.makedirs(self.data_dir)
+
+ super(FakeAD, self)._setup_config()
+ self._setup_config()
+
+ # Start the daemon
+ super(FakeAD, self)._start_daemon()
+
+ # Relax requirement of surname attribute presence in person
+ modlist = [
+ (ldap.MOD_DELETE, "olcObjectClasses",
+ b"{4}( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top "
+ b"STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ "
+ b"telephoneNumber $ seeAlso $ description ) )"),
+ (ldap.MOD_ADD, "olcObjectClasses",
+ b"{4}( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top "
+ b"STRUCTURAL MUST ( cn ) MAY ( sn $ userPassword $ "
+ b"telephoneNumber $ seeAlso $ description ) )"),
+ ]
+ ldap_conn = ldap.initialize(self.ldapi_url)
+ ldap_conn.simple_bind_s(self.admin_rdn + ",cn=config", self.admin_pw)
+ ldap_conn.modify_s("cn={0}core,cn=schema,cn=config", modlist)
+ ldap_conn.unbind_s()
+
+ # restart daemon for reloading schema
+ super(FakeAD, self)._stop_daemon()
+ super(FakeAD, self)._start_daemon()
+
+ # Add data
+ ldap_conn = ldap.initialize(self.ldap_url)
+ ldap_conn.simple_bind_s(self.admin_dn, self.admin_pw)
+ ldap_conn.add_s(self.base_dn, [
+ ("objectClass", [b"dcObject", b"organization"]),
+ ("o", b"Example Company"),
+ ])
+ ldap_conn.add_s("cn=Manager," + self.base_dn, [
+ ("objectClass", b"organizationalRole"),
+ ])
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"):
+ ldap_conn.add_s("ou=" + ou + "," + self.base_dn, [
+ ("objectClass", [b"top", b"organizationalUnit"]),
+ ])
+ ldap_conn.unbind_s()
+
+ # import data from real AD
+ subprocess.check_call(
+ ["ldapadd", "-x", "-w", self.admin_pw, "-D",
+ self.admin_dn, "-H", self.ldap_url,
+ "-f", "data/ad_data.ldif"],
+ )
+
+ def teardown(self):
+ """Teardown the instance."""
+ super(FakeAD, self).teardown()
diff --git a/src/tests/intg/ent.py b/src/tests/intg/ent.py
new file mode 100644
index 0000000..d716ae5
--- /dev/null
+++ b/src/tests/intg/ent.py
@@ -0,0 +1,506 @@
+#
+# Abstract passwd/group entry management
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+from pprint import pformat
+import pwd
+import grp
+
+_PASSWD_LIST_DESC = {None: ("user", {})}
+_GROUP_DESC = {"mem": ("member list", {None: ("member", {})})}
+_GROUP_LIST_DESC = {None: ("group", _GROUP_DESC)}
+
+
+def _get_desc(desc_map, key):
+ """
+ Get an item description from a container description map.
+
+ Arguments:
+ desc_map Container description map.
+ key Item key, None for wildcard description.
+ """
+ assert isinstance(desc_map, dict)
+ if key in desc_map:
+ return desc_map[key]
+ if None in desc_map:
+ desc = desc_map[None]
+ if key is not None:
+ desc = (desc[0] + " " + pformat(key), desc[1])
+ return desc
+ elif key is None:
+ return ("item", {})
+ else:
+ return (pformat(key), {})
+
+
+def _diff(ent, pattern, desc_map={}):
+ """
+ Describe difference between an entry and a pattern.
+ Return None, if none.
+
+ Arguments:
+ ent Entry.
+ pattern Pattern.
+ desc_map Container pattern description map.
+
+ An entry is a value, a list of entries, or a dictionary of entries.
+ Entries are used to store passwd and group database entries as
+ dictionaries, in lists and dictionaries.
+
+ A pattern is a value, a tuple, a list, or a dictionary of patterns.
+
+ E.g. 123, "abc", [ 123, "abc" ], { "abc": 123 }, { "abc": ( 123 ) }
+
+ A pattern can be matched against a value, a list, or a dictionary entry.
+
+ A value is considered matching, if it's equal to the pattern.
+
+ E.g. 123 == 123, 123 != 456, "abc" == "abc", "abc" != "def", 123 != "abc"
+
+ A list is considered matching a pattern, if the pattern is a list or a
+ tuple, where each of pattern list items matches an entry list item and
+ vice versa, or where each pattern tuple item matches an entry list item,
+ but not necessarily the other way around.
+
+ E.g. [] != "abc", [] == [], [ "abc", 123 ] == [ 123, "abc" ],
+ [ "abc" ] != [ 123 ], [ 123 ] != [],
+ [] == (), [ "abc", 123 ] == ( 123, "abc" ),
+ [ "abc" ] != ( 123 ), [ 123 ] == (), [ 123, "abc" ] == ( 123 )
+
+ NOTE: For the sake of readability, it is recommended to use
+ "contains_only" function to create patterns matching all entry list
+ items (list patterns), and "contains" function to create patterns
+ matching a subset of entry list items (tuple patterns).
+
+ A dictionary is considered matching a pattern, if it is also a dictionary,
+ and all of pattern values match identically-keyed values of the
+ dictionary.
+
+ E.g. {} == {}, {} != "abc", { "abc": 123, "def": 456 } == { "abc": 123 },
+ { "abc": 123 } == {}
+
+ Container pattern description map is a dictionary with keys being item
+ keys/indices and values being (name, description map) tuples. None key
+ points to a wildcard description, others to specific item descriptions.
+ The description map argument is optional, and is used to generate more
+ readable difference explanations.
+ """
+ assert isinstance(desc_map, dict)
+
+ if isinstance(pattern, dict):
+ if not isinstance(ent, dict):
+ return "not a dict, " + str(type(ent))
+
+ for key, value in pattern.items():
+ item_name, item_map = _get_desc(desc_map, key)
+ d = _diff(ent[key], value, item_map)
+ if d:
+ return item_name + " mismatch: " + d
+ elif isinstance(pattern, tuple):
+ if not isinstance(ent, list):
+ return "not a list, " + str(type(ent))
+
+ pattern_matches = [0 for pv in pattern]
+
+ for ei, ev in enumerate(ent):
+ for pi, pv in enumerate(pattern):
+ d = _diff(ev, pv)
+ if not d:
+ pattern_matches[pi] += 1
+
+ unmatched_pattern = [pattern[pi] for pi in range(0, len(pattern))
+ if pattern_matches[pi] == 0]
+
+ items = _get_desc(desc_map, None)[0] + "s"
+ if len(unmatched_pattern) > 0:
+ return "\nexpected " + items + " not found:\n" + \
+ pformat(unmatched_pattern)
+ elif isinstance(pattern, list):
+ if not isinstance(ent, list):
+ return "not a list, " + str(type(ent))
+
+ pattern_matches = [0 for pv in pattern]
+ ent_matches = [0 for ev in ent]
+
+ for ei, ev in enumerate(ent):
+ for pi, pv in enumerate(pattern):
+ d = _diff(ev, pv)
+ if not d:
+ pattern_matches[pi] += 1
+ ent_matches[ei] += 1
+
+ unmatched_pattern = [pattern[pi] for pi in range(0, len(pattern))
+ if pattern_matches[pi] == 0]
+ unmatched_ent = [ent[pi] for pi in range(0, len(ent))
+ if ent_matches[pi] == 0]
+
+ items = _get_desc(desc_map, None)[0] + "s"
+ d = ""
+ if len(unmatched_pattern) > 0:
+ d += "\nexpected " + items + " not found:\n" + \
+ pformat(unmatched_pattern)
+ if len(unmatched_ent) != 0:
+ d += "\nunexpected " + items + " found:\n" + \
+ pformat(unmatched_ent)
+ if len(d) > 0:
+ return d
+ else:
+ if pattern != ent:
+ return pformat(pattern) + " != " + pformat(ent)
+
+ return None
+
+
+def contains_only(*args):
+ """
+ Produce a pattern matching all list items against arguments.
+ Use this function instead of constructing bare lists, for readability.
+ """
+ return list(args)
+
+
+def contains(*args):
+ """
+ Produce a pattern matching a subset of list items against arguments.
+ Use this function instead of constructing bare tuples, for readability.
+ """
+ return args
+
+
+def _convert_passwd(passwd):
+ """
+ Convert a passwd entry returned by pwd module to an entry dictionary.
+ """
+ return dict(
+ name=passwd.pw_name,
+ passwd=passwd.pw_passwd,
+ uid=passwd.pw_uid,
+ gid=passwd.pw_gid,
+ gecos=passwd.pw_gecos,
+ dir=passwd.pw_dir,
+ shell=passwd.pw_shell
+ )
+
+
+def get_passwd_by_name(name):
+ """Get a passwd database entry by name."""
+ return _convert_passwd(pwd.getpwnam(name))
+
+
+def get_passwd_by_uid(uid):
+ """Get a passwd database entry by UID."""
+ return _convert_passwd(pwd.getpwuid(uid))
+
+
+def assert_passwd_by_name(name, pattern):
+ """Assert a passwd entry, retrieved by name, matches a pattern."""
+ try:
+ ent = get_passwd_by_name(name)
+ except KeyError as err:
+ assert False, err
+ d = _diff(ent, pattern)
+ assert not d, d
+
+
+def assert_passwd_by_uid(uid, pattern):
+ """Assert a passwd entry, retrieved by UID, matches a pattern."""
+ try:
+ ent = get_passwd_by_uid(uid)
+ except KeyError as err:
+ assert False, err
+ d = _diff(ent, pattern)
+ assert not d, d
+
+
+def get_passwd_list():
+ """Get passwd database entry list with root user removed."""
+ passwd_list = pwd.getpwall()
+ for i, v in enumerate(passwd_list):
+ if v.pw_name == "root" and v.pw_uid == 0 and v.pw_gid == 0:
+ del passwd_list[i]
+ return list(map(_convert_passwd, passwd_list))
+ raise Exception("no root user found")
+
+
+def assert_passwd_list(pattern):
+ """Assert retrieved passwd list matches a pattern."""
+ d = _diff(get_passwd_list(), pattern, _PASSWD_LIST_DESC)
+ assert not d, d
+
+
+def _diff_each_passwd_by_name(pattern_dict):
+ """
+ Describe difference between each pattern_dict value and a passwd entry
+ retrieved by name being the corresponding key.
+ """
+ try:
+ ent = dict((k, get_passwd_by_name(k)) for k in pattern_dict.keys())
+ except KeyError as err:
+ return str(err)
+ return _diff(ent, pattern_dict, _PASSWD_LIST_DESC)
+
+
+def _diff_each_passwd_by_uid(pattern_dict):
+ """
+ Describe difference between each pattern_dict value and a passwd entry
+ retrieved by UID being the corresponding key.
+ """
+ try:
+ ent = dict((k, get_passwd_by_uid(k)) for k in pattern_dict.keys())
+ except KeyError as err:
+ return str(err)
+ return _diff(ent, pattern_dict, _PASSWD_LIST_DESC)
+
+
+def _diff_each_passwd_with_name(pattern_seq):
+ """
+ Describe difference between each pattern in pattern_seq sequence and a
+ passwd entry retrieved by name being the pattern's "name" value.
+ """
+ return _diff_each_passwd_by_name(dict((p["name"], p) for p in pattern_seq))
+
+
+def _diff_each_passwd_with_uid(pattern_seq):
+ """
+ Describe difference between each pattern in pattern_seq sequence and a
+ passwd entry retrieved by UID being the pattern's "uid" value.
+ """
+ return _diff_each_passwd_by_uid(dict((p["uid"], p) for p in pattern_seq))
+
+
+def assert_each_passwd_by_name(pattern_dict):
+ """
+ Assert each pattern_dict value matches a passwd entry retrieved by
+ name being the corresponding key.
+ """
+ d = _diff_each_passwd_by_name(pattern_dict)
+ assert not d, d
+
+
+def assert_each_passwd_by_uid(pattern_dict):
+ """
+ Assert each pattern_dict value matches a passwd entry retrieved by
+ UID being the corresponding key.
+ """
+ d = _diff_each_passwd_by_uid(pattern_dict)
+ assert not d, d
+
+
+def assert_each_passwd_with_name(pattern_seq):
+ """
+ Assert each pattern in pattern_seq sequence matches a passwd entry
+ retrieved by name being the pattern's "name" value.
+ """
+ d = _diff_each_passwd_with_name(pattern_seq)
+ assert not d, d
+
+
+def assert_each_passwd_with_uid(pattern_seq):
+ """
+ Assert each pattern in pattern_seq sequence matches a passwd entry
+ retrieved by UID being the pattern's "uid" value.
+ """
+ d = _diff_each_passwd_with_uid(pattern_seq)
+ assert not d, d
+
+
+def _diff_passwd(pattern):
+ """
+ Describe difference between passwd database and a pattern.
+ Each pattern entry must have "name" and "uid" attribute.
+ """
+ d = _diff(get_passwd_list(), pattern, _PASSWD_LIST_DESC)
+ if d:
+ return "list mismatch: " + d
+ d = _diff_each_passwd_with_name(pattern)
+ if d:
+ return "name retrieval mismatch: " + d
+ d = _diff_each_passwd_with_uid(pattern)
+ if d:
+ return "UID retrieval mismatch: " + d
+ return None
+
+
+def assert_passwd(pattern):
+ """
+ Assert passwd database matches a pattern.
+ Each pattern entry must have "name" and "uid" attribute.
+ """
+ d = _diff_passwd(pattern)
+ assert not d, d
+
+
+def _convert_group(group):
+ """
+ Convert a group entry returned by grp module to an entry dictionary.
+ """
+ return dict(
+ name=group.gr_name,
+ passwd=group.gr_passwd,
+ gid=group.gr_gid,
+ mem=group.gr_mem
+ )
+
+
+def get_group_by_name(name):
+ """Get a group database entry by name."""
+ return _convert_group(grp.getgrnam(name))
+
+
+def get_group_by_gid(gid):
+ """Get a group database entry by GID."""
+ return _convert_group(grp.getgrgid(gid))
+
+
+def assert_group_by_name(name, pattern):
+ """Assert a group entry, retrieved by name, matches a pattern."""
+ try:
+ ent = get_group_by_name(name)
+ except KeyError as err:
+ assert False, err
+ d = _diff(ent, pattern, _GROUP_DESC)
+ assert not d, d
+
+
+def assert_group_by_gid(gid, pattern):
+ """Assert a group entry, retrieved by GID, matches a pattern."""
+ try:
+ ent = get_group_by_gid(gid)
+ except KeyError as err:
+ assert False, err
+ d = _diff(ent, pattern, _GROUP_DESC)
+ assert not d, d
+
+
+def get_group_list():
+ """Get group database entry list with root group removed."""
+ group_list = grp.getgrall()
+ for i, v in enumerate(group_list):
+ if v.gr_name == "root" and v.gr_gid == 0:
+ del group_list[i]
+ return list(map(_convert_group, group_list))
+ raise Exception("no root group found")
+
+
+def assert_group_list(pattern):
+ """Assert retrieved group list matches a pattern."""
+ d = _diff(get_group_list(), pattern, _GROUP_LIST_DESC)
+ assert not d, d
+
+
+def _diff_each_group_by_name(pattern_dict):
+ """
+ Describe difference between each pattern_dict value and a group entry
+ retrieved by name being the corresponding key.
+ """
+ try:
+ ent = dict((k, get_group_by_name(k)) for k in pattern_dict.keys())
+ except KeyError as err:
+ return str(err)
+ return _diff(ent, pattern_dict, _GROUP_LIST_DESC)
+
+
+def _diff_each_group_by_gid(pattern_dict):
+ """
+ Describe difference between each pattern_dict value and a group entry
+ retrieved by GID being the corresponding key.
+ """
+ try:
+ ent = dict((k, get_group_by_gid(k)) for k in pattern_dict.keys())
+ except KeyError as err:
+ return str(err)
+ return _diff(ent, pattern_dict, _GROUP_LIST_DESC)
+
+
+def _diff_each_group_with_name(pattern_seq):
+ """
+ Describe difference between each pattern in pattern_seq sequence and a
+ group entry retrieved name being the pattern's "name" value.
+ """
+ return _diff_each_group_by_name(dict((p["name"], p) for p in pattern_seq))
+
+
+def _diff_each_group_with_gid(pattern_seq):
+ """
+ Describe difference between each pattern in pattern_seq sequence and a
+ group entry retrieved by GID being the pattern's "gid" value.
+ """
+ return _diff_each_group_by_gid(dict((p["gid"], p) for p in pattern_seq))
+
+
+def assert_each_group_by_name(pattern_dict):
+ """
+ Assert each pattern_dict value matches a group entry retrieved by
+ name being the corresponding key.
+ """
+ d = _diff_each_group_by_name(pattern_dict)
+ assert not d, d
+
+
+def assert_each_group_by_gid(pattern_dict):
+ """
+ Assert each pattern_dict value matches a group entry retrieved by
+ GID being the corresponding key.
+ """
+ d = _diff_each_group_by_gid(pattern_dict)
+ assert not d, d
+
+
+def assert_each_group_with_name(pattern_seq):
+ """
+ Assert each pattern in pattern_seq sequence matches a group entry
+ retrieved by name being the pattern's "name" value.
+ """
+ d = _diff_each_group_with_name(pattern_seq)
+ assert not d, d
+
+
+def assert_each_group_with_gid(pattern_seq):
+ """
+ Assert each pattern in pattern_seq sequence matches a group entry
+ retrieved by GID being the pattern's "gid" value.
+ """
+ d = _diff_each_group_with_gid(pattern_seq)
+ assert not d, d
+
+
+def _diff_group(pattern):
+ """
+ Describe difference between group database and a pattern.
+ Each pattern entry must have "name" and "gid" attribute.
+ """
+ d = _diff(get_group_list(), pattern, _GROUP_LIST_DESC)
+ if d:
+ return "list mismatch: " + d
+ d = _diff_each_group_with_name(pattern)
+ if d:
+ return "name retrieval mismatch: " + d
+ d = _diff_each_group_with_gid(pattern)
+ if d:
+ return "GID retrieval mismatch: " + d
+ return None
+
+
+def assert_group(pattern):
+ """
+ Assert group database matches a pattern.
+ Each pattern entry must have "name" and "gid" attribute.
+ """
+ d = _diff_group(pattern)
+ assert not d, d
diff --git a/src/tests/intg/ent_test.py b/src/tests/intg/ent_test.py
new file mode 100644
index 0000000..4d741a3
--- /dev/null
+++ b/src/tests/intg/ent_test.py
@@ -0,0 +1,429 @@
+#
+# ent.py module tests
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import re
+import pytest
+import ent
+from util import backup_envvar_file, restore_envvar_file
+
+
+@pytest.fixture(scope="module")
+def passwd_path(request):
+ name = "NSS_WRAPPER_PASSWD"
+ request.addfinalizer(lambda: restore_envvar_file(name))
+ return backup_envvar_file(name)
+
+
+@pytest.fixture(scope="module")
+def group_path(request):
+ name = "NSS_WRAPPER_GROUP"
+ request.addfinalizer(lambda: restore_envvar_file(name))
+ return backup_envvar_file(name)
+
+
+USER1 = dict(name="user1", passwd="x", uid=1001, gid=2001,
+ gecos="User 1", dir="/home/user1", shell="/bin/bash")
+USER2 = dict(name="user2", passwd="x", uid=1002, gid=2002,
+ gecos="User 2", dir="/home/user2", shell="/bin/bash")
+USER_LIST = [USER1, USER2]
+USER_NAME_DICT = dict((u["name"], u) for u in USER_LIST)
+USER_UID_DICT = dict((u["uid"], u) for u in USER_LIST)
+
+
+EMPTY_GROUP = dict(name="empty_group", passwd="x", gid=2000,
+ mem=ent.contains_only())
+GROUP1 = dict(name="group1", passwd="x", gid=2001,
+ mem=ent.contains_only())
+GROUP2 = dict(name="group2", passwd="x", gid=2002,
+ mem=ent.contains_only())
+ONE_USER_GROUP1 = dict(name="one_user_group1", passwd="x", gid=2011,
+ mem=ent.contains_only("user1"))
+ONE_USER_GROUP2 = dict(name="one_user_group2", passwd="x", gid=2012,
+ mem=ent.contains_only("user2"))
+TWO_USER_GROUP = dict(name="two_user_group", passwd="x", gid=2020,
+ mem=ent.contains_only("user1", "user2"))
+GROUP_LIST = [EMPTY_GROUP,
+ GROUP1,
+ GROUP2,
+ ONE_USER_GROUP1,
+ ONE_USER_GROUP2,
+ TWO_USER_GROUP]
+GROUP_NAME_DICT = dict((g["name"], g) for g in GROUP_LIST)
+GROUP_GID_DICT = dict((g["gid"], g) for g in GROUP_LIST)
+
+
+@pytest.fixture(scope="module")
+def users_and_groups(request, passwd_path, group_path):
+ passwd_contents = "".join([
+ "{name}:{passwd}:{uid}:{gid}:{gecos}:{dir}:{shell}\n".format(**u)
+ for u in USER_LIST
+ ])
+ group_contents = "".join([
+ "%s:%s:%s:%s\n" % (g["name"], g["passwd"], g["gid"],
+ ",".join(g["mem"]))
+ for g in GROUP_LIST
+ ])
+
+ with open(passwd_path, "a") as f:
+ f.write(passwd_contents)
+ with open(group_path, "a") as f:
+ f.write(group_contents)
+
+
+def test_assert_passwd_by_name(users_and_groups):
+ ent.assert_passwd_by_name("user1", {})
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001))
+ ent.assert_passwd_by_name("user1", USER1)
+
+ try:
+ ent.assert_passwd_by_name("user3", {})
+ assert False
+ except AssertionError as e:
+ assert str(e) in ("'getpwnam(): name not found: user3'",
+ "\"getpwnam(): name not found: 'user3'\"")
+
+ try:
+ ent.assert_passwd_by_name("user2", dict(name="user1"))
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'name' mismatch: 'user1' != 'user2'"
+
+
+def test_assert_passwd_by_uid(users_and_groups):
+ ent.assert_passwd_by_uid(1001, {})
+ ent.assert_passwd_by_uid(1001, dict(name="user1", uid=1001))
+ ent.assert_passwd_by_uid(1001, USER1)
+
+ try:
+ ent.assert_passwd_by_uid(1003, {})
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'getpwuid(): uid not found: 1003'"
+
+ try:
+ ent.assert_passwd_by_uid(1002, dict(name="user1"))
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'name' mismatch: 'user1' != 'user2'"
+
+
+def test_assert_passwd_list(users_and_groups):
+ ent.assert_passwd_list(ent.contains())
+ ent.assert_passwd_list(ent.contains(USER1))
+ ent.assert_passwd_list(ent.contains_only(*USER_LIST))
+ try:
+ ent.assert_passwd_list(ent.contains_only())
+ assert False
+ except AssertionError as e:
+ assert not re.search("expected users not found:", str(e))
+ assert re.search("unexpected users found:", str(e))
+ try:
+ ent.assert_passwd_list(ent.contains(dict(name="non_existent")))
+ assert False
+ except AssertionError as e:
+ assert re.search("expected users not found:", str(e))
+ assert not re.search("unexpected users found:", str(e))
+
+
+def test_assert_each_passwd_by_name(users_and_groups):
+ ent.assert_each_passwd_by_name({})
+ ent.assert_each_passwd_by_name(dict(user1=USER1))
+ ent.assert_each_passwd_by_name(USER_NAME_DICT)
+ try:
+ ent.assert_each_passwd_by_name(dict(user3={}))
+ assert False
+ except AssertionError as e:
+ assert str(e) in ("'getpwnam(): name not found: user3'",
+ "\"getpwnam(): name not found: 'user3'\"")
+ try:
+ ent.assert_each_passwd_by_name(dict(user1=dict(name="user2")))
+ assert False
+ except AssertionError as e:
+ assert str(e) == \
+ "user 'user1' mismatch: 'name' mismatch: 'user2' != 'user1'"
+
+
+def test_assert_each_passwd_by_uid(users_and_groups):
+ ent.assert_each_passwd_by_uid({})
+ ent.assert_each_passwd_by_uid({1001: USER1})
+ ent.assert_each_passwd_by_uid(USER_UID_DICT)
+ try:
+ ent.assert_each_passwd_by_uid({1003: {}})
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'getpwuid(): uid not found: 1003'"
+ try:
+ ent.assert_each_passwd_by_uid({1001: dict(uid=1002)})
+ assert False
+ except AssertionError as e:
+ assert str(e) == \
+ "user 1001 mismatch: 'uid' mismatch: 1002 != 1001"
+
+
+def test_assert_each_passwd_with_name(users_and_groups):
+ ent.assert_each_passwd_with_name([])
+ ent.assert_each_passwd_with_name([USER1])
+ ent.assert_each_passwd_with_name(USER_LIST)
+ try:
+ ent.assert_each_passwd_with_name([dict(name="user3")])
+ assert False
+ except AssertionError as e:
+ assert str(e) in ("'getpwnam(): name not found: user3'",
+ "\"getpwnam(): name not found: 'user3'\"")
+ try:
+ ent.assert_each_passwd_with_name([dict(name="user1", uid=1002)])
+ assert False
+ except AssertionError as e:
+ assert str(e) == \
+ "user 'user1' mismatch: 'uid' mismatch: 1002 != 1001"
+
+
+def test_assert_each_passwd_with_uid(users_and_groups):
+ ent.assert_each_passwd_with_uid([])
+ ent.assert_each_passwd_with_uid([USER1])
+ ent.assert_each_passwd_with_uid(USER_LIST)
+ try:
+ ent.assert_each_passwd_with_uid([dict(uid=1003)])
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'getpwuid(): uid not found: 1003'"
+ try:
+ ent.assert_each_passwd_with_uid([dict(name="user2", uid=1001)])
+ assert False
+ except AssertionError as e:
+ assert str(e) == \
+ "user 1001 mismatch: 'name' mismatch: 'user2' != 'user1'"
+
+
+def test_assert_passwd(users_and_groups):
+ ent.assert_passwd(ent.contains())
+ ent.assert_passwd(ent.contains(USER1))
+ ent.assert_passwd(ent.contains_only(*USER_LIST))
+ try:
+ ent.assert_passwd(ent.contains(dict(name="user3", uid=1003)))
+ assert False
+ except AssertionError as e:
+ assert re.search("list mismatch:", str(e))
+ assert re.search("expected users not found:", str(e))
+ assert not re.search("unexpected users found:", str(e))
+ try:
+ ent.assert_passwd(ent.contains_only(USER1))
+ assert False
+ except AssertionError as e:
+ assert re.search("list mismatch:", str(e))
+ assert not re.search("expected users not found:", str(e))
+ assert re.search("unexpected users found:", str(e))
+
+
+def test_group_member_matching(users_and_groups):
+ ent.assert_group_by_name("empty_group", dict(mem=ent.contains()))
+ ent.assert_group_by_name("empty_group", dict(mem=ent.contains_only()))
+ try:
+ ent.assert_group_by_name("empty_group",
+ dict(mem=ent.contains("user1")))
+ except AssertionError as e:
+ assert re.search("member list mismatch:", str(e))
+ assert re.search("expected members not found:", str(e))
+
+ ent.assert_group_by_name("one_user_group1", dict(mem=ent.contains()))
+ ent.assert_group_by_name("one_user_group1",
+ dict(mem=ent.contains("user1")))
+ ent.assert_group_by_name("one_user_group1",
+ dict(mem=ent.contains_only("user1")))
+ try:
+ ent.assert_group_by_name("one_user_group1",
+ dict(mem=ent.contains_only()))
+ except AssertionError as e:
+ assert re.search("member list mismatch:", str(e))
+ assert re.search("unexpected members found:", str(e))
+ assert not re.search("expected members not found:", str(e))
+ try:
+ ent.assert_group_by_name("one_user_group1",
+ dict(mem=ent.contains_only("user3")))
+ except AssertionError as e:
+ assert re.search("member list mismatch:", str(e))
+ assert re.search("unexpected members found:", str(e))
+ assert re.search("expected members not found:", str(e))
+ try:
+ ent.assert_group_by_name("one_user_group1",
+ dict(mem=ent.contains("user3")))
+ except AssertionError as e:
+ assert re.search("member list mismatch:", str(e))
+ assert not re.search("unexpected members found:", str(e))
+ assert re.search("expected members not found:", str(e))
+
+ ent.assert_group_by_name("two_user_group", dict(mem=ent.contains()))
+ ent.assert_group_by_name("two_user_group",
+ dict(mem=ent.contains("user1")))
+ ent.assert_group_by_name("two_user_group",
+ dict(mem=ent.contains("user1", "user2")))
+ ent.assert_group_by_name("two_user_group",
+ dict(mem=ent.contains_only("user1", "user2")))
+ try:
+ ent.assert_group_by_name("two_user_group",
+ dict(mem=ent.contains_only("user1")))
+ except AssertionError as e:
+ assert re.search("member list mismatch:", str(e))
+ assert re.search("unexpected members found:", str(e))
+ assert not re.search("expected members not found:", str(e))
+
+
+def test_assert_group_by_name(users_and_groups):
+ ent.assert_group_by_name("group1", {})
+ ent.assert_group_by_name("group1", dict(name="group1", gid=2001))
+ ent.assert_group_by_name("group1", GROUP1)
+
+ try:
+ ent.assert_group_by_name("group3", {})
+ assert False
+ except AssertionError as e:
+ assert str(e) in ("'getgrnam(): name not found: group3'",
+ "\"getgrnam(): name not found: 'group3'\"")
+
+ try:
+ ent.assert_group_by_name("group2", dict(name="group1"))
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'name' mismatch: 'group1' != 'group2'"
+
+
+def test_assert_group_by_gid(users_and_groups):
+ ent.assert_group_by_gid(2001, {})
+ ent.assert_group_by_gid(2001, dict(name="group1", gid=2001))
+ ent.assert_group_by_gid(2001, GROUP1)
+
+ try:
+ ent.assert_group_by_gid(2003, {})
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'getgrgid(): gid not found: 2003'"
+
+ try:
+ ent.assert_group_by_gid(2002, dict(name="group1"))
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'name' mismatch: 'group1' != 'group2'"
+
+
+def test_assert_group_list(users_and_groups):
+ ent.assert_group_list(ent.contains())
+ ent.assert_group_list(ent.contains(GROUP1))
+ ent.assert_group_list(ent.contains_only(*GROUP_LIST))
+ try:
+ ent.assert_group_list(ent.contains_only())
+ assert False
+ except AssertionError as e:
+ assert not re.search("expected groups not found:", str(e))
+ assert re.search("unexpected groups found:", str(e))
+ try:
+ ent.assert_group_list(ent.contains(dict(name="non_existent")))
+ assert False
+ except AssertionError as e:
+ assert re.search("expected groups not found:", str(e))
+ assert not re.search("unexpected groups found:", str(e))
+
+
+def test_assert_each_group_by_name(users_and_groups):
+ ent.assert_each_group_by_name({})
+ ent.assert_each_group_by_name(dict(group1=GROUP1))
+ ent.assert_each_group_by_name(GROUP_NAME_DICT)
+ try:
+ ent.assert_each_group_by_name(dict(group3={}))
+ assert False
+ except AssertionError as e:
+ assert str(e) in ("'getgrnam(): name not found: group3'",
+ "\"getgrnam(): name not found: 'group3'\"")
+ try:
+ ent.assert_each_group_by_name(dict(group1=dict(name="group2")))
+ assert False
+ except AssertionError as e:
+ assert str(e) == "group 'group1' mismatch: " + \
+ "'name' mismatch: 'group2' != 'group1'"
+
+
+def test_assert_each_group_by_gid(users_and_groups):
+ ent.assert_each_group_by_gid({})
+ ent.assert_each_group_by_gid({2001: GROUP1})
+ ent.assert_each_group_by_gid(GROUP_GID_DICT)
+ try:
+ ent.assert_each_group_by_gid({2003: {}})
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'getgrgid(): gid not found: 2003'"
+ try:
+ ent.assert_each_group_by_gid({2001: dict(gid=2002)})
+ assert False
+ except AssertionError as e:
+ assert str(e) == \
+ "group 2001 mismatch: 'gid' mismatch: 2002 != 2001"
+
+
+def test_assert_each_group_with_name(users_and_groups):
+ ent.assert_each_group_with_name([])
+ ent.assert_each_group_with_name([GROUP1])
+ ent.assert_each_group_with_name(GROUP_LIST)
+ try:
+ ent.assert_each_group_with_name([dict(name="group3")])
+ assert False
+ except AssertionError as e:
+ assert str(e) in ("'getgrnam(): name not found: group3'",
+ "\"getgrnam(): name not found: 'group3'\"")
+ try:
+ ent.assert_each_group_with_name([dict(name="group1", gid=2002)])
+ assert False
+ except AssertionError as e:
+ assert str(e) == \
+ "group 'group1' mismatch: 'gid' mismatch: 2002 != 2001"
+
+
+def test_assert_each_group_with_gid(users_and_groups):
+ ent.assert_each_group_with_gid([])
+ ent.assert_each_group_with_gid([GROUP1])
+ ent.assert_each_group_with_gid(GROUP_LIST)
+ try:
+ ent.assert_each_group_with_gid([dict(gid=2003)])
+ assert False
+ except AssertionError as e:
+ assert str(e) == "'getgrgid(): gid not found: 2003'"
+ try:
+ ent.assert_each_group_with_gid([dict(name="group2", gid=2001)])
+ assert False
+ except AssertionError as e:
+ assert str(e) == \
+ "group 2001 mismatch: 'name' mismatch: 'group2' != 'group1'"
+
+
+def test_assert_group(users_and_groups):
+ ent.assert_group(ent.contains())
+ ent.assert_group(ent.contains(GROUP1))
+ ent.assert_group(ent.contains_only(*GROUP_LIST))
+ try:
+ ent.assert_group(ent.contains(dict(name="group3", gid=2003)))
+ assert False
+ except AssertionError as e:
+ assert re.search("list mismatch:", str(e))
+ assert re.search("expected groups not found:", str(e))
+ assert not re.search("unexpected groups found:", str(e))
+ try:
+ ent.assert_group(ent.contains_only(GROUP1))
+ assert False
+ except AssertionError as e:
+ assert re.search("list mismatch:", str(e))
+ assert not re.search("expected groups not found:", str(e))
+ assert re.search("unexpected groups found:", str(e))
diff --git a/src/tests/intg/files_ops.py b/src/tests/intg/files_ops.py
new file mode 100644
index 0000000..57959f5
--- /dev/null
+++ b/src/tests/intg/files_ops.py
@@ -0,0 +1,173 @@
+#
+# SSSD integration test - operations on UNIX user and group database
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import os.path
+import tempfile
+import pytest
+
+import ent
+from util import backup_envvar_file, restore_envvar_file
+
+
+@pytest.fixture
+def passwd_ops_setup(request):
+ pwd_file = os.environ["NSS_WRAPPER_PASSWD"]
+ backup_envvar_file("NSS_WRAPPER_PASSWD")
+ request.addfinalizer(lambda: restore_envvar_file("NSS_WRAPPER_PASSWD"))
+ pwd_ops = PasswdOps(pwd_file)
+ return pwd_ops
+
+
+@pytest.fixture
+def group_ops_setup(request):
+ grp_file = os.environ["NSS_WRAPPER_GROUP"]
+ backup_envvar_file("NSS_WRAPPER_GROUP")
+ request.addfinalizer(lambda: restore_envvar_file("NSS_WRAPPER_GROUP"))
+ grp_ops = GroupOps(grp_file)
+ return grp_ops
+
+
+@pytest.fixture
+def group_db_setup(request):
+ group = request.param
+ grp_ops = group_ops_setup(request)
+ grp_ops.groupadd(**group)
+ ent.assert_group_by_name(group['name'], group)
+ return grp_ops
+
+
+class FilesOps(object):
+ """
+ A naive implementation of operations as a basis for user or group
+ operations. Uses rename to (hopefully) trigger the same fs-level
+ notifications as shadow-utils would.
+ """
+ def __init__(self, file_name):
+ self.file_name = file_name
+ self.tmp_dir = os.path.dirname(self.file_name)
+
+ @staticmethod
+ def _get_named_line(name, contents):
+ for num, line in enumerate(contents, 0):
+ pname = line.split(':')[0]
+ if name == pname:
+ return num
+ raise KeyError("%s not found" % name)
+
+ def _read_contents(self):
+ with open(self.file_name, "r") as pfile:
+ contents = pfile.readlines()
+ return contents
+
+ def _write_contents(self, contents):
+ tmp_file = tempfile.NamedTemporaryFile(mode='w', dir=self.tmp_dir,
+ delete=False)
+ tmp_file.writelines(contents)
+ tmp_file.flush()
+
+ os.rename(tmp_file.name, self.file_name)
+
+ def _append_line(self, new_line):
+ contents = self._read_contents()
+ contents.extend(new_line)
+ self._write_contents(contents)
+
+ def _subst_line(self, key, line):
+ contents = self._read_contents()
+ kindex = self._get_named_line(key, contents)
+ contents[kindex] = line
+ self._write_contents(contents)
+
+ def _del_line(self, key):
+ contents = self._read_contents()
+ kindex = self._get_named_line(key, contents)
+ contents.pop(kindex)
+ self._write_contents(contents)
+
+ contents = self._read_contents()
+
+ def _has_line(self, key):
+ try:
+ self._get_named_line(key, self._read_contents())
+ return True
+ except KeyError:
+ return False
+
+
+class PasswdOps(FilesOps):
+ """
+ A naive implementation of user operations
+ """
+ def __init__(self, file_name):
+ super(PasswdOps, self).__init__(file_name)
+
+ def _pwd2line(self, name, uid, gid, passwd, gecos, homedir, shell):
+ pwd_fmt = "{name}:{passwd}:{uid}:{gid}:{gecos}:{homedir}:{shell}\n"
+ return pwd_fmt.format(name=name,
+ passwd=passwd,
+ uid=uid,
+ gid=gid,
+ gecos=gecos,
+ homedir=homedir,
+ shell=shell)
+
+ def useradd(self, name, uid, gid, passwd='', gecos='', dir='', shell=''):
+ pwd_line = self._pwd2line(name, uid, gid, passwd, gecos, dir, shell)
+ self._append_line(pwd_line)
+
+ def usermod(self, name, uid, gid, passwd='', gecos='', dir='', shell=''):
+ pwd_line = self._pwd2line(name, uid, gid, passwd, gecos, dir, shell)
+ self._subst_line(name, pwd_line)
+
+ def userdel(self, name):
+ self._del_line(name)
+
+ def userexist(self, name):
+ return self._has_line(name)
+
+
+class GroupOps(FilesOps):
+ """
+ A naive implementation of group operations
+ """
+ def __init__(self, file_name):
+ super(GroupOps, self).__init__(file_name)
+
+ def _grp2line(self, name, gid, mem, passwd):
+ member_list = ",".join(m for m in mem)
+ grp_fmt = "{name}:{passwd}:{gid}:{member_list}\n"
+ return grp_fmt.format(name=name,
+ passwd=passwd,
+ gid=gid,
+ member_list=member_list)
+
+ def groupadd(self, name, gid, mem, passwd="*"):
+ grp_line = self._grp2line(name, gid, mem, passwd)
+ self._append_line(grp_line)
+
+ def groupmod(self, old_name, name, gid, mem, passwd="*"):
+ grp_line = self._grp2line(name, gid, mem, passwd)
+ self._subst_line(old_name, grp_line)
+
+ def groupdel(self, name):
+ self._del_line(name)
+
+ def groupexist(self, name):
+ return self._has_line(name)
diff --git a/src/tests/intg/getsockopt_wrapper.c b/src/tests/intg/getsockopt_wrapper.c
new file mode 100644
index 0000000..725693c
--- /dev/null
+++ b/src/tests/intg/getsockopt_wrapper.c
@@ -0,0 +1,94 @@
+/* gcc -Wall -fPIC -shared -o getsockopt_wrapper.so getsockopt_wrapper.c -ldl */
+
+/* for RTLD_NEXT */
+#define _GNU_SOURCE 1
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <dlfcn.h>
+
+static bool is_dbus_socket(int fd)
+{
+ int ret;
+ struct sockaddr_storage addr = { 0 };
+ socklen_t addrlen = sizeof(addr);
+ struct sockaddr_un *unix_socket;
+
+ ret = getsockname(fd, (struct sockaddr *)&addr, &addrlen);
+ if (ret != 0) return false;
+
+ if (addr.ss_family != AF_UNIX) return false;
+
+ unix_socket = (struct sockaddr_un *)&addr;
+
+ return NULL != strstr(unix_socket->sun_path, "system_bus_socket");
+}
+
+static bool peer_is_private_pam(int fd)
+{
+ int ret;
+ struct sockaddr_storage addr = { 0 };
+ socklen_t addrlen = sizeof(addr);
+ struct sockaddr_un *unix_socket;
+
+ ret = getpeername(fd, (struct sockaddr *)&addr, &addrlen);
+ if (ret != 0) return false;
+
+ if (addr.ss_family != AF_UNIX) return false;
+
+ unix_socket = (struct sockaddr_un *)&addr;
+
+ return NULL != strstr(unix_socket->sun_path, "private/pam");
+}
+
+static void fake_peer_uid_gid(uid_t *uid, gid_t *gid)
+{
+ char *val;
+
+ val = getenv("SSSD_INTG_PEER_UID");
+ if (val != NULL) {
+ *uid = atoi(val);
+ }
+
+ val = getenv("SSSD_INTG_PEER_GID");
+ if (val != NULL) {
+ *gid = atoi(val);
+ }
+}
+
+typedef typeof(getsockopt) getsockopt_fn_t;
+
+static getsockopt_fn_t *orig_getsockopt = NULL;
+
+int getsockopt(int sockfd, int level, int optname,
+ void *optval, socklen_t *optlen)
+{
+ int ret;
+#ifdef __OpenBSD__
+ struct sockpeercred *cr;
+#else
+ struct ucred *cr;
+#endif
+
+ if (orig_getsockopt == NULL) {
+ orig_getsockopt = (getsockopt_fn_t *)dlsym(RTLD_NEXT, "getsockopt");
+ }
+
+ ret = orig_getsockopt(sockfd, level, optname, optval, optlen);
+
+ if (ret == 0 && level == SOL_SOCKET && optname == SO_PEERCRED
+ && *optlen == sizeof(*cr)) {
+ cr = optval;
+ if (cr->uid != 0 && is_dbus_socket(sockfd)) {
+ cr->uid = 0;
+ } else if (peer_is_private_pam(sockfd)) {
+ fake_peer_uid_gid(&cr->uid, &cr->gid);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/tests/intg/kdc.py b/src/tests/intg/kdc.py
new file mode 100644
index 0000000..a574e1f
--- /dev/null
+++ b/src/tests/intg/kdc.py
@@ -0,0 +1,178 @@
+#
+# MIT Kerberos server class
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import signal
+import shutil
+import subprocess
+
+from util import unindent
+
+
+class KDC(object):
+ """
+ MIT Kerberos KDC instance
+ """
+
+ def __init__(self, basedir, realm,
+ includedir=None,
+ kdc_port=10088,
+ kadmin_port=10749,
+ master_key='master'):
+ self.basedir = basedir
+ self.realm = realm
+ self.kdc_port = kdc_port
+ self.kadmin_port = kadmin_port
+ self.master_key = master_key
+
+ self.kdc_basedir = self.basedir + "/var/krb5kdc"
+ self.includedir = includedir or (self.kdc_basedir + "/include")
+ self.kdc_logdir = self.kdc_basedir + "/log"
+ self.kdc_conf_path = self.kdc_basedir + "/kdc.conf"
+ self.krb5_conf_path = self.kdc_basedir + "/krb5.conf"
+
+ self.kdc_pid_file = self.kdc_basedir + "/kdc.pid"
+
+ self.acl_file = self.kdc_basedir + "/kadm5.acl"
+
+ self.admin_princ = "admin/admin@" + self.realm
+
+ def start_kdc(self, extra_args=[]):
+ args = ["krb5kdc", '-P', self.kdc_pid_file] + extra_args
+ return self._run_in_env(args, self.get_krb5_env())
+
+ def stop_kdc(self):
+ try:
+ with open(self.kdc_pid_file, "r") as pid_file:
+ os.kill(int(pid_file.read()), signal.SIGTERM)
+ except IOError as ioex:
+ if ioex.errno == 2:
+ pass
+ else:
+ raise ioex
+
+ def teardown(self):
+ self.stop_kdc()
+ shutil.rmtree(self.kdc_basedir)
+
+ def set_up(self):
+ self._create_config()
+ self._create_acl()
+ self._create_kdb()
+
+ def get_krb5_env(self):
+ my_env = os.environ.copy()
+ my_env['KRB5_CONFIG'] = self.krb5_conf_path
+ my_env['KRB5_KDC_PROFILE'] = self.kdc_conf_path
+ return my_env
+
+ def add_config(self, include_files):
+ for name, contents in include_files.items():
+ include_fpath = os.path.join(self.includedir, name)
+ with open(include_fpath, 'w') as include_file:
+ include_file.write(contents)
+
+ def add_principal(self, princ, password=None):
+ args = ["kadmin.local", "-q"]
+ if password is None:
+ args += ["addprinc -randkey %s" % (princ)]
+ else:
+ args += ["addprinc -pw %s %s" % (password, princ)]
+ return self._run_in_env(args, self.get_krb5_env())
+
+ def _run_in_env(self, args, env):
+ cmd = subprocess.Popen(args, env=env)
+ out, err = cmd.communicate()
+ return cmd.returncode, out, err
+
+ def _create_config(self):
+ try:
+ os.makedirs(self.kdc_basedir)
+ os.makedirs(self.kdc_logdir)
+ os.makedirs(self.includedir)
+ except OSError as osex:
+ if osex.errno == 17:
+ pass
+
+ kdc_conf = self._format_kdc_conf()
+ with open(self.kdc_conf_path, 'w') as kdc_conf_file:
+ kdc_conf_file.write(kdc_conf)
+
+ krb5_conf = self._format_krb5_conf()
+ with open(self.krb5_conf_path, 'w') as krb5_conf_file:
+ krb5_conf_file.write(krb5_conf)
+
+ def _create_acl(self):
+ with open(self.acl_file, 'w') as acl_fobject:
+ acl_fobject.write(self.admin_princ)
+
+ def _create_kdb(self):
+ self._run_in_env(
+ ['kdb5_util', 'create', '-W', '-s', '-P', self.master_key],
+ self.get_krb5_env()
+ )
+
+ def _format_kdc_conf(self):
+ database_path = self.kdc_basedir + "/principal"
+ key_stash = self.kdc_basedir + "/stash." + self.realm
+
+ kdc_logfile = "FILE:" + self.kdc_logdir + "/krb5kdc.log"
+ kadmin_logfile = "FILE:" + self.kdc_logdir + "/kadmin.log"
+ libkrb5_logfile = "FILE:" + self.kdc_logdir + "/libkrb5.log"
+
+ kdc_conf = unindent("""
+ [kdcdefaults]
+ kdc_ports = {self.kdc_port}
+ kdc_tcp_ports = {self.kdc_port}
+
+ [realms]
+ {self.realm} = {{
+ kadmind_port = {self.kadmin_port}
+ database_name = {database_path}
+ key_stash_file = {key_stash}
+ max_life = 7d
+ max_renewable_life = 14d
+ acl_file = {self.acl_file}
+ }}
+
+ [logging]
+ kdc = {kdc_logfile}
+ admin_server = {kadmin_logfile}
+ default = {libkrb5_logfile}
+ """).format(**locals())
+ return kdc_conf
+
+ def _format_krb5_conf(self):
+ kdc_uri = "localhost:%d" % self.kdc_port
+ kadmin_uri = "localhost:%d" % self.kadmin_port
+
+ krb5_conf = unindent("""
+ includedir {self.includedir}
+
+ [libdefaults]
+ default_realm = {self.realm}
+ dns_lookup_kdc = false
+ dns_lookup_realm = false
+
+ [realms]
+ {self.realm} = {{
+ kdc = {kdc_uri}
+ admin_server = {kadmin_uri}
+ }}
+ """).format(**locals())
+ return krb5_conf
diff --git a/src/tests/intg/krb5utils.py b/src/tests/intg/krb5utils.py
new file mode 100644
index 0000000..2d1b79e
--- /dev/null
+++ b/src/tests/intg/krb5utils.py
@@ -0,0 +1,174 @@
+#
+# MIT Kerberos server class
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import subprocess
+
+
+class NoPrincipals(Exception):
+ def __init__(self):
+ Exception.__init__(self, 'No principals in the collection')
+
+
+class PrincNotFound(Exception):
+ def __init__(self, principal):
+ Exception.__init__(self, 'Principal %s not found' % principal)
+
+
+class Krb5Utils(object):
+ """
+ Helper class to test Kerberos command line utilities
+ """
+ def __init__(self, krb5_conf_path):
+ self.krb5_conf_path = krb5_conf_path
+
+ def spawn_in_env(self, args, stdin=None, extra_env=None):
+ my_env = os.environ.copy()
+ my_env['KRB5_CONFIG'] = self.krb5_conf_path
+
+ if 'KRB5CCNAME' in my_env:
+ del my_env['KRB5CCNAME']
+ if extra_env is not None:
+ my_env.update(extra_env)
+
+ cmd = subprocess.Popen(args,
+ env=my_env,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ return cmd
+
+ def _run_in_env(self, args, stdin=None, extra_env=None):
+ cmd = self.spawn_in_env(args, stdin, extra_env)
+ out, err = cmd.communicate(stdin)
+ return cmd.returncode, out.decode('utf-8'), err.decode('utf-8')
+
+ def kinit(self, principal, password, options=None, env=None):
+ args = ["kinit", principal]
+ if options:
+ args.extend(options)
+ return self._run_in_env(args, password.encode('utf-8'), env)
+
+ def kvno(self, principal, env=None):
+ args = ["kvno", principal]
+ return self._run_in_env(args, env)
+
+ def kdestroy(self, all_ccaches=False, env=None):
+ args = ["kdestroy"]
+ if all_ccaches is True:
+ args += ["-A"]
+ retval, _, _ = self._run_in_env(args, env)
+ return retval
+
+ def kswitch(self, principal, env=None):
+ args = ["kswitch", '-p', principal]
+ retval, _, _ = self._run_in_env(args, env)
+ return retval
+
+ def _check_klist_l(self, line, exp_principal, exp_cache):
+ try:
+ princ, cache = line.split()
+ except ValueError:
+ return False
+
+ if exp_cache is not None and cache != exp_cache:
+ return False
+
+ if exp_principal != princ:
+ return False
+
+ return True
+
+ def num_princs(self, env=None):
+ args = ["klist", "-l"]
+ retval, out, err = self._run_in_env(args, extra_env=env)
+ if retval != 0:
+ return 0
+
+ outlines = [ln for ln in out.split('\n') if len(ln) > 1]
+ return len(outlines) - 2
+
+ def list_princs(self, env=None):
+ args = ["klist", "-l"]
+ retval, out, err = self._run_in_env(args, extra_env=env)
+ if retval == 1:
+ raise NoPrincipals
+ elif retval != 0:
+ raise Exception("klist failed: %d: %s\n", retval, err)
+
+ outlines = out.split('\n')
+ if len(outlines) < 2:
+ raise Exception("Not enough output from klist -l")
+
+ return [ln for ln in outlines[2:] if len(ln) > 0]
+
+ def list_times(self, env=None):
+ p = self.spawn_in_env(['klist', '-A'])
+ output = p.stdout.read().splitlines()
+ for line in output:
+ if not line:
+ continue
+
+ line_str = line.decode("utf-8")
+ if line_str[0].isdigit():
+ return line_str
+
+ def has_principal(self, exp_principal, exp_cache=None, env=None):
+ try:
+ princlist = self.list_princs(env)
+ except NoPrincipals:
+ return False
+
+ for line in princlist:
+ matches = self._check_klist_l(line, exp_principal, exp_cache)
+ if matches is True:
+ return True
+
+ return False
+
+ def default_principal(self, env=None):
+ principals = self.list_princs(env)
+ return principals[0].split()[0]
+
+ def _parse_klist_a(self, out):
+ dflprinc = None
+ thisrealm = None
+ ccache_dict = dict()
+
+ for line in [ln for ln in out.split('\n') if len(ln) > 0]:
+ if line.startswith("Default principal"):
+ dflprinc = line.split()[2]
+ thisrealm = '@' + dflprinc.split('@')[1]
+ elif thisrealm is not None and line.endswith(thisrealm):
+ svc = line.split()[-1]
+ if dflprinc in ccache_dict:
+ ccache_dict[dflprinc].append(svc)
+ else:
+ ccache_dict[dflprinc] = [svc]
+
+ return ccache_dict
+
+ def list_all_princs(self, env=None):
+ args = ["klist", "-A"]
+ retval, out, err = self._run_in_env(args, extra_env=env)
+ if retval == 1:
+ raise NoPrincipals
+ elif retval != 0:
+ raise Exception("klist -A failed: %d: %s\n", retval, err)
+
+ return self._parse_klist_a(out)
diff --git a/src/tests/intg/ldap_ent.py b/src/tests/intg/ldap_ent.py
new file mode 100644
index 0000000..2a56f3d
--- /dev/null
+++ b/src/tests/intg/ldap_ent.py
@@ -0,0 +1,235 @@
+#
+# LDAP modlist generation
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+
+def user(base_dn, uid, uidNumber, gidNumber,
+ userPassword=None,
+ gecos=None,
+ homeDirectory=None,
+ loginShell=None,
+ cn=None,
+ sn=None,
+ sshPubKey=(),
+ mail=None):
+ """
+ Generate an RFC2307(bis) user add-modlist for passing to ldap.add*
+ """
+ uidNumber = str(uidNumber).encode('utf-8')
+ gidNumber = str(gidNumber).encode('utf-8')
+ user = (
+ "uid=" + uid + ",ou=Users," + base_dn,
+ [
+ ('objectClass', [b'top', b'inetOrgPerson', b'mailRecipient',
+ b'posixAccount', b'ldapPublicKey']),
+ ('cn', [uidNumber if cn is None else cn.encode('utf-8')]),
+ ('sn', [b'User' if sn is None else sn.encode('utf-8')]),
+ ('uidNumber', [uidNumber]),
+ ('gidNumber', [gidNumber]),
+ ('userPassword', [b'Password' + uidNumber
+ if userPassword is None
+ else userPassword.encode('utf-8')]),
+ ('homeDirectory', [b'/home/' + uid.encode('utf-8')
+ if homeDirectory is None
+ else homeDirectory.encode('utf-8')]),
+ ('loginShell', [b'/bin/bash'
+ if loginShell is None
+ else loginShell.encode('utf-8')]),
+ ]
+ )
+ if gecos is not None:
+ user[1].append(('gecos', [gecos.encode('utf-8')]))
+ if len(sshPubKey) > 0:
+ pubkeys = [key.encode('utf-8') for key in sshPubKey]
+ user[1].append(('sshPublicKey', pubkeys))
+ if mail is not None:
+ user[1].append(('mail', [mail.encode('utf-8')]))
+ return user
+
+
+def group(base_dn, cn, gidNumber, member_uids=()):
+ """
+ Generate an RFC2307 group add-modlist for passing to ldap.add*.
+ """
+ gidNumber = str(gidNumber).encode('utf-8')
+ attr_list = [
+ ('objectClass', [b'top', b'posixGroup']),
+ ('gidNumber', [gidNumber])
+ ]
+ if len(member_uids) > 0:
+ mem_uids = [member.encode('utf-8') for member in member_uids]
+ attr_list.append(('memberUid', mem_uids))
+ return ("cn=" + cn + ",ou=Groups," + base_dn, attr_list)
+
+
+def group_bis(base_dn, cn, gidNumber, member_uids=(), member_gids=()):
+ """
+ Generate an RFC2307bis group add-modlist for passing to ldap.add*.
+ """
+ gidNumber = str(gidNumber).encode('utf-8')
+ attr_list = [
+ ('objectClass', [b'top', b'extensibleObject', b'groupOfNames']),
+ ('gidNumber', [gidNumber])
+ ]
+ member_list = []
+ for uid in member_uids:
+ member_list.append("uid=" + uid + ",ou=Users," + base_dn)
+ for gid in member_gids:
+ member_list.append("cn=" + gid + ",ou=Groups," + base_dn)
+ if len(member_list) > 0:
+ mem_list = [member.encode('utf-8') for member in member_list]
+ attr_list.append(('member', mem_list))
+ return ("cn=" + cn + ",ou=Groups," + base_dn, attr_list)
+
+
+def netgroup(base_dn, cn, triples=(), members=()):
+ """
+ Generate an RFC2307bis netgroup add-modlist for passing to ldap.add*.
+ """
+ attr_list = [
+ ('objectClass', [b'top', b'nisNetgroup'])
+ ]
+ if triples:
+ triples = [triple.encode('utf-8') for triple in triples]
+ attr_list.append(('nisNetgroupTriple', triples))
+ if members:
+ members = [member.encode('utf-8') for member in members]
+ attr_list.append(('memberNisNetgroup', members))
+ return ("cn=" + cn + ",ou=Netgroups," + base_dn, attr_list)
+
+
+def sudo_rule(base_dn, name, users=(), hosts=(), commands=()):
+ """
+ Generate a sudo rule for passing to ldap.add*
+ """
+ attr_list = [
+ ('objectClass', [b'top', b'sudoRole']),
+ ('cn', [name.encode('utf-8')])
+ ]
+
+ if len(users) > 0:
+ sudo_user_list = [u.encode('utf-8') for u in users]
+ attr_list.append(('sudoUser', sudo_user_list))
+ if len(hosts) > 0:
+ sudo_host_list = [h.encode('utf-8') for h in hosts]
+ attr_list.append(('sudoHost', sudo_host_list))
+ if len(commands) > 0:
+ sudo_command_list = [cmd.encode('utf-8') for cmd in commands]
+ attr_list.append(('sudoCommand', sudo_command_list))
+ return ("cn=" + name + ",ou=sudoers," + base_dn, attr_list)
+
+
+def ip_host(base_dn, name, aliases=(), addresses=()):
+ """
+ Generate an RFC2307 ipHost add-modlist for passing to ldap.add*.
+ """
+ attr_list = [
+ ('objectClass', [b'top', b'device', b'ipHost']),
+ ]
+ if (len(aliases)) > 0:
+ alias_list = [alias.encode('utf-8') for alias in aliases]
+ alias_list.insert(0, name.encode('utf-8'))
+ attr_list.append(('cn', alias_list))
+ else:
+ attr_list.append(('cn', [name.encode('utf-8')]))
+ if len(addresses) > 0:
+ addr_list = [addr.encode('utf-8') for addr in addresses]
+ attr_list.append(('ipHostNumber', addr_list))
+ return ("cn=" + name + ",ou=Hosts," + base_dn, attr_list)
+
+
+def ip_net(base_dn, name, address, aliases=()):
+ """
+ Generate an RFC2307 ipNetwork add-modlist for passing to ldap.add*.
+ """
+ attr_list = [
+ ('objectClass', [b'top', b'ipNetwork']),
+ ('ipNetworkNumber', [address.encode('utf-8')]),
+ ]
+ if (len(aliases)) > 0:
+ alias_list = [alias.encode('utf-8') for alias in aliases]
+ alias_list.insert(0, name.encode('utf-8'))
+ attr_list.append(('cn', alias_list))
+ else:
+ attr_list.append(('cn', [name.encode('utf-8')]))
+ return ("cn=" + name + ",ou=Networks," + base_dn, attr_list)
+
+
+class List(list):
+ """LDAP add-modlist list"""
+
+ def __init__(self, base_dn):
+ self.base_dn = base_dn
+
+ def add_user(self, uid, uidNumber, gidNumber,
+ base_dn=None,
+ userPassword=None,
+ gecos=None,
+ homeDirectory=None,
+ loginShell=None,
+ cn=None,
+ sn=None,
+ sshPubKey=(),
+ mail=None):
+ """Add an RFC2307(bis) user add-modlist."""
+ self.append(user(base_dn or self.base_dn,
+ uid, uidNumber, gidNumber,
+ userPassword=userPassword,
+ gecos=gecos,
+ homeDirectory=homeDirectory,
+ loginShell=loginShell,
+ cn=cn,
+ sn=sn,
+ sshPubKey=sshPubKey,
+ mail=mail))
+
+ def add_group(self, cn, gidNumber, member_uids=[],
+ base_dn=None):
+ """Add an RFC2307 group add-modlist."""
+ self.append(group(base_dn or self.base_dn,
+ cn, gidNumber, member_uids))
+
+ def add_group_bis(self, cn, gidNumber,
+ member_uids=[], member_gids=[],
+ base_dn=None):
+ """Add an RFC2307bis group add-modlist."""
+ self.append(group_bis(base_dn or self.base_dn,
+ cn, gidNumber,
+ member_uids, member_gids))
+
+ def add_netgroup(self, cn, triples=(), members=(), base_dn=None):
+ """Add an RFC2307bis netgroup add-modlist."""
+ self.append(netgroup(base_dn or self.base_dn,
+ cn, triples, members))
+
+ def add_sudo_rule(self, name,
+ users=(), hosts=(), commands=(),
+ base_dn=None):
+ self.append(sudo_rule(base_dn or self.base_dn,
+ name, users, hosts, commands))
+
+ def add_host(self, name, aliases=[], addresses=[], base_dn=None):
+ """Add an RFC2307 ipHost add-modlist."""
+ self.append(ip_host(base_dn or self.base_dn,
+ name, aliases, addresses))
+
+ def add_ipnet(self, name, address, aliases=[], base_dn=None):
+ """Add an RFC2307 ipNetwork add-modlist."""
+ self.append(ip_net(base_dn or self.base_dn,
+ name, address, aliases))
diff --git a/src/tests/intg/ldap_local_override_test.py b/src/tests/intg/ldap_local_override_test.py
new file mode 100644
index 0000000..dd153f6
--- /dev/null
+++ b/src/tests/intg/ldap_local_override_test.py
@@ -0,0 +1,1193 @@
+#
+# integration test for sss_override tool
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Pavel Reichl <preichl@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import stat
+import ent
+import pwd
+import config
+import signal
+import subprocess
+import time
+import pytest
+import ds_openldap
+import ldap_ent
+import sssd_id
+from util import unindent
+
+try:
+ from subprocess import check_output
+except ImportError:
+ # In Python 2.6, the module subprocess does not have the function
+ # check_output. This is a fallback implementation.
+ def check_output(*popenargs, **kwargs):
+ if 'stdout' in kwargs:
+ raise ValueError('stdout argument not allowed, it will be '
+ 'overridden.')
+ process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs,
+ **kwargs)
+ output, _ = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
+ raise subprocess.CalledProcessError(retcode, cmd, output=output)
+ return output
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, 'dc=example,dc=com',
+ "cn=admin", "Secret123")
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(lambda: ds_inst.teardown())
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(lambda: ldap_conn.unbind_s())
+ return ldap_conn
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list):
+ """Add LDAP entries and add teardown for removing them"""
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+ def teardown():
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+ request.addfinalizer(teardown)
+
+
+def create_conf_fixture(request, contents):
+ """Generate sssd.conf and add teardown for removing it"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+ request.addfinalizer(lambda: os.unlink(config.CONF_PATH))
+
+
+def stop_sssd():
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+
+
+def start_sssd():
+ """Start sssd"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def restart_sssd():
+ stop_sssd()
+ start_sssd()
+
+
+def create_sssd_fixture(request):
+ """Start sssd and add teardown for stopping it and removing state"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+ def teardown():
+ try:
+ stop_sssd()
+ except Exception:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+ request.addfinalizer(teardown)
+
+
+OVERRIDE_FILENAME = "export_file"
+
+
+def prepare_sssd(request, ldap_conn, use_fully_qualified_names=False,
+ case_sensitive=True, override_homedir_option=False):
+ """Prepare SSSD with defaults"""
+ conf_override_homedir_option = ""
+ if override_homedir_option:
+ conf_override_homedir_option = "override_homedir = /home/ov_option/%u"
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+ memcache_timeout = 1
+ {conf_override_homedir_option}
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ use_fully_qualified_names = {use_fully_qualified_names}
+ case_sensitive = {case_sensitive}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+ def teardown():
+ # remove user export file
+ try:
+ os.unlink(OVERRIDE_FILENAME)
+ except OSError:
+ pass
+ request.addfinalizer(teardown)
+
+
+#
+# Common asserts for users
+#
+
+def assert_user_default():
+
+ # Assert entries are not overriden
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user1')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user1@LDAP')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user2')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user2@LDAP')
+
+ user1 = dict(name='user1', passwd='*', uid=10001, gid=20001,
+ gecos='User Number 1',
+ dir='/home/user1',
+ shell='/bin/user1_shell')
+ user2 = dict(name='user2', passwd='*', uid=10002, gid=20001,
+ gecos='User Number 2',
+ dir='/home/user2',
+ shell='/bin/user2_shell')
+
+ ent.assert_passwd_by_name('user1', user1)
+ ent.assert_passwd_by_name('user1@LDAP', user1)
+
+ ent.assert_passwd_by_name('user2', user2)
+ ent.assert_passwd_by_name('user2@LDAP', user2)
+
+
+def assert_user_overriden(override_name=True):
+
+ if override_name:
+ name1 = "ov_user1"
+ name2 = "ov_user2"
+ else:
+ name1 = "user1"
+ name2 = "user2"
+
+ user1 = dict(name=name1, passwd='*', uid=10010, gid=20010,
+ gecos='Overriden User 1',
+ dir='/home/ov/user1',
+ shell='/bin/ov_user1_shell')
+
+ user2 = dict(name=name2, passwd='*', uid=10020, gid=20020,
+ gecos='Overriden User 2',
+ dir='/home/ov/user2',
+ shell='/bin/ov_user2_shell')
+
+ ent.assert_passwd_by_name('user1', user1)
+ ent.assert_passwd_by_name('user1@LDAP', user1)
+
+ if override_name:
+ ent.assert_passwd_by_name('ov_user1', user1)
+ ent.assert_passwd_by_name('ov_user1@LDAP', user1)
+
+ ent.assert_passwd_by_name('user2', user2)
+ ent.assert_passwd_by_name('user2@LDAP', user2)
+
+ if override_name:
+ ent.assert_passwd_by_name('ov_user2', user2)
+ ent.assert_passwd_by_name('ov_user2@LDAP', user2)
+
+
+#
+# Common fixtures for users
+#
+
+
+@pytest.fixture
+def env_two_users_and_group(request, ldap_conn):
+
+ prepare_sssd(request, ldap_conn)
+
+ # Add entries
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 10001, 20001,
+ gecos='User Number 1',
+ loginShell='/bin/user1_shell',
+ homeDirectory='/home/user1')
+
+ ent_list.add_user("user2", 10002, 20001,
+ gecos='User Number 2',
+ loginShell='/bin/user2_shell',
+ homeDirectory='/home/user2')
+
+ ent_list.add_group("group", 2001,
+ ["user2", "user1"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ # Assert entries are not overriden
+ assert_user_default()
+
+
+@pytest.fixture
+def env_two_users_and_group_overriden(request, ldap_conn,
+ env_two_users_and_group):
+
+ # Override
+ subprocess.check_call(["sss_override", "user-add", "user1",
+ "-u", "10010",
+ "-g", "20010",
+ "-n", "ov_user1",
+ "-c", "Overriden User 1",
+ "-h", "/home/ov/user1",
+ "-s", "/bin/ov_user1_shell"])
+
+ subprocess.check_call(["sss_override", "user-add", "user2@LDAP",
+ "-u", "10020",
+ "-g", "20020",
+ "-n", "ov_user2",
+ "-c", "Overriden User 2",
+ "-h", "/home/ov/user2",
+ "-s", "/bin/ov_user2_shell"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+ # Assert entries are overriden
+ assert_user_overriden()
+
+
+#
+# Simple user override
+#
+
+
+@pytest.fixture
+def env_simple_user_override(request, ldap_conn, env_two_users_and_group):
+
+ # Override
+ subprocess.check_call(["sss_override", "user-add", "user1",
+ "-u", "10010",
+ "-g", "20010",
+ "-n", "ov_user1",
+ "-c", "Overriden User 1",
+ "-h", "/home/ov/user1",
+ "-s", "/bin/ov_user1_shell"])
+
+ subprocess.check_call(["sss_override", "user-add", "user2@LDAP",
+ "-u", "10020",
+ "-g", "20020",
+ "-n", "ov_user2",
+ "-c", "Overriden User 2",
+ "-h", "/home/ov/user2",
+ "-s", "/bin/ov_user2_shell"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+
+def test_simple_user_override(ldap_conn, env_simple_user_override):
+ """Test entries are overriden"""
+
+ assert_user_overriden()
+
+
+#
+# Root user override
+#
+
+
+@pytest.fixture
+def env_root_user_override(request, ldap_conn, env_two_users_and_group):
+
+ # Assert entries are not overriden
+ ent.assert_passwd_by_name(
+ 'root',
+ dict(name='root', uid=0, gid=0))
+
+ ent.assert_passwd_by_uid(0, dict(name="root"))
+
+ # Override
+ subprocess.check_call(["sss_override", "user-add", "user1",
+ "-u", "0",
+ "-g", "0",
+ "-n", "ov_user1",
+ "-c", "Overriden User 1",
+ "-h", "/home/ov/user1",
+ "-s", "/bin/ov_user1_shell"])
+
+ subprocess.check_call(["sss_override", "user-add", "user2",
+ "-u", "10020",
+ "-g", "20020",
+ "-n", "root",
+ "-c", "Overriden User 2",
+ "-h", "/home/ov/user2",
+ "-s", "/bin/ov_user2_shell"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+
+def test_root_user_override(ldap_conn, env_root_user_override):
+ """Test entries are not overriden to root"""
+
+ # Override does not have to happen completly, trying to set uid or gid
+ # to 0 is simply ignored.
+ ent.assert_passwd_by_name(
+ 'ov_user1',
+ dict(name='ov_user1', passwd='*', uid=10001, gid=20001,
+ gecos='Overriden User 1',
+ dir='/home/ov/user1',
+ shell='/bin/ov_user1_shell'))
+
+ # We can create override with name root. This test is just for tracking
+ # that this particular behavior won't change.
+ ent.assert_passwd_by_name(
+ 'user2',
+ dict(name='root', passwd='*', uid=10020, gid=20020,
+ gecos='Overriden User 2',
+ dir='/home/ov/user2',
+ shell='/bin/ov_user2_shell'))
+
+ ent.assert_passwd_by_uid(0, dict(name="root"))
+
+
+#
+# Override replaces previous override
+#
+
+
+@pytest.fixture
+def env_replace_user_override(request, ldap_conn):
+
+ prepare_sssd(request, ldap_conn)
+
+ # Add entries
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 10001, 20001,
+ gecos='User Number 1',
+ loginShell='/bin/user1_shell',
+ homeDirectory='/home/user1')
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ # Assert entries are not overriden
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=10001, gid=20001,
+ gecos='User Number 1',
+ dir='/home/user1',
+ shell='/bin/user1_shell'))
+
+ # Override
+ subprocess.check_call(["sss_override", "user-add", "user1",
+ "-u", "10010",
+ "-g", "20010",
+ "-n", "ov_user1",
+ "-c", "Overriden User 1",
+ "-h", "/home/ov/user1",
+ "-s", "/bin/ov_user1_shell"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+ # Assert entries are overriden
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='ov_user1', passwd='*', uid=10010, gid=20010,
+ gecos='Overriden User 1',
+ dir='/home/ov/user1',
+ shell='/bin/ov_user1_shell'))
+
+ # Override of override
+ subprocess.check_call(["sss_override", "user-add", "user1",
+ "-u", "10100",
+ "-g", "20100",
+ "-n", "ov2_user1",
+ "-c", "Overriden2 User 1",
+ "-h", "/home/ov2/user1",
+ "-s", "/bin/ov2_user1_shell"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+
+def test_replace_user_override(ldap_conn, env_replace_user_override):
+
+ user = dict(name='ov2_user1', passwd='*', uid=10100, gid=20100,
+ gecos='Overriden2 User 1',
+ dir='/home/ov2/user1',
+ shell='/bin/ov2_user1_shell')
+
+ ent.assert_passwd_by_name('ov2_user1', user)
+ ent.assert_passwd_by_name('ov2_user1@LDAP', user)
+
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user1')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user1@LDAP')
+
+
+#
+# Override removal
+#
+
+
+@pytest.fixture
+def env_remove_user_override(request, ldap_conn,
+ env_two_users_and_group_overriden):
+
+ # Drop all overrides
+ subprocess.check_call(["sss_override", "user-del", "user1"])
+ subprocess.check_call(["sss_override", "user-del", "user2@LDAP"])
+
+ # Avoid hitting memory cache
+ time.sleep(2)
+
+
+def test_remove_user_override(ldap_conn, env_remove_user_override):
+
+ # Test entries are not overriden
+ assert_user_default()
+
+
+#
+# Override import/export
+#
+
+
+@pytest.fixture
+def env_imp_exp_user_override(request, ldap_conn,
+ env_two_users_and_group_overriden):
+
+ # Export overrides
+ subprocess.check_call(["sss_override", "user-export", OVERRIDE_FILENAME])
+
+ # Drop all overrides
+ subprocess.check_call(["sss_override", "user-del", "user1"])
+ subprocess.check_call(["sss_override", "user-del", "user2@LDAP"])
+
+ # Avoid hitting memory cache
+ time.sleep(2)
+
+ # Assert entries are not overridden
+ assert_user_default()
+
+ # Import overrides
+ subprocess.check_call(["sss_override", "user-import",
+ OVERRIDE_FILENAME])
+ restart_sssd()
+
+
+def test_imp_exp_user_override(ldap_conn, env_imp_exp_user_override):
+
+ assert_user_overriden()
+
+
+# Regression test for bug 3179
+
+
+def test_imp_exp_user_override_noname(ldap_conn,
+ env_two_users_and_group):
+
+ # Override
+ subprocess.check_call(["sss_override", "user-add", "user1",
+ "-u", "10010",
+ "-g", "20010",
+ "-c", "Overriden User 1",
+ "-h", "/home/ov/user1",
+ "-s", "/bin/ov_user1_shell"])
+
+ subprocess.check_call(["sss_override", "user-add", "user2@LDAP",
+ "-u", "10020",
+ "-g", "20020",
+ "-c", "Overriden User 2",
+ "-h", "/home/ov/user2",
+ "-s", "/bin/ov_user2_shell"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+ # Assert entries are overriden
+ assert_user_overriden(override_name=False)
+
+ # Export overrides
+ subprocess.check_call(["sss_override", "user-export", OVERRIDE_FILENAME])
+
+ # Drop all overrides
+ subprocess.check_call(["sss_override", "user-del", "user1"])
+ subprocess.check_call(["sss_override", "user-del", "user2@LDAP"])
+
+ # Avoid hitting memory cache
+ time.sleep(2)
+
+ # Assert entries are not overridden
+ assert_user_default()
+
+ # Import overrides
+ subprocess.check_call(["sss_override", "user-import",
+ OVERRIDE_FILENAME])
+ restart_sssd()
+
+ assert_user_overriden(override_name=False)
+
+
+#
+# Override user-show
+#
+
+
+@pytest.fixture
+def env_show_user_override(request, ldap_conn,
+ env_two_users_and_group_overriden):
+ pass
+
+
+def test_show_user_override(ldap_conn, env_show_user_override):
+
+ out = check_output(['sss_override', 'user-show', 'user1']).decode('utf-8')
+ assert out == "user1@LDAP:ov_user1:10010:20010:Overriden User 1:"\
+ "/home/ov/user1:/bin/ov_user1_shell:\n"
+
+ out = check_output(['sss_override', 'user-show',
+ 'user2@LDAP']).decode('utf-8')
+ assert out == "user2@LDAP:ov_user2:10020:20020:Overriden User 2:"\
+ "/home/ov/user2:/bin/ov_user2_shell:\n"
+
+ # Return error on non-existing user
+ ret = subprocess.call(['sss_override', 'user-show', 'nonexisting_user'])
+ assert ret == 1
+
+
+#
+# Override user-find
+#
+
+
+@pytest.fixture
+def env_find_user_override(request, ldap_conn,
+ env_two_users_and_group_overriden):
+ pass
+
+
+def test_find_user_override(ldap_conn, env_find_user_override):
+
+ out = check_output(['sss_override', 'user-find']).decode('utf-8')
+
+ # Expected override of users
+ exp_usr_ovrd = ['user1@LDAP:ov_user1:10010:20010:Overriden User 1:'
+ '/home/ov/user1:/bin/ov_user1_shell:',
+ 'user2@LDAP:ov_user2:10020:20020:Overriden User 2:'
+ '/home/ov/user2:/bin/ov_user2_shell:']
+
+ assert set(out.splitlines()) == set(exp_usr_ovrd)
+
+ out = check_output(['sss_override', 'user-find', '--domain=LDAP'])
+
+ assert set(out.decode('utf-8').splitlines()) == set(exp_usr_ovrd)
+
+ # Unexpected parameter is reported
+ ret = subprocess.call(['sss_override', 'user-find', 'PARAM'])
+ assert ret == 1
+
+
+#
+# Group tests
+#
+
+
+#
+# Common group asserts
+#
+
+def assert_group_overriden(override_name=True):
+
+ # Assert entries are overridden
+ empty_group = dict(gid=3002, mem=ent.contains_only())
+ group = dict(gid=3001, mem=ent.contains_only("user1", "user2"))
+
+ ent.assert_group_by_name("group", group)
+ ent.assert_group_by_name("group@LDAP", group)
+
+ if override_name:
+ ent.assert_group_by_name("ov_group", group)
+ ent.assert_group_by_name("ov_group@LDAP", group)
+
+ ent.assert_group_by_name("empty_group", empty_group)
+ ent.assert_group_by_name("empty_group@LDAP", empty_group)
+
+ if override_name:
+ ent.assert_group_by_name("ov_empty_group", empty_group)
+ ent.assert_group_by_name("ov_empty_group@LDAP", empty_group)
+
+
+def assert_group_default():
+
+ # Assert entries are not overridden
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_group')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_group@LDAP')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_empty_group')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_empty_group@LDAP')
+
+ empty_group = dict(gid=2002, mem=ent.contains_only())
+ group = dict(gid=2001, mem=ent.contains_only("user1", "user2"))
+
+ ent.assert_group_by_name("group", group)
+ ent.assert_group_by_name("group@LDAP", group)
+ ent.assert_group_by_name("empty_group", empty_group)
+ ent.assert_group_by_name("empty_group@LDAP", empty_group)
+
+
+#
+# Common fixtures for groups
+#
+
+
+@pytest.fixture
+def env_group_basic(request, ldap_conn):
+ prepare_sssd(request, ldap_conn)
+
+ # Add entries
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 10001, 20001,
+ gecos='User Number 1',
+ loginShell='/bin/user1_shell',
+ homeDirectory='/home/user1')
+
+ ent_list.add_user("user2", 10002, 20001,
+ gecos='User Number 2',
+ loginShell='/bin/user2_shell',
+ homeDirectory='/home/user2')
+
+ ent_list.add_group("group", 2001,
+ ["user2", "user1"])
+ ent_list.add_group("empty_group", 2002, [])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ # Assert entries are not overriden
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_group')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_group@LDAP')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_empty_group')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_empty_group@LDAP')
+
+
+@pytest.fixture
+def env_group_override(request, ldap_conn, env_group_basic):
+
+ # Override
+ subprocess.check_call(["sss_override", "group-add", "group",
+ "-n", "ov_group",
+ "-g", "3001"])
+
+ subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP",
+ "--name", "ov_empty_group",
+ "--gid", "3002"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+ # Assert entries are overridden
+ assert_group_overriden()
+
+
+#
+# Simple group override
+#
+
+
+@pytest.fixture
+def env_simple_group_override(request, ldap_conn, env_group_basic):
+
+ # Override
+ subprocess.check_call(["sss_override", "group-add", "group",
+ "-n", "ov_group",
+ "-g", "3001"])
+
+ subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP",
+ "--name", "ov_empty_group",
+ "--gid", "3002"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+
+def test_simple_group_override(ldap_conn, env_simple_group_override):
+ """Test entries are overriden"""
+
+ assert_group_overriden()
+
+
+#
+# Root group override
+#
+
+
+@pytest.fixture
+def env_root_group_override(request, ldap_conn, env_group_basic):
+
+ # Override
+ subprocess.check_call(["sss_override", "group-add", "group",
+ "-n", "ov_group",
+ "-g", "0"])
+
+ subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP",
+ "--name", "ov_empty_group",
+ "--gid", "0"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+
+def test_root_group_override(ldap_conn, env_root_group_override):
+ """Test entries are overriden"""
+
+ group = dict(gid=2001, mem=ent.contains_only("user1", "user2"))
+ empty_group = dict(gid=2002, mem=ent.contains_only())
+
+ ent.assert_group_by_name("group", group)
+ ent.assert_group_by_name("ov_group", group)
+ ent.assert_group_by_name("group@LDAP", group)
+ ent.assert_group_by_name("ov_group@LDAP", group)
+ ent.assert_group_by_name("empty_group", empty_group)
+ ent.assert_group_by_name("ov_empty_group", empty_group)
+ ent.assert_group_by_name("empty_group@LDAP", empty_group)
+ ent.assert_group_by_name("ov_empty_group@LDAP", empty_group)
+
+
+#
+# Replace group override
+#
+
+
+@pytest.fixture
+def env_replace_group_override(request, ldap_conn, env_group_override):
+
+ # Override of override
+ subprocess.check_call(["sss_override", "group-add", "group",
+ "-n", "ov2_group",
+ "-g", "4001"])
+
+ subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP",
+ "--name", "ov2_empty_group",
+ "--gid", "4002"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+
+def test_replace_group_override(ldap_conn, env_replace_group_override):
+
+ # Test overrides are overridden
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_group')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_group@LDAP')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_empty_group')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_empty_group@LDAP')
+
+ group = dict(gid=4001, mem=ent.contains_only("user1", "user2"))
+ empty_group = dict(gid=4002, mem=ent.contains_only())
+
+ ent.assert_group_by_name("group", group)
+ ent.assert_group_by_name("ov2_group", group)
+ ent.assert_group_by_name("group@LDAP", group)
+ ent.assert_group_by_name("ov2_group@LDAP", group)
+
+ ent.assert_group_by_name("empty_group", empty_group)
+ ent.assert_group_by_name("empty_group@LDAP", empty_group)
+ ent.assert_group_by_name("ov2_empty_group", empty_group)
+ ent.assert_group_by_name("ov2_empty_group@LDAP", empty_group)
+
+
+#
+# Remove group override
+#
+
+
+@pytest.fixture
+def env_remove_group_override(request, ldap_conn, env_group_override):
+
+ # Drop all overrides
+ subprocess.check_call(["sss_override", "group-del", "group"])
+ subprocess.check_call(["sss_override", "group-del", "empty_group@LDAP"])
+
+ # Avoid hitting memory cache
+ time.sleep(2)
+
+
+def test_remove_group_override(ldap_conn, env_remove_group_override):
+
+ # Test overrides were dropped
+ assert_group_default()
+
+
+#
+# Override group import/export
+#
+
+
+@pytest.fixture
+def env_imp_exp_group_override(request, ldap_conn, env_group_override):
+
+ # Export overrides
+ subprocess.check_call(["sss_override", "group-export",
+ OVERRIDE_FILENAME])
+
+ # Drop all overrides
+ subprocess.check_call(["sss_override", "group-del", "group"])
+ subprocess.check_call(["sss_override", "group-del", "empty_group@LDAP"])
+
+ # Avoid hitting memory cache
+ time.sleep(2)
+
+ assert_group_default()
+
+ # Import overrides
+ subprocess.check_call(["sss_override", "group-import",
+ OVERRIDE_FILENAME])
+ restart_sssd()
+
+
+def test_imp_exp_group_override(ldap_conn, env_imp_exp_group_override):
+
+ assert_group_overriden()
+
+
+# Regression test for bug 3179
+
+
+def test_imp_exp_group_override_noname(ldap_conn, env_group_basic):
+
+ # Override - do not use -n here)
+ subprocess.check_call(["sss_override", "group-add", "group",
+ "-g", "3001"])
+
+ subprocess.check_call(["sss_override", "group-add", "empty_group@LDAP",
+ "--gid", "3002"])
+
+ # Restart SSSD so the override might take effect
+ restart_sssd()
+
+ # Assert entries are overridden
+ assert_group_overriden(override_name=False)
+
+ # Export overrides
+ subprocess.check_call(["sss_override", "group-export",
+ OVERRIDE_FILENAME])
+
+ # Drop all overrides
+ subprocess.check_call(["sss_override", "group-del", "group"])
+ subprocess.check_call(["sss_override", "group-del", "empty_group@LDAP"])
+
+ # Avoid hitting memory cache
+ time.sleep(2)
+
+ assert_group_default()
+
+ # Import overrides
+ subprocess.check_call(["sss_override", "group-import",
+ OVERRIDE_FILENAME])
+ restart_sssd()
+
+ assert_group_overriden(override_name=False)
+
+
+# Regression test for bug #2802
+# sss_override segfaults when accidentally adding --help flag to some commands
+
+
+@pytest.fixture
+def env_regr_2802_override(request, ldap_conn):
+
+ prepare_sssd(request, ldap_conn)
+
+
+def test_regr_2802_override(ldap_conn, env_regr_2802_override):
+
+ subprocess.check_call(["sss_override", "user-del", "--help"])
+
+
+# Regression test for bug #2757
+# sss_override does not work correctly when 'use_fully_qualified_names = True'
+
+
+@pytest.fixture
+def env_regr_2757_override(request, ldap_conn):
+
+ prepare_sssd(request, ldap_conn, use_fully_qualified_names=True)
+
+ # Add entries
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 10001, 20001)
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ # Assert entries are not overridden
+ ent.assert_passwd_by_name(
+ 'user1@LDAP',
+ dict(name='user1@LDAP', passwd='*', uid=10001, gid=20001))
+ with pytest.raises(KeyError):
+ pwd.getpwnam('alias1')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('alias1@LDAP')
+
+ # Override
+ subprocess.check_call(["sss_override", "user-add", "user1@LDAP",
+ "-n", "alias1"])
+ restart_sssd()
+
+
+def test_regr_2757_override(ldap_conn, env_regr_2757_override):
+
+ # Assert entries are overridden
+ ent.assert_passwd_by_name(
+ 'user1@LDAP',
+ dict(name='alias1@LDAP', passwd='*', uid=10001, gid=20001))
+ ent.assert_passwd_by_name(
+ 'alias1@LDAP',
+ dict(name='alias1@LDAP', passwd='*', uid=10001, gid=20001))
+
+ with pytest.raises(KeyError):
+ pwd.getpwnam('user1')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('alias1')
+
+
+# Regression test for bug #2790
+# sss_override --name doesn't work with RFC2307 and ghost users
+
+
+@pytest.fixture
+def env_regr_2790_override(request, ldap_conn):
+
+ prepare_sssd(request, ldap_conn)
+
+ # Add entries
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 10001, 20001)
+ ent_list.add_user("user2", 10002, 20002)
+ ent_list.add_group("group1", 2001,
+ ["user1", "user2"])
+ ent_list.add_group("group2", 2002,
+ ["user2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ # Assert entries are not overridden
+ with pytest.raises(KeyError):
+ pwd.getpwnam('alias1')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('alias1@LDAP')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('alias2')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('alias2@LDAP')
+
+ # Override
+ subprocess.check_call(["sss_override", "user-add", "user1",
+ "-n", "alias1"])
+ subprocess.check_call(["sss_override", "user-add", "user2",
+ "-n", "alias2"])
+
+ restart_sssd()
+
+
+def test_regr_2790_override(ldap_conn, env_regr_2790_override):
+
+ # Assert entries are overridden
+ (res, errno, grp_list) = sssd_id.get_user_groups("alias1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1 %d" % errno
+ assert sorted(grp_list) == sorted(["20001", "group1"])
+
+ (res, errno, grp_list) = sssd_id.get_user_groups("alias2")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user2 %d" % errno
+ assert sorted(grp_list) == sorted(["20002", "group1", "group2"])
+
+
+# Test fully qualified and case-insensitive names
+@pytest.fixture
+def env_mix_cased_name_override(request, ldap_conn):
+ """Setup test for mixed case names"""
+
+ prepare_sssd(request, ldap_conn, True, False)
+
+ # Add entries
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 10001, 20001)
+ ent_list.add_user("uSeR2", 10002, 20002)
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ pwd.getpwnam('user1@LDAP')
+ pwd.getpwnam('user2@LDAP')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user1@LDAP')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user2@LDAP')
+
+ # Override
+ subprocess.check_call(["sss_override", "user-add", "user1@LDAP",
+ "-u", "10010",
+ "-g", "20010",
+ "-n", "ov_user1",
+ "-c", "Overriden User 1",
+ "-h", "/home/ov/user1",
+ "-s", "/bin/ov_user1_shell"])
+
+ subprocess.check_call(["sss_override", "user-add", "user2@LDAP",
+ "-u", "10020",
+ "-g", "20020",
+ "-n", "ov_user2",
+ "-c", "Overriden User 2",
+ "-h", "/home/ov/user2",
+ "-s", "/bin/ov_user2_shell"])
+
+ restart_sssd()
+
+
+def test_mix_cased_name_override(ldap_conn, env_mix_cased_name_override):
+ """Test if names with upper and lower case letter are overridden"""
+
+ # Assert entries are overridden
+ user1 = dict(name='ov_user1@LDAP', passwd='*', uid=10010, gid=20010,
+ gecos='Overriden User 1',
+ dir='/home/ov/user1',
+ shell='/bin/ov_user1_shell')
+
+ user2 = dict(name='ov_user2@LDAP', passwd='*', uid=10020, gid=20020,
+ gecos='Overriden User 2',
+ dir='/home/ov/user2',
+ shell='/bin/ov_user2_shell')
+
+ ent.assert_passwd_by_name('user1@LDAP', user1)
+ ent.assert_passwd_by_name('ov_user1@LDAP', user1)
+
+ ent.assert_passwd_by_name('user2@LDAP', user2)
+ ent.assert_passwd_by_name('ov_user2@LDAP', user2)
+
+
+# Test with override_homedir option
+@pytest.fixture
+def env_override_homedir_option(request, ldap_conn):
+ """Setup test for override_homedir option and overrides"""
+
+ prepare_sssd(request, ldap_conn, override_homedir_option=True)
+
+ # Add entries
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 10001, 20001)
+ ent_list.add_user("user2", 10002, 20002)
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ pwd.getpwnam('user1@LDAP')
+ pwd.getpwnam('user2@LDAP')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user1@LDAP')
+ with pytest.raises(KeyError):
+ pwd.getpwnam('ov_user2@LDAP')
+
+ # Override
+ subprocess.check_call(["sss_override", "user-add", "user1@LDAP",
+ "-u", "10010",
+ "-g", "20010",
+ "-n", "ov_user1",
+ "-c", "Overriden User 1",
+ # no homedir override
+ "-s", "/bin/ov_user1_shell"])
+
+ subprocess.check_call(["sss_override", "user-add", "user2@LDAP",
+ "-u", "10020",
+ "-g", "20020",
+ "-n", "ov_user2",
+ "-c", "Overriden User 2",
+ "-h", "/home/ov/user2",
+ "-s", "/bin/ov_user2_shell"])
+
+ restart_sssd()
+
+
+def test_override_homedir_option(ldap_conn, env_override_homedir_option):
+ """Test if overrides will overwrite override_homedir option"""
+
+ # Assert entries are overridden, user1 has no homedir override and
+ # override_homedir option should be used, user2 has a homedir override
+ # which should be used.
+ user1 = dict(name='ov_user1', passwd='*', uid=10010, gid=20010,
+ gecos='Overriden User 1',
+ dir='/home/ov_option/ov_user1',
+ shell='/bin/ov_user1_shell')
+
+ user2 = dict(name='ov_user2', passwd='*', uid=10020, gid=20020,
+ gecos='Overriden User 2',
+ dir='/home/ov/user2',
+ shell='/bin/ov_user2_shell')
+
+ ent.assert_passwd_by_name('user1@LDAP', user1)
+ ent.assert_passwd_by_name('ov_user1@LDAP', user1)
+ ent.assert_passwd_by_name('user1', user1)
+ ent.assert_passwd_by_name('ov_user1', user1)
+
+ ent.assert_passwd_by_name('user2@LDAP', user2)
+ ent.assert_passwd_by_name('ov_user2@LDAP', user2)
+ ent.assert_passwd_by_name('user2', user2)
+ ent.assert_passwd_by_name('ov_user2', user2)
diff --git a/src/tests/intg/nss_call.c b/src/tests/intg/nss_call.c
new file mode 100644
index 0000000..a8c7d30
--- /dev/null
+++ b/src/tests/intg/nss_call.c
@@ -0,0 +1,166 @@
+/*
+ NSS module which calls glibc's user and group lookup functions again
+
+ DO NOT USE THIS IN /etc/nsswitch.conf, it will cause an infinite loop.
+
+ The main use case is to run proxy provider tests with cwrap's nss-wrapper.
+ The proxy provider loads the NSS modules directly with dlopen() and is not
+ using glibc's NSS mechanism. Since nss-wrapper just wraps the standard
+ glibc calls and does not provide an NSS module on its own we have to use
+ this workaround to make proxy provider work with nss-wrapper.
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (c) 2023 Red Hat, Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#define _DEFAULT_SOURCE
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <nss.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+
+
+#define NSSRET(r) return (((r) == 0) ? NSS_STATUS_SUCCESS : NSS_STATUS_NOTFOUND )
+
+enum nss_status _nss_call_getpwnam_r(const char *name, struct passwd *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct passwd *res;
+ int ret = getpwnam_r(name, result, buffer, buflen, &res);
+ NSSRET(ret);
+}
+
+enum nss_status _nss_call_getpwuid_r(uid_t uid, struct passwd *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct passwd *res;
+ int ret = getpwuid_r(uid, result, buffer, buflen, &res);
+ NSSRET(ret);
+}
+
+enum nss_status _nss_call_setpwent(void)
+{
+ setpwent();
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_call_getpwent_r(struct passwd *result,
+ char *buffer, size_t buflen,
+ int *errnop)
+{
+ struct passwd *res;
+ int ret = getpwent_r(result, buffer, buflen, &res);
+ NSSRET(ret);
+}
+
+enum nss_status _nss_call_endpwent(void)
+{
+ endpwent();
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_call_getgrnam_r(const char *name, struct group *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct group *res;
+ int ret = getgrnam_r(name, result, buffer, buflen, &res);
+ NSSRET(ret);
+}
+
+enum nss_status _nss_call_getgrgid_r(gid_t gid, struct group *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct group *res;
+ int ret = getgrgid_r(gid, result, buffer, buflen, &res);
+ NSSRET(ret);
+}
+
+enum nss_status _nss_call_setgrent(void)
+{
+ setgrent();
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_call_getgrent_r(struct group *result,
+ char *buffer, size_t buflen, int *errnop)
+{
+ struct group *res;
+ int ret = getgrent_r(result, buffer, buflen, &res);
+ NSSRET(ret);
+}
+
+enum nss_status _nss_call_endgrent(void)
+{
+ endgrent();
+ return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_call_initgroups_dyn(const char *user, gid_t group,
+ long int *start, long int *size,
+ gid_t **groups, long int limit,
+ int *errnop)
+{
+ int ngroups = 0;
+ gid_t *grps = NULL;
+ long int max_ret;
+ long int i;
+ int ret;
+
+ ret = getgrouplist(user, group, grps, &ngroups);
+ if (ret != -1) {
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ grps = malloc(ngroups * sizeof(gid_t));
+ if (grps == NULL) {
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ max_ret = ngroups;
+ /* check we have enough space in the buffer */
+ if ((*size - *start) < ngroups) {
+ long int newsize;
+ gid_t *newgroups;
+
+ newsize = *size + ngroups;
+ if ((limit > 0) && (newsize > limit)) {
+ newsize = limit;
+ max_ret = newsize - *start;
+ }
+
+ newgroups = (gid_t *)realloc((*groups), newsize * sizeof(**groups));
+ if (!newgroups) {
+ free(grps);
+ return NSS_STATUS_UNAVAIL;
+ }
+ *groups = newgroups;
+ *size = newsize;
+ }
+
+ for (i = 0; i < max_ret; i++) {
+ (*groups)[*start] = grps[i];
+ *start += 1;
+ }
+ free(grps);
+
+ return NSS_STATUS_SUCCESS;
+}
diff --git a/src/tests/intg/sss_netgroup_thread_test.c b/src/tests/intg/sss_netgroup_thread_test.c
new file mode 100644
index 0000000..d3bee9b
--- /dev/null
+++ b/src/tests/intg/sss_netgroup_thread_test.c
@@ -0,0 +1,81 @@
+/*
+ Helper program to test if innetgr() is thread-safe
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (c) 2021 Red Hat, Inc.
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <grp.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+
+
+struct data {
+ const char *group;
+ const char *host;
+ const char *user;
+ const char *domain;
+ bool *failed;
+};
+
+static void *full_netgroup(void *arg)
+{
+ int ret;
+ size_t c = 0;
+ struct data *data = arg;
+
+ do {
+ ret = innetgr(data->group, data->host, data->user, data->domain);
+ if (ret != 1) {
+ *(data->failed) = true;
+ }
+ c++;
+ } while (!*(data->failed) && c<100000);
+
+ pthread_exit(NULL);
+}
+
+int main()
+{
+ pthread_t thread[2];
+ bool failed[2] = {false, false};
+
+ struct data data[3] = {{"ng1", "host1", "user924", "domain1", &failed[0]},
+ {"ng2", "host2", "user925", "domain2", &failed[1]},
+ {NULL, NULL, NULL, NULL, NULL}};
+
+
+ pthread_create(&thread[0], NULL, full_netgroup, &data[0]);
+ pthread_create(&thread[1], NULL, full_netgroup, &data[1]);
+
+ pthread_join(thread[1], NULL);
+ pthread_join(thread[0], NULL);
+
+ if (failed[0] || failed[1]) {
+ printf ("Test failed.\n");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/src/tests/intg/sssd_group.py b/src/tests/intg/sssd_group.py
new file mode 100644
index 0000000..b6a37c5
--- /dev/null
+++ b/src/tests/intg/sssd_group.py
@@ -0,0 +1,132 @@
+#
+# Module for simulation of utility "getent group -s sss" from coreutils
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+from ctypes import (c_int, c_char_p, c_ulong, POINTER, Structure,
+ create_string_buffer)
+from sssd_nss import NssReturnCode, SssdNssError, nss_sss_ctypes_loader
+
+GROUP_BUFLEN = 1024
+
+
+class Group(Structure):
+ _fields_ = [("gr_name", c_char_p),
+ ("gr_passwd", c_char_p),
+ ("gr_gid", c_int),
+ ("gr_mem", POINTER(c_char_p))]
+
+
+def getgrnam_r(name, result_p, buffer_p, buflen):
+ """
+ ctypes wrapper for:
+ enum nss_status _nss_sss_getgrnam_r(const char *name,
+ struct group *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_getgrnam_r")
+ func.restype = c_int
+ func.argtypes = [c_char_p, POINTER(Group),
+ c_char_p, c_ulong, POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+
+ name = name.encode('utf-8')
+ res = func(c_char_p(name), result_p, buffer_p, buflen, errno)
+
+ return (int(res), int(errno[0]), result_p)
+
+
+def getgrgid_r(gid, result_p, buffer_p, buflen):
+ """
+ ctypes wrapper for:
+ enum nss_status _nss_sss_getgrgid_r(gid_t gid,
+ struct passwd *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_getgrgid_r")
+ func.restype = c_int
+ func.argtypes = [c_ulong, POINTER(Group),
+ c_char_p, c_ulong, POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+
+ res = func(gid, result_p, buffer_p, buflen, errno)
+
+ return (int(res), int(errno[0]), result_p)
+
+
+def set_group_dict(res, result_p):
+ if res != NssReturnCode.SUCCESS:
+ return dict()
+
+ group_dict = dict()
+ group_dict['name'] = result_p[0].gr_name.decode('utf-8')
+ group_dict['gid'] = result_p[0].gr_gid
+ group_dict['mem'] = list()
+
+ i = 0
+ while result_p[0].gr_mem[i] is not None:
+ grp_name = result_p[0].gr_mem[i].decode('utf-8')
+ group_dict['mem'].append(grp_name)
+ i = i + 1
+
+ return group_dict
+
+
+def call_sssd_getgrnam(name):
+ """
+ A Python wrapper to retrieve a group by name. Returns:
+ (res, group_dict)
+ if res is NssReturnCode.SUCCESS, then group_dict contains the keys
+ corresponding to the C passwd structure fields. Otherwise, the dictionary
+ is empty and errno indicates the error code
+ """
+ result = Group()
+ result_p = POINTER(Group)(result)
+ buff = create_string_buffer(GROUP_BUFLEN)
+
+ res, errno, result_p = getgrnam_r(name, result_p, buff, GROUP_BUFLEN)
+ if errno != 0:
+ raise SssdNssError(errno, "getgrnam_r")
+
+ group_dict = set_group_dict(res, result_p)
+ return res, group_dict
+
+
+def call_sssd_getgrgid(gid):
+ """
+ A Python wrapper to retrieve a group by GID. Returns:
+ (res, group_dict)
+ if res is NssReturnCode.SUCCESS, then group_dict contains the keys
+ corresponding to the C passwd structure fields. Otherwise, the dictionary
+ is empty and errno indicates the error code
+ """
+ result = Group()
+ result_p = POINTER(Group)(result)
+ buff = create_string_buffer(GROUP_BUFLEN)
+
+ res, errno, result_p = getgrgid_r(gid, result_p, buff, GROUP_BUFLEN)
+ if errno != 0:
+ raise SssdNssError(errno, "getgrgid_r")
+
+ group_dict = set_group_dict(res, result_p)
+ return res, group_dict
diff --git a/src/tests/intg/sssd_hosts.py b/src/tests/intg/sssd_hosts.py
new file mode 100644
index 0000000..1473277
--- /dev/null
+++ b/src/tests/intg/sssd_hosts.py
@@ -0,0 +1,142 @@
+#
+# Module for simulation of utility "getent hosts -s sss" from coreutils
+#
+# Authors:
+# Samuel Cabrero <scabrero@suse.com>
+#
+# Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from ctypes import (c_int, c_char_p, c_ulong, POINTER,
+ Structure, create_string_buffer)
+from sssd_nss import NssReturnCode, SssdNssError, nss_sss_ctypes_loader
+import socket
+from ipaddress import IPv4Address, IPv6Address
+
+HOST_BUFLEN = 1024
+
+
+class Hostent(Structure):
+ _fields_ = [("h_name", c_char_p),
+ ("h_aliases", POINTER(c_char_p)),
+ ("h_addrtype", c_int),
+ ("h_length", c_int),
+ ("h_addr_list", POINTER(c_char_p))]
+
+
+def gethostbyname_r(name, result_p, buffer_p, buflen):
+ """
+ ctypes wrapper for:
+ enum nss_status _nss_sss_gethostbyname_r(const char *name,
+ struct hostent *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_gethostbyname_r")
+ func.restype = c_int
+ func.argtypes = [c_char_p, POINTER(Hostent),
+ c_char_p, c_ulong, POINTER(c_int), POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+ h_errno = POINTER(c_int)(c_int(0))
+
+ name = name.encode('utf-8')
+ res = func(c_char_p(name), result_p, buffer_p, buflen, errno, h_errno)
+
+ return (int(res), int(errno[0]), int(h_errno[0]), result_p)
+
+
+def gethostbyname2_r(name, af, result_p, buffer_p, buflen):
+ """
+ ctypes wrapper for:
+ enum nss_status _nss_sss_gethostbyname2_r(const char *name,
+ int af,
+ struct hostent *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_gethostbyname2_r")
+ func.restype = c_int
+ func.argtypes = [c_char_p, c_int, POINTER(Hostent),
+ c_char_p, c_ulong, POINTER(c_int), POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+ h_errno = POINTER(c_int)(c_int(0))
+
+ name = name.encode('utf-8')
+ res = func(c_char_p(name), af, result_p, buffer_p, buflen, errno, h_errno)
+
+ return (int(res), int(errno[0]), int(h_errno[0]), result_p)
+
+
+def set_hostent_dict(res, result_p):
+ if res != NssReturnCode.SUCCESS:
+ return dict()
+
+ hostent_dict = dict()
+ hostent_dict['name'] = result_p[0].h_name.decode('utf-8')
+ hostent_dict['aliases'] = list()
+ hostent_dict['addrtype'] = result_p[0].h_addrtype
+ hostent_dict['length'] = result_p[0].h_length
+ hostent_dict['addresses'] = list()
+
+ i = 0
+ while result_p[0].h_aliases[i] is not None:
+ alias = result_p[0].h_aliases[i].decode('utf-8')
+ hostent_dict['aliases'].append(alias)
+ i = i + 1
+
+ i = 0
+ while result_p[0].h_addr_list[i] is not None:
+ length = result_p[0].h_length
+ binaddr = result_p[0].h_addr_list[i][:length]
+ if result_p[0].h_addrtype == socket.AF_INET:
+ addr = IPv4Address(binaddr)
+ addr = socket.inet_ntop(socket.AF_INET, addr.packed)
+ elif result_p[0].h_addrtype == socket.AF_INET6:
+ addr = IPv6Address(binaddr)
+ addr = socket.inet_ntop(socket.AF_INET, addr.packed)
+ else:
+ raise Exception("Failed to parse IP address")
+
+ hostent_dict['addresses'].append(addr)
+ i = i + 1
+
+ return hostent_dict
+
+
+def call_sssd_gethostbyname(name):
+ """
+ A Python wrapper to retrieve a host by name. Returns:
+ (res, hostent_dict)
+ if res is NssReturnCode.SUCCESS, then hostent_dict contains the keys
+ corresponding to the C hostent structure fields. Otherwise, the dictionary
+ is empty and errno indicates the error code
+ """
+ result = Hostent()
+ result_p = POINTER(Hostent)(result)
+ buff = create_string_buffer(HOST_BUFLEN)
+
+ (res, errno, h_errno, result_p) = gethostbyname_r(name, result_p,
+ buff, HOST_BUFLEN)
+ if errno != 0:
+ raise SssdNssError(errno, "gethostbyname_r")
+
+ hostent_dict = set_hostent_dict(res, result_p)
+ return (res, h_errno, hostent_dict)
diff --git a/src/tests/intg/sssd_id.py b/src/tests/intg/sssd_id.py
new file mode 100644
index 0000000..3a45e3b
--- /dev/null
+++ b/src/tests/intg/sssd_id.py
@@ -0,0 +1,129 @@
+#
+# Module for simulation of utility "id" from coreutils
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Lukas Slebodnik <lslebodn@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import pwd
+import grp
+from ctypes import (c_int, c_char, c_uint32, c_long, c_char_p,
+ POINTER, pointer)
+from sssd_nss import NssReturnCode, nss_sss_ctypes_loader
+
+
+def call_sssd_initgroups(user, gid):
+ """
+ Function will initialize the supplementary group access list
+ for given user. It will gather groups only provided by sssd.
+
+ Arguments are the same as for C function initgroups
+ @param string user name of user
+ @param int gid the additional gid will be also added to the list.
+
+ @return (int, int, List[int]) (err, errno, gids)
+ gids should contain user group IDs if err is NssReturnCode.SUCCESS
+ otherwise errno will contain non-zero value.
+ """
+ func = nss_sss_ctypes_loader('_nss_sss_initgroups_dyn')
+
+ func.restype = c_int
+ func.argtypes = [POINTER(c_char), c_uint32, POINTER(c_long),
+ POINTER(c_long), POINTER(POINTER(c_uint32)), c_long,
+ POINTER(c_int)]
+
+ start = POINTER(c_long)(c_long(0))
+ size = POINTER(c_long)(c_long(0))
+ groups = POINTER(c_uint32)()
+ p_groups = pointer(groups)
+ limit = c_long(-1)
+ errno = POINTER(c_int)(c_int(0))
+
+ res = func(c_char_p(user.encode('utf-8)')), c_uint32(gid), start, size,
+ p_groups, limit, errno)
+
+ gids = []
+ if res == NssReturnCode.SUCCESS:
+ gids_count = size[0]
+ assert gids_count > 0, "_nss_sss_initgroups_dyn should return " \
+ "one gid"
+
+ for i in range(0, gids_count):
+ gids.append(int(p_groups.contents[i]))
+
+ # add primary group if missing
+ if gid not in gids:
+ gids.append(gid)
+
+ return (int(res), errno[0], gids)
+
+
+def get_user_gids(user):
+ """
+ Function will initialize the supplementary group access list
+ for given user. It will gather groups only provided by sssd.
+
+ Arguments are the same as for C function initgroups
+ @param string user name of user
+
+ @return (int, int, List[int]) (err, errno, gids)
+ gids should contain user group IDs if err is NssReturnCode.SUCCESS
+ otherwise errno will contain non-zero value.
+ """
+ pwd_user = pwd.getpwnam(user)
+ uid = pwd_user.pw_uid
+ gid = pwd_user.pw_gid
+
+ user = pwd.getpwuid(uid).pw_name
+
+ return call_sssd_initgroups(user, gid)
+
+
+def gid_to_str(gid):
+ """
+ Function will map numeric GID into names.
+ If there isn't a group for GID (getgrgid failed)
+ then the function will return decimal representation of ID.
+
+ @param int gid ID of groups which should be converted to string.
+ @return string name of group with requested ID or decimal
+ representation of ID
+ """
+ try:
+ return grp.getgrgid(gid).gr_name
+ except KeyError:
+ return str(gid)
+
+
+def get_user_groups(user):
+ """
+ Function will initialize the supplementary group access list
+ for given user. It will gather groups only provided by sssd.
+
+ Arguments are the same as for C function initgroups
+ @param string user name of user
+
+ @return (int, int, List[string]) (err, errno, groups)
+ groups should contain names of user groups
+ if err is NssReturnCode.SUCCESS
+ otherwise errno will contain non-zero value.
+ """
+ (res, errno, gids) = get_user_gids(user)
+ groups = []
+
+ if res == NssReturnCode.SUCCESS:
+ groups = [gid_to_str(gid) for gid in gids]
+
+ return (res, errno, groups)
diff --git a/src/tests/intg/sssd_ldb.py b/src/tests/intg/sssd_ldb.py
new file mode 100644
index 0000000..746dbfa
--- /dev/null
+++ b/src/tests/intg/sssd_ldb.py
@@ -0,0 +1,96 @@
+#
+# SSSD integration test - access the ldb cache
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import ldb
+import config
+
+
+class CacheType(object):
+ sysdb = 1
+ timestamps = 2
+
+
+class TsCacheEntry(object):
+ user = 1
+ group = 2
+
+
+class SssdLdb(object):
+ def __init__(self, domain_name):
+ self._domain_name = domain_name
+ self._sysdb = self._create_dbconn(CacheType.sysdb,
+ domain_name)
+ self._timestamps = self._create_dbconn(CacheType.timestamps,
+ domain_name)
+
+ def _create_dbconn(self, cache_type, domain_name):
+ if cache_type == CacheType.sysdb:
+ db_path = os.path.join(config.DB_PATH,
+ "cache_%s.ldb" % domain_name)
+ elif cache_type == CacheType.timestamps:
+ db_path = os.path.join(config.DB_PATH,
+ "timestamps_%s.ldb" % domain_name)
+ else:
+ raise ValueError("Unknown cache type\n")
+
+ pyldb = ldb.Ldb()
+ pyldb.connect(db_path)
+ return pyldb
+
+ def _get_dbconn(self, cache_type):
+ dbconn = None
+ if cache_type == CacheType.sysdb:
+ dbconn = self._sysdb
+ elif cache_type == CacheType.timestamps:
+ dbconn = self._timestamps
+ return dbconn
+
+ def _entry_basedn(self, entry_type):
+ if entry_type == TsCacheEntry.user:
+ rdn = "users"
+ elif entry_type == TsCacheEntry.group:
+ rdn = "groups"
+ else:
+ raise ValueError("Unknown entry type\n")
+ return "cn=%s,cn=%s,cn=sysdb" % (rdn, self._domain_name)
+
+ def _basedn(self, name, domain, entry_type):
+ return "name=%s@%s,%s" % (name, domain.lower(),
+ self._entry_basedn(entry_type))
+
+ def get_entry_attr(self, cache_type, entry_type, name, domain, attr):
+ dbconn = self._get_dbconn(cache_type)
+ basedn = self._basedn(name, domain, entry_type)
+
+ res = dbconn.search(base=basedn, scope=ldb.SCOPE_BASE, attrs=[attr])
+ if res.count != 1:
+ return None
+
+ return res.msgs[0].get(attr).get(0)
+
+ def invalidate_entry(self, name, entry_type, domain):
+ dbconn = self._get_dbconn(CacheType.timestamps)
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(dbconn, self._basedn(name, domain, entry_type))
+ m["dataExpireTimestamp"] = ldb.MessageElement(str(1),
+ ldb.FLAG_MOD_REPLACE,
+ "dataExpireTimestamp")
+ dbconn.modify(m)
diff --git a/src/tests/intg/sssd_netgroup.py b/src/tests/intg/sssd_netgroup.py
new file mode 100644
index 0000000..81d017f
--- /dev/null
+++ b/src/tests/intg/sssd_netgroup.py
@@ -0,0 +1,247 @@
+#
+# Module for simulation of utility "getent netgroup -s sss" from coreutils
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# Author: Lukas Slebodnik <lslebodn@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+from ctypes import (c_int, c_char, c_char_p, c_size_t, c_void_p, c_ulong,
+ POINTER, Structure, Union, create_string_buffer, get_errno)
+from sssd_nss import NssReturnCode, nss_sss_ctypes_loader
+
+
+class NetgroupType(object):
+ """ 'enum' class for type of netgroup """
+ TRIPLE_VAL = 0
+ GROUP_VAL = 1
+
+
+class Triple(Structure):
+ _fields_ = [("host", c_char_p),
+ ("user", c_char_p),
+ ("domain", c_char_p)]
+
+
+class Val(Union):
+ _fields_ = [("triple", Triple),
+ ("group", c_char_p)]
+
+
+class Idx(Union):
+ _fields_ = [("cursor", POINTER(c_char)),
+ ("position", c_ulong)]
+
+
+class NameList(Structure):
+ pass
+
+
+NameList._fields_ = [("next", POINTER(NameList)),
+ ("name", POINTER(c_char))]
+
+
+class Netgrent(Structure):
+ _fields_ = [("type", c_int),
+ ("val", Val),
+ ("data", POINTER(c_char)),
+ ("data_size", c_size_t),
+ ("idx", Idx),
+ ("first", c_int),
+ ("known_groups", POINTER(NameList)),
+ ("needed_groups", POINTER(NameList)),
+ ("nip", c_void_p)]
+
+
+class NetgroupRetriever(object):
+ def __init__(self, name):
+ self.name = name.encode('utf-8')
+ self.needed_groups = []
+ self.known_groups = []
+ self.netgroups = []
+
+ @staticmethod
+ def _setnetgrent(netgroup):
+ """
+ This private method is ctypes wrapper for
+ enum nss_status _nss_sss_setnetgrent(const char *netgroup,
+ struct __netgrent *result)
+
+ @param string name name of netgroup
+
+ @return (int, POINTER(Netgrent)) (err, result_p)
+ err is a constant from class NssReturnCode and in case of SUCCESS
+ result_p will contain POINTER(Netgrent) which can be used in
+ _getnetgrent_r or _getnetgrent_r.
+ """
+ func = nss_sss_ctypes_loader('_nss_sss_setnetgrent')
+ func.restype = c_int
+ func.argtypes = [c_char_p, POINTER(Netgrent)]
+
+ result = Netgrent()
+ result_p = POINTER(Netgrent)(result)
+
+ res = func(c_char_p(netgroup), result_p)
+
+ return (int(res), result_p)
+
+ @staticmethod
+ def _getnetgrent_r(result_p, buff, buff_len):
+ """
+ This private method is ctypes wrapper for
+ enum nss_status _nss_sss_getnetgrent_r(struct __netgrent *result,
+ char *buffer, size_t buflen,
+ int *errnop)
+ @param POINTER(Netgrent) result_p pointer to initialized C structure
+ struct __netgrent
+ @param ctypes.c_char_Array buff buffer used by C functions
+ @param int buff_len size of c_char_Array passed as a parameter buff
+
+ @return (int, int, List[(string, string, string])
+ (err, errno, netgroups)
+ if err is NssReturnCode.SUCCESS netgroups will contain list of
+ touples. Each touple will consist of 3 elements either string or
+ """
+ func = nss_sss_ctypes_loader('_nss_sss_getnetgrent_r')
+ func.restype = c_int
+ func.argtypes = [POINTER(Netgrent), POINTER(c_char), c_size_t,
+ POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+
+ res = func(result_p, buff, buff_len, errno)
+
+ return (int(res), int(errno[0]), result_p)
+
+ @staticmethod
+ def _endnetgrent(result_p):
+ """
+ This private method is ctypes wrapper for
+ enum nss_status _nss_sss_endnetgrent(struct __netgrent *result)
+
+ @param POINTER(Netgrent) result_p pointer to initialized C structure
+ struct __netgrent
+
+ @return int a constant from class NssReturnCode
+ """
+ func = nss_sss_ctypes_loader('_nss_sss_endnetgrent')
+ func.restype = c_int
+ func.argtypes = [POINTER(Netgrent)]
+
+ res = func(result_p)
+
+ return int(res)
+
+ def get_netgroups(self):
+ """
+ Function will return netgroup triplets for given user. All nested
+ netgroups will be retrieved as part of executions and will content
+ will be merged with direct triplets.
+ Missing nested netgroups will not cause failure and are considered
+ as an empty netgroup without triplets.
+
+ @param string name name of netgroup
+
+ @return (int, int, List[(string, string, string])
+ (err, errno, netgroups)
+ if err is NssReturnCode.SUCCESS netgroups will contain list of
+ touples. Each touple will consist of 3 elements either string or
+ None (host, user, domain).
+ """
+ res, errno, result = self._flat_fetch_netgroups(self.name)
+ if res != NssReturnCode.SUCCESS:
+ return (res, errno, self.netgroups)
+
+ self.netgroups += result
+
+ while self.needed_groups:
+ name = self.needed_groups.pop(0)
+
+ nest_res, nest_errno, result = self._flat_fetch_netgroups(name)
+ # do not fail for missing nested netgroup
+ if nest_res not in (NssReturnCode.SUCCESS, NssReturnCode.NOTFOUND):
+ return (nest_res, nest_errno, self.netgroups)
+
+ self.netgroups = result + self.netgroups
+
+ return (res, errno, self.netgroups)
+
+ def _flat_fetch_netgroups(self, name):
+ """
+ Function will return netgroup triplets for given user. The nested
+ netgroups will not be returned. Missing nested netgroups will be
+ appended to the array needed_groups
+
+ @param string name name of netgroup
+
+ @return (int, int, List[(string, string, string])
+ (err, errno, netgroups)
+ if err is NssReturnCode.SUCCESS netgroups will contain list of
+ touples. Each touple will consist of 3 elements either string or
+ None (host, user, domain).
+ """
+ buff_len = 1024 * 1024
+ buff = create_string_buffer(buff_len)
+
+ result = []
+
+ res, result_p = self._setnetgrent(name)
+ if res != NssReturnCode.SUCCESS:
+ return (res, get_errno(), result)
+
+ res, errno, result_p = self._getnetgrent_r(result_p, buff, buff_len)
+ while res == NssReturnCode.SUCCESS:
+ if result_p[0].type == NetgroupType.GROUP_VAL:
+ nested_netgroup = result_p[0].val.group
+ if nested_netgroup not in self.known_groups:
+ self.needed_groups.append(nested_netgroup)
+ self.known_groups.append(nested_netgroup)
+
+ if result_p[0].type == NetgroupType.TRIPLE_VAL:
+ triple = result_p[0].val.triple
+ result.append((triple.host and triple.host.decode('utf-8')
+ or "",
+ triple.user and triple.user.decode('utf-8')
+ or "",
+ triple.domain and triple.domain.decode('utf-8')
+ or ""))
+
+ res, errno, result_p = self._getnetgrent_r(result_p, buff,
+ buff_len)
+
+ if res != NssReturnCode.RETURN:
+ return (res, errno, result)
+
+ res = self._endnetgrent(result_p)
+
+ return (res, errno, result)
+
+
+def get_sssd_netgroups(name):
+ """
+ Function will return netgroup triplets for given user. It will gather
+ netgroups only provided by sssd.
+ The equivalent of "getent netgroup -s sss user"
+
+ @param string name name of netgroup
+
+ @return (int, int, List[(string, string, string]) (err, errno, netgroups)
+ if err is NssReturnCode.SUCCESS netgroups will contain list of touples.
+ Each touple will consist of 3 elements either string or None
+ (host, user, domain).
+ """
+
+ retriever = NetgroupRetriever(name)
+
+ return retriever.get_netgroups()
diff --git a/src/tests/intg/sssd_nets.py b/src/tests/intg/sssd_nets.py
new file mode 100644
index 0000000..a64b5ad
--- /dev/null
+++ b/src/tests/intg/sssd_nets.py
@@ -0,0 +1,147 @@
+#
+# Module for simulation of utility "getent networks -s sss" from coreutils
+#
+# Authors:
+# Samuel Cabrero <scabrero@suse.com>
+#
+# Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from ctypes import (c_int, c_char_p, c_ulong, c_uint32, POINTER,
+ Structure, create_string_buffer)
+from sssd_nss import NssReturnCode, SssdNssError, nss_sss_ctypes_loader
+import socket
+from ipaddress import IPv4Address
+from struct import unpack
+
+IP_NETWORK_BUFLEN = 1024
+
+
+class Netent(Structure):
+ _fields_ = [("n_name", c_char_p),
+ ("n_aliases", POINTER(c_char_p)),
+ ("n_addrtype", c_int),
+ ("n_net", c_uint32)]
+
+
+def getnetbyname_r(name, result_p, buffer_p, buflen):
+ """
+ ctypes wrapper for:
+ enum nss_status _nss_sss_getnetbyname_r(const char *name,
+ struct netent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_getnetbyname_r")
+ func.restype = c_int
+ func.argtypes = [c_char_p, POINTER(Netent),
+ c_char_p, c_ulong, POINTER(c_int), POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+ h_errno = POINTER(c_int)(c_int(0))
+
+ name = name.encode('utf-8')
+ res = func(c_char_p(name), result_p, buffer_p, buflen, errno, h_errno)
+
+ return (int(res), int(errno[0]), int(h_errno[0]), result_p)
+
+
+def getnetbyaddr_r(addr, af, result_p, buffer_p, buflen):
+ """
+ ctypes wrapper for:
+ enum nss_status _nss_sss_getnetbyaddr_r(uint32_t addr, int type,
+ struct netent *result,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_getnetbyaddr_r")
+ func.restype = c_int
+ func.argtypes = [c_uint32, c_int, POINTER(Netent),
+ c_char_p, c_ulong, POINTER(c_int), POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+ h_errno = POINTER(c_int)(c_int(0))
+
+ res = func(addr, af, result_p, buffer_p, buflen, errno, h_errno)
+
+ return (int(res), int(errno[0]), int(h_errno[0]), result_p)
+
+
+def set_netent_dict(res, result_p):
+ if res != NssReturnCode.SUCCESS:
+ return dict()
+
+ netent_dict = dict()
+ netent_dict['name'] = result_p[0].n_name.decode('utf-8')
+ netent_dict['aliases'] = list()
+ netent_dict['addrtype'] = result_p[0].n_addrtype
+ netent_dict['address'] = result_p[0].n_net
+
+ i = 0
+ while result_p[0].n_aliases[i] is not None:
+ alias = result_p[0].n_aliases[i].decode('utf-8')
+ netent_dict['aliases'].append(alias)
+ i = i + 1
+
+ return netent_dict
+
+
+def call_sssd_getnetbyname(name):
+ """
+ A Python wrapper to retrieve an IP network by name. Returns:
+ (res, netent_dict)
+ if res is NssReturnCode.SUCCESS, then netent_dict contains the keys
+ corresponding to the C netent structure fields. Otherwise, the dictionary
+ is empty and errno indicates the error code
+ """
+ result = Netent()
+ result_p = POINTER(Netent)(result)
+ buff = create_string_buffer(IP_NETWORK_BUFLEN)
+
+ (res, errno, h_errno, result_p) = getnetbyname_r(name, result_p,
+ buff, IP_NETWORK_BUFLEN)
+ if errno != 0:
+ raise SssdNssError(errno, "getnetbyname_r")
+
+ netent_dict = set_netent_dict(res, result_p)
+ return (res, h_errno, netent_dict)
+
+
+def call_sssd_getnetbyaddr(addrstr, af):
+ """
+ A Python wrapper to retrieve an IP network by address. Returns:
+ (res, netent_dict)
+ if res is NssReturnCode.SUCCESS, then netent_dict contains the keys
+ corresponding to the C netent structure fields. Otherwise, the dictionary
+ is empty and errno indicates the error code
+ """
+ result = Netent()
+ result_p = POINTER(Netent)(result)
+ buff = create_string_buffer(IP_NETWORK_BUFLEN)
+
+ if isinstance(addrstr, bytes):
+ addrstr = addrstr.decode('utf-8')
+ addr = IPv4Address(addrstr)
+ binaddr = unpack('<I', addr.packed)[0]
+ binaddr = socket.ntohl(binaddr)
+
+ (res, errno, h_errno, result_p) = getnetbyaddr_r(binaddr, af,
+ result_p, buff,
+ IP_NETWORK_BUFLEN)
+ if errno != 0:
+ raise SssdNssError(errno, "getnetbyaddr_r")
+
+ netent_dict = set_netent_dict(res, result_p)
+ return (res, h_errno, netent_dict)
diff --git a/src/tests/intg/sssd_nss.py b/src/tests/intg/sssd_nss.py
new file mode 100644
index 0000000..c89750b
--- /dev/null
+++ b/src/tests/intg/sssd_nss.py
@@ -0,0 +1,78 @@
+#
+# Shared module for integration tests that need to access the sssd_nss
+# module directly
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import config
+import ctypes
+
+
+class NssReturnCode(object):
+ """ 'enum' class for name service switch return code """
+ TRYAGAIN = -2,
+ UNAVAIL = -1
+ NOTFOUND = 0
+ SUCCESS = 1
+ RETURN = 2
+
+
+class HostError(object):
+ """ 'enum' class for h_errno (glibc >= 2.19) """
+ HOST_NOT_FOUND = 1
+ TRY_AGAIN = 2
+ NO_RECOVERY = 3
+ NO_DATA = 4
+
+ @classmethod
+ def tostring(cls, val):
+ if (val == 1):
+ return "HOST_NOT_FOUND"
+ if (val == 2):
+ return "TRY_AGAIN"
+ if (val == 3):
+ return "NO_RECOVERY"
+ if (val == 4):
+ return "NO_DATA"
+ return "UNKNOWN"
+
+
+class SssdNssError(Exception):
+ """ Raised when one of the NSS operations fail """
+ def __init__(self, errno, nssop):
+ self.errno = errno
+ self.nssop = nssop
+
+ def __str__(self):
+ return "NSS operation %s failed %d" % (self.nssop, self.errno)
+
+
+class SssdNssHostError(Exception):
+ """ Raised when one of the NSS hosts operations fail """
+ def __init__(self, h_errno, nssop):
+ self.h_errno = h_errno
+ self.nssop = nssop
+
+ def __str__(self):
+ str_herr = HostError.tostring(self.h_errno)
+ return "NSS host operation %s failed: %s" % (self.nssop, str_herr)
+
+
+def nss_sss_ctypes_loader(func_name):
+ libnss_sss_path = config.NSS_MODULE_DIR + "/libnss_sss.so.2"
+ libnss_sss = ctypes.cdll.LoadLibrary(libnss_sss_path)
+ func = getattr(libnss_sss, func_name)
+ return func
diff --git a/src/tests/intg/sssd_passwd.py b/src/tests/intg/sssd_passwd.py
new file mode 100644
index 0000000..f8c91da
--- /dev/null
+++ b/src/tests/intg/sssd_passwd.py
@@ -0,0 +1,210 @@
+#
+# Module for simulation of utility "getent passwd -s sss" from coreutils
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+from ctypes import (c_int, c_char_p, c_ulong, POINTER,
+ Structure, create_string_buffer, get_errno)
+from sssd_nss import NssReturnCode, SssdNssError, nss_sss_ctypes_loader
+
+PASSWD_BUFLEN = 1024
+
+
+class Passwd(Structure):
+ _fields_ = [("pw_name", c_char_p),
+ ("pw_passwd", c_char_p),
+ ("pw_uid", c_int),
+ ("pw_gid", c_int),
+ ("pw_gecos", c_char_p),
+ ("pw_dir", c_char_p),
+ ("pw_shell", c_char_p)]
+
+
+def set_user_dict(res, result_p):
+ if res != NssReturnCode.SUCCESS:
+ return dict()
+
+ user_dict = dict()
+ user_dict['name'] = result_p[0].pw_name.decode('utf-8')
+ user_dict['passwd'] = result_p[0].pw_passwd.decode('utf-8')
+ user_dict['uid'] = result_p[0].pw_uid
+ user_dict['gid'] = result_p[0].pw_gid
+ user_dict['gecos'] = result_p[0].pw_gecos.decode('utf-8')
+ user_dict['dir'] = result_p[0].pw_dir.decode('utf-8')
+ user_dict['shell'] = result_p[0].pw_shell.decode('utf-8')
+ return user_dict
+
+
+def getpwnam_r(name, result_p, buffer_p, buflen):
+ """
+ ctypes wrapper for:
+ enum nss_status _nss_sss_getpwnam_r(const char *name,
+ struct passwd *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_getpwnam_r")
+ func.restype = c_int
+ func.argtypes = [c_char_p, POINTER(Passwd),
+ c_char_p, c_ulong, POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+
+ name = name.encode('utf-8')
+ res = func(c_char_p(name), result_p, buffer_p, buflen, errno)
+
+ return (int(res), int(errno[0]), result_p)
+
+
+def getpwuid_r(uid, result_p, buffer_p, buflen):
+ """
+ ctypes wrapper for:
+ enum nss_status _nss_sss_getpwuid_r(uid_t uid,
+ struct passwd *result,
+ char *buffer,
+ size_t buflen,
+ int *errnop)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_getpwuid_r")
+ func.restype = c_int
+ func.argtypes = [c_ulong, POINTER(Passwd),
+ c_char_p, c_ulong, POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+
+ res = func(uid, result_p, buffer_p, buflen, errno)
+
+ return (int(res), int(errno[0]), result_p)
+
+
+def setpwent():
+ """
+ ctypes wrapper for:
+ void setpwent(void)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_setpwent")
+ func.argtypes = []
+
+ res = func()
+ assert res == NssReturnCode.SUCCESS
+
+ errno = get_errno()
+ if errno != 0:
+ raise SssdNssError(errno, "setpwent")
+
+
+def endpwent():
+ """
+ ctypes wrapper for:
+ void endpwent(void)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_endpwent")
+ func.argtypes = []
+
+ res = func()
+ assert res == NssReturnCode.SUCCESS
+
+ errno = get_errno()
+ if errno != 0:
+ raise SssdNssError(errno, "endpwent")
+
+
+def getpwent_r(result_p, buffer_p, buflen):
+ """
+ ctypes wrapper for:
+ enum nss_status _nss_sss_getpwent_r(struct passwd *result,
+ char *buffer, size_t buflen,
+ int *errnop)
+ """
+ func = nss_sss_ctypes_loader("_nss_sss_getpwent_r")
+ func.restype = c_int
+ func.argtypes = [POINTER(Passwd), c_char_p, c_ulong, POINTER(c_int)]
+
+ errno = POINTER(c_int)(c_int(0))
+
+ res = func(result_p, buffer_p, buflen, errno)
+ return (int(res), int(errno[0]), result_p)
+
+
+def getpwent():
+ result = Passwd()
+ result_p = POINTER(Passwd)(result)
+ buff = create_string_buffer(PASSWD_BUFLEN)
+
+ res, errno, result_p = getpwent_r(result_p, buff, PASSWD_BUFLEN)
+ if errno != 0:
+ raise SssdNssError(errno, "getpwent_r")
+
+ user_dict = set_user_dict(res, result_p)
+ return res, user_dict
+
+
+def call_sssd_getpwnam(name):
+ """
+ A Python wrapper to retrieve a user by name. Returns:
+ (res, user_dict)
+ if res is NssReturnCode.SUCCESS, then user_dict contains the keys
+ corresponding to the C passwd structure fields. Otherwise, the dictionary
+ is empty and errno indicates the error code
+ """
+ result = Passwd()
+ result_p = POINTER(Passwd)(result)
+ buff = create_string_buffer(PASSWD_BUFLEN)
+
+ res, errno, result_p = getpwnam_r(name, result_p, buff, PASSWD_BUFLEN)
+ if errno != 0:
+ raise SssdNssError(errno, "getpwnam_r")
+
+ user_dict = set_user_dict(res, result_p)
+ return res, user_dict
+
+
+def call_sssd_getpwuid(uid):
+ """
+ A Python wrapper to retrieve a user by UID. Returns:
+ (res, user_dict)
+ if res is NssReturnCode.SUCCESS, then user_dict contains the keys
+ corresponding to the C passwd structure fields. Otherwise, the dictionary
+ is empty and errno indicates the error code
+ """
+ result = Passwd()
+ result_p = POINTER(Passwd)(result)
+ buff = create_string_buffer(PASSWD_BUFLEN)
+
+ res, errno, result_p = getpwuid_r(uid, result_p, buff, PASSWD_BUFLEN)
+ if errno != 0:
+ raise SssdNssError(errno, "getpwuid_r")
+
+ user_dict = set_user_dict(res, result_p)
+ return res, user_dict
+
+
+def call_sssd_enumeration():
+ """
+ enumerate users from sssd module only
+ """
+ setpwent()
+ user_list = []
+
+ res, user = getpwent()
+ while res == NssReturnCode.SUCCESS:
+ user_list.append(user)
+ res, user = getpwent()
+
+ endpwent()
+ return user_list
diff --git a/src/tests/intg/test_confdb.py b/src/tests/intg/test_confdb.py
new file mode 100644
index 0000000..781c369
--- /dev/null
+++ b/src/tests/intg/test_confdb.py
@@ -0,0 +1,158 @@
+#
+# Confdb integration tests
+#
+# Copyright (c) 2022 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import stat
+import signal
+import subprocess
+import time
+import pytest
+
+import config
+from util import unindent
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ with open(config.CONF_PATH, "w") as conf:
+ conf.write(contents)
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+ """Add teardown for removing sssd.conf"""
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+ create_conf_cleanup(request)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def get_sssd_pid():
+ with open(config.PIDFILE_PATH, "r") as pid_file:
+ pid = int(pid_file.read())
+ return pid
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ pid = get_sssd_pid()
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ # Ignore the error.
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def test_domains__domains(request):
+ """
+ Test that SSSD starts with explicitly configured domain.
+ """
+ conf = unindent("""\
+ [sssd]
+ services = nss, sudo
+ domains = test
+
+ [domain/test]
+ id_provider = proxy
+ proxy_lib_name = files
+ auth_provider = none
+ """)
+
+ create_conf_fixture(request, conf)
+
+ try:
+ create_sssd_process()
+ except Exception:
+ assert False
+ finally:
+ cleanup_sssd_process()
+
+
+def test_domains__enabled(request):
+ """
+ Test that SSSD starts without domains option.
+ """
+ conf = unindent("""\
+ [sssd]
+ services = nss, sudo
+
+ [domain/test]
+ enabled = true
+ id_provider = proxy
+ proxy_lib_name = files
+ auth_provider = none
+ """)
+
+ create_conf_fixture(request, conf)
+
+ try:
+ create_sssd_process()
+ except Exception:
+ assert False
+ finally:
+ cleanup_sssd_process()
+
+
+def test_domains__empty(request):
+ """
+ Test that SSSD fails without any domain enabled.
+ """
+ conf = unindent("""\
+ [sssd]
+ services = nss, sudo
+ enable_files_domain = false
+
+ [domain/test]
+ id_provider = proxy
+ proxy_lib_name = files
+ auth_provider = none
+ """)
+
+ create_conf_fixture(request, conf)
+ with pytest.raises(Exception):
+ create_sssd_process()
+
+ cleanup_sssd_process()
diff --git a/src/tests/intg/test_enumeration.py b/src/tests/intg/test_enumeration.py
new file mode 100644
index 0000000..9c5775b
--- /dev/null
+++ b/src/tests/intg/test_enumeration.py
@@ -0,0 +1,792 @@
+#
+# LDAP integration test
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import stat
+import pwd
+import grp
+import ent
+import config
+import signal
+import subprocess
+import time
+import ldap
+import pytest
+import ds_openldap
+import ldap_ent
+from util import unindent
+
+LDAP_BASE_DN = "dc=example,dc=com"
+
+# Enumeration is run periodically. It is scheduled during SSSD startup
+# and then it runs every four seconds. We do not know precisely when
+# the enumeration refresh is triggered so sleeping for four seconds is
+# not enough, we have to sleep longer time to adapt for delays (it is
+# not scheduled on precise time because of other sssd operation, it
+# takes some time to finish so each run moves it further away from
+# exact 4 seconds period, cpu scheduler, context switches, ...).
+# The random offset is set to 0 to prevent it from being applied to the
+# periodic tasks, but mainly to the initial delay.
+# I agree that just little bit longer timeout may work as well.
+# However we can be sure when using twice the period because we know
+# that enumeration was indeed run at least once.
+ENUMERATION_TIMEOUT = 4
+INTERACTIVE_TIMEOUT = ENUMERATION_TIMEOUT * 2
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(lambda: ds_inst.teardown())
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(lambda: ldap_conn.unbind_s())
+ return ldap_conn
+
+
+def create_ldap_entries(ldap_conn, ent_list=None):
+ """Add LDAP entries from ent_list"""
+ if ent_list is not None:
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+
+def cleanup_ldap_entries(ldap_conn, ent_list=None):
+ """Remove LDAP entries added by create_ldap_entries"""
+ if ent_list is None:
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"):
+ for entry in ldap_conn.search_s(f"ou={ou},"
+ f"{ldap_conn.ds_inst.base_dn}",
+ ldap.SCOPE_ONELEVEL,
+ attrlist=[]):
+ ldap_conn.delete_s(entry[0])
+ else:
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+
+
+def create_ldap_cleanup(request, ldap_conn, ent_list=None):
+ """Add teardown for removing all user/group LDAP entries"""
+ request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list))
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list=None):
+ """Add LDAP entries and add teardown for removing them"""
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, ent_list)
+
+
+SCHEMA_RFC2307 = "rfc2307"
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+
+def format_basic_conf(ldap_conn, schema):
+ """
+ Format a basic SSSD configuration
+
+ The files domain is defined but not enabled in order to avoid enumerating
+ users from the files domain that would otherwise by implicitly enabled
+ """
+ schema_conf = "ldap_schema = " + schema + "\n"
+ if schema == SCHEMA_RFC2307_BIS:
+ schema_conf += "ldap_group_object_class = groupOfNames\n"
+ return unindent("""\
+ [sssd]
+ debug_level = 0xffff
+ domains = LDAP
+ services = nss, pam
+ enable_files_domain = false
+
+ [nss]
+ debug_level = 0xffff
+ memcache_timeout = 0
+
+ [pam]
+ debug_level = 0xffff
+
+ [domain/files]
+ id_provider = proxy
+ proxy_lib_name = files
+ auth_provider = none
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ debug_level = 0xffff
+ enumerate = true
+ {schema_conf}
+ id_provider = ldap
+ auth_provider = ldap
+ ldap_enumeration_refresh_offset = 0
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+
+
+def format_interactive_conf(ldap_conn, schema):
+ """Format an SSSD configuration with all caches refreshing in 4 seconds"""
+ return \
+ format_basic_conf(ldap_conn, schema) + \
+ unindent("""
+ [nss]
+ memcache_timeout = 0
+ enum_cache_timeout = {0}
+ entry_negative_timeout = 0
+
+ [domain/LDAP]
+ ldap_enumeration_refresh_timeout = {0}
+ ldap_purge_cache_timeout = 1
+ entry_cache_timeout = {0}
+ """).format(ENUMERATION_TIMEOUT)
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+ """Add teardown for removing sssd.conf"""
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+ create_conf_cleanup(request)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_cleanup(request):
+ """Add teardown for stopping SSSD and removing its state"""
+ request.addfinalizer(cleanup_sssd_process)
+
+
+def create_sssd_fixture(request):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process()
+ create_sssd_cleanup(request)
+
+
+@pytest.fixture
+def sanity_rfc2307(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 2003)
+
+ ent_list.add_group("group1", 2001)
+ ent_list.add_group("group2", 2002)
+ ent_list.add_group("group3", 2003)
+
+ ent_list.add_group("empty_group", 2010)
+
+ ent_list.add_group("two_user_group", 2012, ["user1", "user2"])
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def populate_rfc2307bis(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 2003)
+
+ ent_list.add_group_bis("group1", 2001)
+ ent_list.add_group_bis("group2", 2002)
+ ent_list.add_group_bis("group3", 2003)
+
+ ent_list.add_group_bis("empty_group1", 2010)
+ ent_list.add_group_bis("empty_group2", 2011)
+
+ ent_list.add_group_bis("two_user_group", 2012, ["user1", "user2"])
+ ent_list.add_group_bis("group_empty_group", 2013, [], ["empty_group1"])
+ ent_list.add_group_bis("group_two_empty_groups", 2014,
+ [], ["empty_group1", "empty_group2"])
+ ent_list.add_group_bis("one_user_group1", 2015, ["user1"])
+ ent_list.add_group_bis("one_user_group2", 2016, ["user2"])
+ ent_list.add_group_bis("group_one_user_group", 2017,
+ [], ["one_user_group1"])
+ ent_list.add_group_bis("group_two_user_group", 2018,
+ [], ["two_user_group"])
+ ent_list.add_group_bis("group_two_one_user_groups", 2019,
+ [], ["one_user_group1", "one_user_group2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+
+@pytest.fixture
+def sanity_rfc2307_bis(request, ldap_conn):
+ populate_rfc2307bis(request, ldap_conn)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_sanity_rfc2307(ldap_conn, sanity_rfc2307):
+ time.sleep(INTERACTIVE_TIMEOUT)
+ passwd_pattern = ent.contains_only(
+ dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001',
+ dir='/home/user1', shell='/bin/bash'),
+ dict(name='user2', passwd='*', uid=1002, gid=2002, gecos='1002',
+ dir='/home/user2', shell='/bin/bash'),
+ dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003',
+ dir='/home/user3', shell='/bin/bash')
+ )
+ ent.assert_passwd(passwd_pattern)
+
+ group_pattern = ent.contains_only(
+ dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()),
+ dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()),
+ dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()),
+ dict(name='empty_group', passwd='*', gid=2010,
+ mem=ent.contains_only()),
+ dict(name='two_user_group', passwd='*', gid=2012,
+ mem=ent.contains_only("user1", "user2"))
+ )
+ ent.assert_group(group_pattern)
+
+ with pytest.raises(KeyError):
+ pwd.getpwnam("non_existent_user")
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1)
+ with pytest.raises(KeyError):
+ grp.getgrnam("non_existent_group")
+ with pytest.raises(KeyError):
+ grp.getgrgid(1)
+
+
+def test_sanity_rfc2307_bis(ldap_conn, sanity_rfc2307_bis):
+ time.sleep(INTERACTIVE_TIMEOUT)
+ passwd_pattern = ent.contains_only(
+ dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001',
+ dir='/home/user1', shell='/bin/bash'),
+ dict(name='user2', passwd='*', uid=1002, gid=2002, gecos='1002',
+ dir='/home/user2', shell='/bin/bash'),
+ dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003',
+ dir='/home/user3', shell='/bin/bash')
+ )
+ ent.assert_passwd(passwd_pattern)
+
+ group_pattern = ent.contains_only(
+ dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()),
+ dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()),
+ dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()),
+ dict(name='empty_group1', passwd='*', gid=2010,
+ mem=ent.contains_only()),
+ dict(name='empty_group2', passwd='*', gid=2011,
+ mem=ent.contains_only()),
+ dict(name='two_user_group', passwd='*', gid=2012,
+ mem=ent.contains_only("user1", "user2")),
+ dict(name='group_empty_group', passwd='*', gid=2013,
+ mem=ent.contains_only()),
+ dict(name='group_two_empty_groups', passwd='*', gid=2014,
+ mem=ent.contains_only()),
+ dict(name='one_user_group1', passwd='*', gid=2015,
+ mem=ent.contains_only("user1")),
+ dict(name='one_user_group2', passwd='*', gid=2016,
+ mem=ent.contains_only("user2")),
+ dict(name='group_one_user_group', passwd='*', gid=2017,
+ mem=ent.contains_only("user1")),
+ dict(name='group_two_user_group', passwd='*', gid=2018,
+ mem=ent.contains_only("user1", "user2")),
+ dict(name='group_two_one_user_groups', passwd='*', gid=2019,
+ mem=ent.contains_only("user1", "user2"))
+ )
+ ent.assert_group(group_pattern)
+
+ with pytest.raises(KeyError):
+ pwd.getpwnam("non_existent_user")
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1)
+ with pytest.raises(KeyError):
+ grp.getgrnam("non_existent_group")
+ with pytest.raises(KeyError):
+ grp.getgrgid(1)
+
+
+@pytest.fixture
+def blank_rfc2307(request, ldap_conn):
+ """Create blank RFC2307 directory fixture with interactive SSSD conf"""
+ create_ldap_cleanup(request, ldap_conn)
+ create_conf_fixture(request,
+ format_interactive_conf(ldap_conn, SCHEMA_RFC2307))
+ create_sssd_fixture(request)
+
+
+@pytest.fixture
+def blank_rfc2307_bis(request, ldap_conn):
+ """Create blank RFC2307bis directory fixture with interactive SSSD conf"""
+ create_ldap_cleanup(request, ldap_conn)
+ create_conf_fixture(request,
+ format_interactive_conf(ldap_conn, SCHEMA_RFC2307_BIS))
+ create_sssd_fixture(request)
+
+
+@pytest.fixture
+def user_and_group_rfc2307(request, ldap_conn):
+ """
+ Create an RFC2307 directory fixture with interactive SSSD conf,
+ one user and one group
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user", 1001, 2000)
+ ent_list.add_group("group", 2001)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ create_conf_fixture(request,
+ format_interactive_conf(ldap_conn, SCHEMA_RFC2307))
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def user_and_groups_rfc2307_bis(request, ldap_conn):
+ """
+ Create an RFC2307bis directory fixture with interactive SSSD conf,
+ one user and two groups
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user", 1001, 2000)
+ ent_list.add_group_bis("group1", 2001)
+ ent_list.add_group_bis("group2", 2002)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ create_conf_fixture(request,
+ format_interactive_conf(ldap_conn, SCHEMA_RFC2307_BIS))
+ create_sssd_fixture(request)
+ return None
+
+
+def test_add_remove_user(ldap_conn, blank_rfc2307):
+ """Test user addition and removal are reflected by SSSD"""
+ e = ldap_ent.user(ldap_conn.ds_inst.base_dn, "user", 2001, 2000)
+ time.sleep(INTERACTIVE_TIMEOUT)
+ # Add the user
+ ent.assert_passwd(ent.contains_only())
+ ldap_conn.add_s(*e)
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_passwd(ent.contains_only(dict(name="user", uid=2001)))
+ # Remove the user
+ ldap_conn.delete_s(e[0])
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_passwd(ent.contains_only())
+
+
+def test_add_remove_group_rfc2307(ldap_conn, blank_rfc2307):
+ """Test RFC2307 group addition and removal are reflected by SSSD"""
+ e = ldap_ent.group(ldap_conn.ds_inst.base_dn, "group", 2001)
+ time.sleep(INTERACTIVE_TIMEOUT)
+ # Add the group
+ ent.assert_group(ent.contains_only())
+ ldap_conn.add_s(*e)
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group(ent.contains_only(dict(name="group", gid=2001)))
+ # Remove the group
+ ldap_conn.delete_s(e[0])
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group(ent.contains_only())
+
+
+def test_add_remove_group_rfc2307_bis(ldap_conn, blank_rfc2307_bis):
+ """Test RFC2307bis group addition and removal are reflected by SSSD"""
+ e = ldap_ent.group_bis(ldap_conn.ds_inst.base_dn, "group", 2001)
+ time.sleep(INTERACTIVE_TIMEOUT)
+ # Add the group
+ ent.assert_group(ent.contains_only())
+ ldap_conn.add_s(*e)
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group(ent.contains_only(dict(name="group", gid=2001)))
+ # Remove the group
+ ldap_conn.delete_s(e[0])
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group(ent.contains_only())
+
+
+def test_add_remove_membership_rfc2307(ldap_conn, user_and_group_rfc2307):
+ """Test user membership addition and removal are reflected by SSSD"""
+ time.sleep(INTERACTIVE_TIMEOUT)
+ # Add user to group
+ ent.assert_group_by_name("group", dict(mem=ent.contains_only()))
+ ldap_conn.modify_s("cn=group,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_REPLACE, "memberUid", b"user")])
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group_by_name("group", dict(mem=ent.contains_only("user")))
+ # Remove user from group
+ ldap_conn.modify_s("cn=group,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_DELETE, "memberUid", None)])
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group_by_name("group", dict(mem=ent.contains_only()))
+
+
+def test_add_remove_membership_rfc2307_bis(ldap_conn,
+ user_and_groups_rfc2307_bis):
+ """
+ Test user and group membership addition and removal are reflected by SSSD,
+ with RFC2307bis schema
+ """
+ base_dn_bytes = ldap_conn.ds_inst.base_dn.encode('utf-8')
+
+ time.sleep(INTERACTIVE_TIMEOUT)
+ # Add user to group1
+ ent.assert_group_by_name("group1", dict(mem=ent.contains_only()))
+ ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_REPLACE, "member",
+ b"uid=user,ou=Users," + base_dn_bytes)])
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group_by_name("group1", dict(mem=ent.contains_only("user")))
+
+ # Add group1 to group2
+ ldap_conn.modify_s("cn=group2,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_REPLACE, "member",
+ b"cn=group1,ou=Groups," + base_dn_bytes)])
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group_by_name("group2", dict(mem=ent.contains_only("user")))
+
+ # Remove group1 from group2
+ ldap_conn.modify_s("cn=group2,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_DELETE, "member", None)])
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group_by_name("group2", dict(mem=ent.contains_only()))
+
+ # Remove user from group1
+ ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_DELETE, "member", None)])
+ time.sleep(INTERACTIVE_TIMEOUT)
+ ent.assert_group_by_name("group1", dict(mem=ent.contains_only()))
+
+
+@pytest.fixture
+def override_homedir(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_homedir_A", 1001, 2001,
+ homeDirectory="/home/A")
+ ent_list.add_user("user_with_homedir_B", 1002, 2002,
+ homeDirectory="/home/B")
+ ent_list.add_user("user_with_empty_homedir", 1003, 2003,
+ homeDirectory="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ override_homedir = /home/B
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_override_homedir(override_homedir):
+ """Test the effect of the "override_homedir" option"""
+ ent.assert_passwd(
+ ent.contains_only(
+ dict(name="user_with_homedir_A", uid=1001, dir="/home/B"),
+ dict(name="user_with_homedir_B", uid=1002, dir="/home/B"),
+ dict(name="user_with_empty_homedir", uid=1003, dir="/home/B")
+ )
+ )
+
+
+@pytest.fixture
+def fallback_homedir(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_homedir_A", 1001, 2001,
+ homeDirectory="/home/A")
+ ent_list.add_user("user_with_homedir_B", 1002, 2002,
+ homeDirectory="/home/B")
+ ent_list.add_user("user_with_empty_homedir", 1003, 2003,
+ homeDirectory="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ fallback_homedir = /home/B
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_fallback_homedir(fallback_homedir):
+ """Test the effect of the "fallback_homedir" option"""
+ ent.assert_passwd(
+ ent.contains_only(
+ dict(name="user_with_homedir_A", uid=1001, dir="/home/A"),
+ dict(name="user_with_homedir_B", uid=1002, dir="/home/B"),
+ dict(name="user_with_empty_homedir", uid=1003, dir="/home/B")
+ )
+ )
+
+
+@pytest.fixture
+def override_shell(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_shell_A", 1001, 2001,
+ loginShell="/bin/A")
+ ent_list.add_user("user_with_shell_B", 1002, 2002,
+ loginShell="/bin/B")
+ ent_list.add_user("user_with_empty_shell", 1003, 2003,
+ loginShell="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ override_shell = /bin/B
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_override_shell(override_shell):
+ """Test the effect of the "override_shell" option"""
+ ent.assert_passwd(
+ ent.contains_only(
+ dict(name="user_with_shell_A", uid=1001, shell="/bin/B"),
+ dict(name="user_with_shell_B", uid=1002, shell="/bin/B"),
+ dict(name="user_with_empty_shell", uid=1003, shell="/bin/B")
+ )
+ )
+
+
+@pytest.fixture
+def shell_fallback(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_sh_shell", 1001, 2001,
+ loginShell="/bin/sh")
+ ent_list.add_user("user_with_not_installed_shell", 1002, 2002,
+ loginShell="/bin/not_installed")
+ ent_list.add_user("user_with_empty_shell", 1003, 2003,
+ loginShell="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ shell_fallback = /bin/fallback
+ allowed_shells = /bin/not_installed
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_shell_fallback(shell_fallback):
+ """Test the effect of the "shell_fallback" option"""
+ ent.assert_passwd(
+ ent.contains_only(
+ dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"),
+ dict(name="user_with_not_installed_shell", uid=1002,
+ shell="/bin/fallback"),
+ dict(name="user_with_empty_shell", uid=1003, shell="")
+ )
+ )
+
+
+@pytest.fixture
+def default_shell(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_sh_shell", 1001, 2001,
+ loginShell="/bin/sh")
+ ent_list.add_user("user_with_not_installed_shell", 1002, 2002,
+ loginShell="/bin/not_installed")
+ ent_list.add_user("user_with_empty_shell", 1003, 2003,
+ loginShell="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ default_shell = /bin/default
+ allowed_shells = /bin/default, /bin/not_installed
+ shell_fallback = /bin/fallback
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_default_shell(default_shell):
+ """Test the effect of the "default_shell" option"""
+ ent.assert_passwd(
+ ent.contains_only(
+ dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"),
+ dict(name="user_with_not_installed_shell", uid=1002,
+ shell="/bin/fallback"),
+ dict(name="user_with_empty_shell", uid=1003,
+ shell="/bin/default")
+ )
+ )
+
+
+@pytest.fixture
+def vetoed_shells(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_sh_shell", 1001, 2001,
+ loginShell="/bin/sh")
+ ent_list.add_user("user_with_vetoed_shell", 1002, 2002,
+ loginShell="/bin/vetoed")
+ ent_list.add_user("user_with_empty_shell", 1003, 2003,
+ loginShell="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ default_shell = /bin/default
+ vetoed_shells = /bin/vetoed
+ shell_fallback = /bin/fallback
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_vetoed_shells(vetoed_shells):
+ """Test the effect of the "vetoed_shells" option"""
+ ent.assert_passwd(
+ ent.contains_only(
+ dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"),
+ dict(name="user_with_vetoed_shell", uid=1002,
+ shell="/bin/fallback"),
+ dict(name="user_with_empty_shell", uid=1003,
+ shell="/bin/default")
+ )
+ )
+
+
+@pytest.fixture
+def sanity_rfc2307_bis_mpg(request, ldap_conn):
+ populate_rfc2307bis(request, ldap_conn)
+
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_group_bis("conflict1", 1001)
+ ent_list.add_group_bis("conflict2", 1002)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [domain/LDAP]
+ auto_private_groups = True
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_ldap_auto_private_groups_enumerate(ldap_conn,
+ sanity_rfc2307_bis_mpg):
+ """
+ Test the auto_private_groups together with enumeration
+ """
+ passwd_pattern = ent.contains_only(
+ dict(name='user1', passwd='*', uid=1001, gid=1001, gecos='1001',
+ dir='/home/user1', shell='/bin/bash'),
+ dict(name='user2', passwd='*', uid=1002, gid=1002, gecos='1002',
+ dir='/home/user2', shell='/bin/bash'),
+ dict(name='user3', passwd='*', uid=1003, gid=1003, gecos='1003',
+ dir='/home/user3', shell='/bin/bash')
+ )
+ ent.assert_passwd(passwd_pattern)
+
+ group_pattern = ent.contains_only(
+ dict(name='user1', passwd='*', gid=1001, mem=ent.contains_only()),
+ dict(name='user2', passwd='*', gid=1002, mem=ent.contains_only()),
+ dict(name='user3', passwd='*', gid=1003, mem=ent.contains_only()),
+ dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()),
+ dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()),
+ dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()),
+ dict(name='empty_group1', passwd='*', gid=2010,
+ mem=ent.contains_only()),
+ dict(name='empty_group2', passwd='*', gid=2011,
+ mem=ent.contains_only()),
+ dict(name='two_user_group', passwd='*', gid=2012,
+ mem=ent.contains_only("user1", "user2")),
+ dict(name='group_empty_group', passwd='*', gid=2013,
+ mem=ent.contains_only()),
+ dict(name='group_two_empty_groups', passwd='*', gid=2014,
+ mem=ent.contains_only()),
+ dict(name='one_user_group1', passwd='*', gid=2015,
+ mem=ent.contains_only("user1")),
+ dict(name='one_user_group2', passwd='*', gid=2016,
+ mem=ent.contains_only("user2")),
+ dict(name='group_one_user_group', passwd='*', gid=2017,
+ mem=ent.contains_only("user1")),
+ dict(name='group_two_user_group', passwd='*', gid=2018,
+ mem=ent.contains_only("user1", "user2")),
+ dict(name='group_two_one_user_groups', passwd='*', gid=2019,
+ mem=ent.contains_only("user1", "user2"))
+ )
+ ent.assert_group(group_pattern)
+
+ with pytest.raises(KeyError):
+ grp.getgrnam("conflict1")
+ ent.assert_group_by_gid(1002, dict(name="user2", mem=ent.contains_only()))
diff --git a/src/tests/intg/test_files_ops.py b/src/tests/intg/test_files_ops.py
new file mode 100644
index 0000000..f778a89
--- /dev/null
+++ b/src/tests/intg/test_files_ops.py
@@ -0,0 +1,85 @@
+#
+# SSSD integration test - operations on UNIX user and group database
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import pwd
+import grp
+import pytest
+
+import ent
+
+
+USER1 = dict(name='user1', passwd='*', uid=10001, gid=20001,
+ gecos='User for tests',
+ dir='/home/user1',
+ shell='/bin/bash')
+
+GROUP1 = dict(name='group1',
+ gid=30001,
+ mem=['user1'])
+
+
+def test_useradd(passwd_ops_setup):
+ with pytest.raises(KeyError):
+ pwd.getpwnam("user1")
+ passwd_ops_setup.useradd(**USER1)
+ ent.assert_passwd_by_name("user1", USER1)
+
+
+def test_usermod(passwd_ops_setup):
+ passwd_ops_setup.useradd(**USER1)
+ ent.assert_passwd_by_name("user1", USER1)
+
+ USER1['shell'] = '/bin/zsh'
+ passwd_ops_setup.usermod(**USER1)
+ ent.assert_passwd_by_name("user1", USER1)
+
+
+def test_userdel(passwd_ops_setup):
+ passwd_ops_setup.useradd(**USER1)
+ ent.assert_passwd_by_name("user1", USER1)
+
+ passwd_ops_setup.userdel("user1")
+ with pytest.raises(KeyError):
+ pwd.getpwnam("user1")
+
+
+def test_groupadd(group_ops_setup):
+ with pytest.raises(KeyError):
+ grp.getgrnam("group1")
+ group_ops_setup.groupadd(**GROUP1)
+ ent.assert_group_by_name("group1", GROUP1)
+
+
+def test_groupmod(group_ops_setup):
+ group_ops_setup.groupadd(**GROUP1)
+ ent.assert_group_by_name("group1", GROUP1)
+
+ modgroup = dict(GROUP1)
+ modgroup['mem'] = []
+
+ group_ops_setup.groupmod(old_name=GROUP1["name"], **modgroup)
+ ent.assert_group_by_name("group1", modgroup)
+
+
+def test_groupdel(group_ops_setup):
+ group_ops_setup.groupadd(**GROUP1)
+ ent.assert_group_by_name("group1", GROUP1)
+
+ group_ops_setup.groupdel("group1")
+ with pytest.raises(KeyError):
+ grp.getgrnam("group1")
diff --git a/src/tests/intg/test_files_provider.py b/src/tests/intg/test_files_provider.py
new file mode 100644
index 0000000..fa503dd
--- /dev/null
+++ b/src/tests/intg/test_files_provider.py
@@ -0,0 +1,1341 @@
+#
+# SSSD files domain tests
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import stat
+import time
+import config
+import signal
+import subprocess
+import pwd
+import grp
+import pytest
+import tempfile
+
+import ent
+import sssd_id
+from sssd_nss import NssReturnCode
+from sssd_passwd import (call_sssd_getpwnam,
+ call_sssd_getpwuid)
+from sssd_group import call_sssd_getgrnam, call_sssd_getgrgid
+from files_ops import PasswdOps, GroupOps
+from util import unindent
+
+def have_files_provider():
+ return os.environ['FILES_PROVIDER'] == "enabled"
+
+# Sync this with files_ops.c
+FILES_REALLOC_CHUNK = 64
+
+CANARY = dict(name='canary', passwd='x', uid=100001, gid=200001,
+ gecos='Used to check if passwd is resolvable',
+ dir='/home/canary',
+ shell='/bin/bash')
+
+USER1 = dict(name='user1', passwd='x', uid=10001, gid=20001,
+ gecos='User for tests',
+ dir='/home/user1',
+ shell='/bin/bash')
+
+USER2 = dict(name='user2', passwd='x', uid=10002, gid=20001,
+ gecos='User2 for tests',
+ dir='/home/user2',
+ shell='/bin/bash')
+
+OV_USER1 = dict(name='ov_user1', passwd='x', uid=10010, gid=20010,
+ gecos='Overriden User 1',
+ dir='/home/ov/user1',
+ shell='/bin/ov_user1_shell')
+
+ALT_USER1 = dict(name='alt_user1', passwd='x', uid=60001, gid=70001,
+ gecos='User for tests from alt files',
+ dir='/home/altuser1',
+ shell='/bin/bash')
+
+ALL_USERS = [CANARY, USER1, USER2, OV_USER1, ALT_USER1]
+
+CANARY_GR = dict(name='canary',
+ gid=300001,
+ mem=[])
+
+GROUP1 = dict(name='group1',
+ gid=30001,
+ mem=['user1'])
+
+OV_GROUP1 = dict(name='ov_group1',
+ gid=30002,
+ mem=['user1'])
+
+GROUP12 = dict(name='group12',
+ gid=30012,
+ mem=['user1', 'user2'])
+
+GROUP_NOMEM = dict(name='group_nomem',
+ gid=40000,
+ mem=[])
+
+ALT_GROUP1 = dict(name='alt_group1',
+ gid=80001,
+ mem=['alt_user1'])
+
+
+def start_sssd():
+ """Start sssd and add teardown for stopping it and removing state"""
+ os.environ["SSS_FILES_PASSWD"] = os.environ["NSS_WRAPPER_PASSWD"]
+ os.environ["SSS_FILES_GROUP"] = os.environ["NSS_WRAPPER_GROUP"]
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def stop_sssd():
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+
+
+def restart_sssd():
+ stop_sssd()
+ start_sssd()
+
+
+def create_conf_fixture(request, contents):
+ """Generate sssd.conf and add teardown for removing it"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+ request.addfinalizer(lambda: os.unlink(config.CONF_PATH))
+
+
+def create_sssd_fixture(request):
+ start_sssd()
+
+ def teardown():
+ try:
+ stop_sssd()
+ except Exception:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+ request.addfinalizer(teardown)
+
+
+# Fixtures
+@pytest.fixture
+def files_domain_only(request):
+ conf = unindent("""\
+ [sssd]
+ domains = files
+ services = nss
+
+ [domain/files]
+ id_provider = files
+ fallback_to_nss = False
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def files_multiple_sources(request):
+ _, alt_passwd_path = tempfile.mkstemp(prefix='altpasswd')
+ request.addfinalizer(lambda: os.unlink(alt_passwd_path))
+ alt_pwops = PasswdOps(alt_passwd_path)
+
+ _, alt_group_path = tempfile.mkstemp(prefix='altgroup')
+ request.addfinalizer(lambda: os.unlink(alt_group_path))
+ alt_grops = GroupOps(alt_group_path)
+
+ passwd_list = ",".join([os.environ["NSS_WRAPPER_PASSWD"], alt_passwd_path])
+ group_list = ",".join([os.environ["NSS_WRAPPER_GROUP"], alt_group_path])
+
+ conf = unindent("""\
+ [sssd]
+ domains = files
+ services = nss
+
+ [nss]
+ debug_level = 10
+
+ [domain/files]
+ id_provider = files
+ fallback_to_nss = False
+ passwd_files = {passwd_list}
+ group_files = {group_list}
+ debug_level = 10
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return alt_pwops, alt_grops
+
+
+@pytest.fixture
+def files_multiple_sources_nocreate(request):
+ """
+ Sets up SSSD with multiple sources, but does not actually create
+ the files.
+ """
+ alt_passwd_path = tempfile.mktemp(prefix='altpasswd')
+ request.addfinalizer(lambda: os.unlink(alt_passwd_path))
+
+ alt_group_path = tempfile.mktemp(prefix='altgroup')
+ request.addfinalizer(lambda: os.unlink(alt_group_path))
+
+ passwd_list = ",".join([os.environ["NSS_WRAPPER_PASSWD"], alt_passwd_path])
+ group_list = ",".join([os.environ["NSS_WRAPPER_GROUP"], alt_group_path])
+
+ conf = unindent("""\
+ [sssd]
+ domains = files
+ services = nss
+
+ [nss]
+ debug_level = 10
+
+ [domain/files]
+ id_provider = files
+ fallback_to_nss = False
+ passwd_files = {passwd_list}
+ group_files = {group_list}
+ debug_level = 10
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return alt_passwd_path, alt_group_path
+
+
+@pytest.fixture
+def proxy_to_files_domain_only(request):
+ conf = unindent("""\
+ [sssd]
+ domains = proxy
+ services = nss
+
+ [domain/proxy]
+ id_provider = proxy
+ proxy_lib_name = files
+ auth_provider = none
+ resolver_provider = none
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def no_sssd_domain(request):
+ conf = unindent("""\
+ [sssd]
+ services = nss
+ enable_files_domain = true
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def no_files_domain(request):
+ conf = unindent("""\
+ [sssd]
+ services = nss
+ enable_files_domain = true
+
+ [domain/disabled.files]
+ id_provider = files
+ fallback_to_nss = False
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def disabled_files_domain(request):
+ conf = unindent("""\
+ [sssd]
+ domains = proxy
+ services = nss
+ enable_files_domain = false
+
+ [domain/proxy]
+ id_provider = proxy
+ proxy_lib_name = files
+ auth_provider = none
+ resolver_provider = none
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def domain_resolution_order(request):
+ conf = unindent("""\
+ [sssd]
+ domains = files
+ services = nss
+ domain_resolution_order = foo
+
+ [domain/files]
+ id_provider = files
+ fallback_to_nss = False
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def default_domain_suffix(request):
+ conf = unindent("""\
+ [sssd]
+ domains = files
+ services = nss
+ default_domain_suffix = foo
+
+ [domain/files]
+ id_provider = files
+ fallback_to_nss = False
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def override_homedir_and_shell(request):
+ conf = unindent("""\
+ [sssd]
+ domains = files
+ services = nss
+
+ [domain/files]
+ id_provider = files
+ fallback_to_nss = False
+ override_homedir = /test/bar
+ override_shell = /bin/bar
+
+ [nss]
+ override_homedir = /test/foo
+ override_shell = /bin/foo
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def setup_pw_with_list(pwd_ops, user_list):
+ for user in user_list:
+ pwd_ops.useradd(**user)
+ ent.assert_passwd_by_name(CANARY['name'], CANARY)
+ return pwd_ops
+
+
+@pytest.fixture
+def add_user_with_canary(passwd_ops_setup):
+ return setup_pw_with_list(passwd_ops_setup, [CANARY, USER1])
+
+
+@pytest.fixture
+def setup_pw_with_canary(passwd_ops_setup):
+ return setup_pw_with_list(passwd_ops_setup, [CANARY])
+
+
+def add_group_members(pwd_ops, group):
+ members = {x['name']: x for x in ALL_USERS}
+ for member in group['mem']:
+ if pwd_ops.userexist(member):
+ continue
+
+ pwd_ops.useradd(**members[member])
+
+
+def setup_gr_with_list(pwd_ops, grp_ops, group_list):
+ for group in group_list:
+ add_group_members(pwd_ops, group)
+ grp_ops.groupadd(**group)
+
+ ent.assert_group_by_name(CANARY_GR['name'], CANARY_GR)
+ return grp_ops
+
+
+@pytest.fixture
+def add_group_with_canary(passwd_ops_setup, group_ops_setup):
+ return setup_gr_with_list(
+ passwd_ops_setup, group_ops_setup, [GROUP1, CANARY_GR]
+ )
+
+
+@pytest.fixture
+def setup_gr_with_canary(passwd_ops_setup, group_ops_setup):
+ return setup_gr_with_list(passwd_ops_setup, group_ops_setup, [CANARY_GR])
+
+
+def poll_canary(fn, name, threshold=20):
+ """
+ If we query SSSD while it's updating its cache, it would return NOTFOUND
+ rather than a result from potentially outdated or incomplete cache. In
+ reality this doesn't hurt because the order of the modules is normally
+ "sss files" so the user lookup would fall back to files. But in tests
+ we use this loop to wait until the canary user who is always there is
+ resolved.
+ """
+ for _ in range(0, threshold):
+ res, _ = fn(name)
+ if res == NssReturnCode.SUCCESS:
+ return True
+ elif res == NssReturnCode.NOTFOUND:
+ time.sleep(0.1)
+ continue
+ else:
+ return False
+ return False
+
+
+def sssd_getpwnam_sync(name):
+ ret = poll_canary(call_sssd_getpwnam, CANARY["name"])
+ if ret is False:
+ return NssReturnCode.NOTFOUND, None
+
+ return call_sssd_getpwnam(name)
+
+
+def sssd_getpwuid_sync(uid):
+ ret = poll_canary(call_sssd_getpwnam, CANARY["name"])
+ if ret is False:
+ return NssReturnCode.NOTFOUND, None
+
+ return call_sssd_getpwuid(uid)
+
+
+def sssd_getgrnam_sync(name):
+ ret = poll_canary(call_sssd_getgrnam, CANARY_GR["name"])
+ if ret is False:
+ return NssReturnCode.NOTFOUND, None
+
+ return call_sssd_getgrnam(name)
+
+
+def sssd_getgrgid_sync(name):
+ ret = poll_canary(call_sssd_getgrnam, CANARY_GR["name"])
+ if ret is False:
+ return NssReturnCode.NOTFOUND, None
+
+ return call_sssd_getgrgid(name)
+
+
+def sssd_id_sync(name):
+ sssd_getpwnam_sync(CANARY["name"])
+ res, _, groups = sssd_id.get_user_groups(name)
+ return res, groups
+
+
+# Helper functions
+def user_generator(seqnum):
+ return dict(name='user%d' % seqnum,
+ passwd='x',
+ uid=10000 + seqnum,
+ gid=20000 + seqnum,
+ gecos='User for tests',
+ dir='/home/user%d' % seqnum,
+ shell='/bin/bash')
+
+
+def check_user(exp_user, delay=1.0):
+ if delay > 0:
+ time.sleep(delay)
+
+ res, found_user = sssd_getpwnam_sync(exp_user["name"])
+ assert res == NssReturnCode.SUCCESS
+ assert found_user == exp_user
+
+
+def group_generator(seqnum):
+ return dict(name='group%d' % seqnum,
+ gid=30000 + seqnum,
+ mem=[])
+
+
+def check_group(exp_group, delay=1.0):
+ if delay > 0:
+ time.sleep(delay)
+
+ res, found_group = sssd_getgrnam_sync(exp_group["name"])
+ assert res == NssReturnCode.SUCCESS
+ assert found_group == exp_group
+
+
+def check_group_by_gid(exp_group, delay=1.0):
+ if delay > 0:
+ time.sleep(delay)
+
+ res, found_group = sssd_getgrgid_sync(exp_group["gid"])
+ assert res == NssReturnCode.SUCCESS
+ assert found_group == exp_group
+
+
+def check_group_list(exp_groups_list):
+ for exp_group in exp_groups_list:
+ check_group(exp_group)
+
+
+def assert_user_overriden():
+ """
+ There is an issue in nss_wrapper [0] and nss_wrapper always looks into
+ the files first before using the NSS module. This lets this check fail
+ because the user is found in the file and hence will be returned
+ without overridden values.
+ In order to work this around while there's no fix for nss_wrapper, let's
+ use the fully-qualified name when looking up the USER1
+
+ https://bugzilla.samba.org/show_bug.cgi?id=12883)
+ """
+ ent.assert_passwd_by_name(USER1["name"] + "@files", OV_USER1)
+ ent.assert_passwd_by_name(OV_USER1["name"], OV_USER1)
+
+
+def assert_group_overriden():
+ """
+ There is an issue in nss_wrapper [0] and nss_wrapper always looks into
+ the files first before using the NSS module. This lets this check fail
+ because the user is found in the file and hence will be returned
+ without overridden values.
+ In order to work this around while there's no fix for nss_wrapper, let's
+ use the fully-qualified name when looking up the GROUP1
+
+ https://bugzilla.samba.org/show_bug.cgi?id=12883)
+ """
+ ent.assert_group_by_name(GROUP1["name"] + "@files", OV_GROUP1)
+ ent.assert_group_by_name(OV_GROUP1["name"], OV_GROUP1)
+
+
+# User tests
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getpwnam_after_start(add_user_with_canary, files_domain_only):
+ """
+ Test that after startup without any additional operations, a user
+ can be resolved through sssd
+ """
+ res, user = sssd_getpwnam_sync(USER1["name"])
+ assert res == NssReturnCode.SUCCESS
+ assert user == USER1
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getpwuid_after_start(add_user_with_canary, files_domain_only):
+ """
+ Test that after startup without any additional operations, a user
+ can be resolved through sssd
+ """
+ res, user = sssd_getpwuid_sync(USER1["uid"])
+ assert res == NssReturnCode.SUCCESS
+ assert user == USER1
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_user_overriden(add_user_with_canary, files_domain_only):
+ """
+ Test that user override works with files domain only
+ """
+ # Override
+ subprocess.check_call(["sss_override", "user-add", USER1["name"],
+ "-u", str(OV_USER1["uid"]),
+ "-g", str(OV_USER1["gid"]),
+ "-n", OV_USER1["name"],
+ "-c", OV_USER1["gecos"],
+ "-h", OV_USER1["dir"],
+ "-s", OV_USER1["shell"]])
+
+ restart_sssd()
+
+ assert_user_overriden()
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_group_overriden(add_group_with_canary, files_domain_only):
+ """
+ Test that user override works with files domain only
+ """
+ # Override
+ subprocess.check_call(["sss_override", "group-add", GROUP1["name"],
+ "-n", OV_GROUP1["name"],
+ "-g", str(OV_GROUP1["gid"])])
+
+ restart_sssd()
+
+ assert_group_overriden()
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getpwnam_neg(files_domain_only):
+ """
+ Test that a nonexistent user cannot be resolved by name
+ """
+ res, _ = call_sssd_getpwnam("nosuchuser")
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getpwuid_neg(files_domain_only):
+ """
+ Test that a nonexistent user cannot be resolved by UID
+ """
+ res, _ = call_sssd_getpwuid(12345)
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_root_does_not_resolve(files_domain_only):
+ """
+ SSSD currently does not resolve the root user even though it can
+ be resolved through the NSS interface
+ """
+ nss_root = pwd.getpwnam("root")
+ assert nss_root is not None
+
+ res, _ = call_sssd_getpwnam("root")
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_uid_zero_does_not_resolve(files_domain_only):
+ """
+ SSSD currently does not resolve the UID 0 even though it can
+ be resolved through the NSS interface
+ """
+ nss_root = pwd.getpwuid(0)
+ assert nss_root is not None
+
+ res, _ = call_sssd_getpwuid(0)
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_add_remove_add_file_user(setup_pw_with_canary, files_domain_only):
+ """
+ Test that removing a user is detected and the user
+ is removed from the sssd database. Similarly, an add
+ should be detected. Do this several times to test retaining
+ the inotify watch for moved and unlinked files.
+ """
+ res, _ = call_sssd_getpwnam(USER1["name"])
+ assert res == NssReturnCode.NOTFOUND
+
+ setup_pw_with_canary.useradd(**USER1)
+ check_user(USER1)
+
+ setup_pw_with_canary.userdel(USER1["name"])
+ time.sleep(1.0)
+ res, _ = sssd_getpwnam_sync(USER1["name"])
+ assert res == NssReturnCode.NOTFOUND
+
+ setup_pw_with_canary.useradd(**USER1)
+ check_user(USER1)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_mod_user_shell(add_user_with_canary, files_domain_only):
+ """
+ Test that modifying a user shell is detected and the user
+ is modified in the sssd database
+ """
+ res, user = sssd_getpwnam_sync(USER1["name"])
+ assert res == NssReturnCode.SUCCESS
+ assert user == USER1
+
+ moduser = dict(USER1)
+ moduser['shell'] = '/bin/zsh'
+ add_user_with_canary.usermod(**moduser)
+
+ check_user(moduser)
+
+
+def incomplete_user_setup(pwd_ops, del_field, exp_field):
+ adduser = dict(USER1)
+ del adduser[del_field]
+ exp_user = dict(USER1)
+ exp_user[del_field] = exp_field
+
+ pwd_ops.useradd(**adduser)
+
+ return exp_user
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_user_no_shell(setup_pw_with_canary, files_domain_only):
+ """
+ Test that resolving a user without a shell defined works and returns
+ a fallback value
+ """
+ check_user(incomplete_user_setup(setup_pw_with_canary, 'shell', ''))
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_user_no_dir(setup_pw_with_canary, files_domain_only):
+ """
+ Test that resolving a user without a homedir defined works and returns
+ a fallback value
+ """
+ check_user(incomplete_user_setup(setup_pw_with_canary, 'dir', ''))
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_user_no_gecos(setup_pw_with_canary, files_domain_only):
+ """
+ Test that resolving a user without a gecos defined works and returns
+ a fallback value
+ """
+ check_user(incomplete_user_setup(setup_pw_with_canary, 'gecos', ''))
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_user_no_passwd(setup_pw_with_canary, files_domain_only):
+ """
+ Test that resolving a user without a password defined works and returns
+ a fallback value
+ """
+ check_user(incomplete_user_setup(setup_pw_with_canary, 'passwd', 'x'))
+
+
+def bad_incomplete_user_setup(pwd_ops, del_field):
+ adduser = dict(USER1)
+ adduser[del_field] = ''
+
+ pwd_ops.useradd(**adduser)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_incomplete_user_fail(setup_pw_with_canary, files_domain_only):
+ """
+ Test resolving an incomplete user where the missing field is required
+ to be present in the user record and thus the user shouldn't resolve.
+
+ We cannot test UID and GID missing because nss_wrapper doesn't even
+ load the malformed passwd file, then.
+ """
+ bad_incomplete_user_setup(setup_pw_with_canary, 'name')
+ res, user = sssd_getpwnam_sync(USER1["name"])
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_after_start(add_group_with_canary, files_domain_only):
+ """
+ Test that after startup without any additional operations, a group
+ can be resolved through sssd by name
+ """
+ check_group(GROUP1)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrgid_after_start(add_group_with_canary, files_domain_only):
+ """
+ Test that after startup without any additional operations, a group
+ can be resolved through sssd by GID
+ """
+ check_group_by_gid(GROUP1)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_neg(files_domain_only):
+ """
+ Test that a nonexistent group cannot be resolved
+ """
+ res, user = sssd_getgrnam_sync("nosuchgroup")
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrgid_neg(files_domain_only):
+ """
+ Test that a nonexistent group cannot be resolved
+ """
+ res, user = sssd_getgrgid_sync(123456)
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_root_group_does_not_resolve(files_domain_only):
+ """
+ SSSD currently does not resolve the root group even though it can
+ be resolved through the NSS interface
+ """
+ nss_root = grp.getgrnam("root")
+ assert nss_root is not None
+
+ res, user = call_sssd_getgrnam("root")
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_gid_zero_does_not_resolve(files_domain_only):
+ """
+ SSSD currently does not resolve the group with GID 0 even though it
+ can be resolved through the NSS interface
+ """
+ nss_root = grp.getgrgid(0)
+ assert nss_root is not None
+
+ res, user = call_sssd_getgrgid(0)
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.flaky(max_runs=5)
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_add_remove_add_file_group(
+ setup_pw_with_canary, setup_gr_with_canary, files_domain_only
+):
+ """
+ Test that removing a group is detected and the group
+ is removed from the sssd database. Similarly, an add
+ should be detected. Do this several times to test retaining
+ the inotify watch for moved and unlinked files.
+ """
+ res, group = call_sssd_getgrnam(GROUP1["name"])
+ assert res == NssReturnCode.NOTFOUND
+
+ add_group_members(setup_pw_with_canary, GROUP1)
+ setup_gr_with_canary.groupadd(**GROUP1)
+ check_group(GROUP1)
+
+ setup_gr_with_canary.groupdel(GROUP1["name"])
+ time.sleep(1)
+ res, group = call_sssd_getgrnam(GROUP1["name"])
+ assert res == NssReturnCode.NOTFOUND
+
+ setup_gr_with_canary.groupadd(**GROUP1)
+ check_group(GROUP1)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_mod_group_name(add_group_with_canary, files_domain_only):
+ """
+ Test that modifying a group name is detected and the group
+ is modified in the sssd database
+ """
+ check_group(GROUP1)
+
+ modgroup = dict(GROUP1)
+ modgroup['name'] = 'group1_mod'
+ add_group_with_canary.groupmod(old_name=GROUP1["name"], **modgroup)
+
+ check_group(modgroup)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_mod_group_gid(add_group_with_canary, files_domain_only):
+ """
+ Test that modifying a group name is detected and the group
+ is modified in the sssd database
+ """
+ check_group(GROUP1)
+
+ modgroup = dict(GROUP1)
+ modgroup['gid'] = 30002
+ add_group_with_canary.groupmod(old_name=GROUP1["name"], **modgroup)
+
+ check_group(modgroup)
+
+
+@pytest.fixture
+def add_group_nomem_with_canary(passwd_ops_setup, group_ops_setup):
+ return setup_gr_with_list(
+ passwd_ops_setup, group_ops_setup, [GROUP_NOMEM, CANARY_GR]
+ )
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_no_members(add_group_nomem_with_canary, files_domain_only):
+ """
+ Test that after startup without any additional operations, a group
+ can be resolved through sssd
+ """
+ check_group(GROUP_NOMEM)
+
+
+def groupadd_list(grp_ops, groups):
+ for group in groups:
+ grp_ops.groupadd(**group)
+
+
+def useradd_list(pwd_ops, users):
+ for usr in users:
+ pwd_ops.useradd(**usr)
+
+
+def user_and_group_setup(pwd_ops, grp_ops, users, groups, reverse):
+ """
+ The reverse is added so that we test cases where a group is added first,
+ then a user for this group is created -- in that case, we need to properly
+ link the group after the user is added.
+ """
+ if reverse is False:
+ useradd_list(pwd_ops, users)
+ groupadd_list(grp_ops, groups)
+ else:
+ groupadd_list(grp_ops, groups)
+ useradd_list(pwd_ops, users)
+
+
+def members_check(added_groups):
+ # Test that users are members as per getgrnam
+ check_group_list(added_groups)
+
+ # Test that users are members as per initgroups
+ for group in added_groups:
+ for member in group['mem']:
+ res, groups = sssd_id_sync(member)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+ assert group['name'] in groups
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_members_users_first(setup_pw_with_canary,
+ setup_gr_with_canary,
+ files_domain_only):
+ """
+ A user is linked with a group
+ """
+ user_and_group_setup(setup_pw_with_canary,
+ setup_gr_with_canary,
+ [USER1],
+ [GROUP1],
+ False)
+ members_check([GROUP1])
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_members_users_multiple(setup_pw_with_canary,
+ setup_gr_with_canary,
+ files_domain_only):
+ """
+ Multiple users are linked with a group
+ """
+ user_and_group_setup(setup_pw_with_canary,
+ setup_gr_with_canary,
+ [USER1, USER2],
+ [GROUP12],
+ False)
+ members_check([GROUP12])
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_members_groups_first(setup_pw_with_canary,
+ setup_gr_with_canary,
+ files_domain_only):
+ """
+ A group is linked with a user
+ """
+ user_and_group_setup(setup_pw_with_canary,
+ setup_gr_with_canary,
+ [USER1],
+ [GROUP1],
+ True)
+ members_check([GROUP1])
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_ghost(setup_pw_with_canary,
+ setup_gr_with_canary,
+ files_domain_only):
+ """
+ Test that group if not found (and will be handled by nss_files) if there
+ are any ghost members.
+ """
+ user_and_group_setup(setup_pw_with_canary,
+ setup_gr_with_canary,
+ [],
+ [GROUP12],
+ False)
+
+ time.sleep(1)
+ res, group = call_sssd_getgrnam(GROUP12["name"])
+ assert res == NssReturnCode.NOTFOUND
+
+ for member in GROUP12['mem']:
+ res, _ = call_sssd_getpwnam(member)
+ assert res == NssReturnCode.NOTFOUND
+
+
+def ghost_and_member_test(pw_ops, grp_ops, reverse):
+ user_and_group_setup(pw_ops,
+ grp_ops,
+ [USER1],
+ [GROUP12],
+ reverse)
+
+ time.sleep(1)
+ res, group = call_sssd_getgrnam(GROUP12["name"])
+ assert res == NssReturnCode.NOTFOUND
+
+ # We checked that the group added has the same members as group12,
+ # so both user1 and user2. Now check that user1 is a member of
+ # group12 and its own primary GID but user2 doesn't exist, it's
+ # just a ghost entry
+ res, groups = sssd_id_sync('user1')
+ assert res == sssd_id.NssReturnCode.SUCCESS
+ assert len(groups) == 2
+ assert 'group12' in groups
+
+ res, _ = call_sssd_getpwnam('user2')
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_user_ghost_and_member(setup_pw_with_canary,
+ setup_gr_with_canary,
+ files_domain_only):
+ """
+ Test that a group with one member and one ghost.
+ """
+ ghost_and_member_test(setup_pw_with_canary,
+ setup_gr_with_canary,
+ False)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_user_member_and_ghost(setup_pw_with_canary,
+ setup_gr_with_canary,
+ files_domain_only):
+ """
+ Test that a group with one member and one ghost, adding the group
+ first and then linking the member
+ """
+ ghost_and_member_test(setup_pw_with_canary,
+ setup_gr_with_canary,
+ True)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_add_remove_members(setup_pw_with_canary,
+ add_group_nomem_with_canary,
+ files_domain_only):
+ """
+ Test that a user is linked with a group
+ """
+ pwd_ops = setup_pw_with_canary
+
+ check_group(GROUP_NOMEM)
+
+ for usr in [USER1, USER2]:
+ pwd_ops.useradd(**usr)
+
+ modgroup = dict(GROUP_NOMEM)
+ modgroup['mem'] = ['user1', 'user2']
+ add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup)
+ check_group(modgroup)
+
+ res, groups = sssd_id_sync('user1')
+ assert res == sssd_id.NssReturnCode.SUCCESS
+ assert len(groups) == 2
+ assert 'group_nomem' in groups
+
+ res, groups = sssd_id_sync('user2')
+ assert res == sssd_id.NssReturnCode.SUCCESS
+ assert 'group_nomem' in groups
+
+ modgroup['mem'] = ['user2']
+ add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup)
+ check_group(modgroup)
+
+ # User1 exists, but is not a member of any supplementary group anymore
+ res, _ = call_sssd_getpwnam('user1')
+ assert res == sssd_id.NssReturnCode.SUCCESS
+ res, groups = sssd_id_sync('user1')
+ assert res == sssd_id.NssReturnCode.NOTFOUND
+
+ # user2 still is
+ res, groups = sssd_id_sync('user2')
+ assert res == sssd_id.NssReturnCode.SUCCESS
+ assert len(groups) == 2
+ assert 'group_nomem' in groups
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_getgrnam_add_remove_ghosts(setup_pw_with_canary,
+ add_group_nomem_with_canary,
+ files_domain_only):
+ """
+ Test that a user is linked with a group
+ """
+ check_group(GROUP_NOMEM)
+
+ modgroup = dict(GROUP_NOMEM)
+ modgroup['mem'] = ['user1', 'user2']
+ add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup)
+ time.sleep(1)
+ res, group = call_sssd_getgrnam(modgroup['name'])
+ assert res == sssd_id.NssReturnCode.NOTFOUND
+
+ modgroup['mem'] = ['user2']
+ add_group_nomem_with_canary.groupmod(old_name=modgroup['name'], **modgroup)
+ time.sleep(1)
+ res, group = call_sssd_getgrnam(modgroup['name'])
+ assert res == sssd_id.NssReturnCode.NOTFOUND
+
+ res, _ = call_sssd_getpwnam('user1')
+ assert res == NssReturnCode.NOTFOUND
+ res, _ = call_sssd_getpwnam('user2')
+ assert res == NssReturnCode.NOTFOUND
+
+
+def realloc_users(pwd_ops, num):
+ # Intentionally not including the last one because
+ # canary is added first
+ for i in range(1, num):
+ user = user_generator(i)
+ pwd_ops.useradd(**user)
+
+ user = user_generator(num - 1)
+ check_user(user)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_realloc_users_exact(setup_pw_with_canary, files_domain_only):
+ """
+ Test that returning exactly FILES_REALLOC_CHUNK users (see files_ops.c)
+ works fine to test reallocation logic. Test exact number of users to
+ check for off-by-one errors.
+ """
+ realloc_users(setup_pw_with_canary, FILES_REALLOC_CHUNK)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_realloc_users(setup_pw_with_canary, files_domain_only):
+ """
+ Test that returning exactly FILES_REALLOC_CHUNK users (see files_ops.c)
+ works fine to test reallocation logic.
+ """
+ realloc_users(setup_pw_with_canary, FILES_REALLOC_CHUNK * 3)
+
+
+def realloc_groups(grp_ops, num):
+ for i in range(1, num):
+ group = group_generator(i)
+ grp_ops.groupadd(**group)
+
+ group = group_generator(num - 1)
+ check_group(group)
+
+
+@pytest.mark.flaky(max_runs=5)
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_realloc_groups_exact(setup_gr_with_canary, files_domain_only):
+ """
+ Test that returning exactly FILES_REALLOC_CHUNK groups (see files_ops.c)
+ works fine to test reallocation logic. Test exact number of groups to
+ check for off-by-one errors.
+ """
+ realloc_groups(setup_gr_with_canary, FILES_REALLOC_CHUNK * 3)
+
+
+@pytest.mark.flaky(max_runs=5)
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_realloc_groups(setup_gr_with_canary, files_domain_only):
+ """
+ Test that returning exactly FILES_REALLOC_CHUNK groups (see files_ops.c)
+ works fine to test reallocation logic. Test exact number of groups to
+ check for off-by-one errors.
+ """
+ realloc_groups(setup_gr_with_canary, FILES_REALLOC_CHUNK * 3)
+
+
+# Files domain autoconfiguration tests
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_no_sssd_domain(add_user_with_canary, no_sssd_domain):
+ """
+ Test that if no sssd domain is configured, sssd will add the implicit one
+ """
+ res, user = sssd_getpwnam_sync(USER1["name"])
+ assert res == NssReturnCode.SUCCESS
+ assert user == USER1
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_proxy_to_files_domain_only(add_user_with_canary,
+ proxy_to_files_domain_only):
+ """
+ Test that implicit_files domain is not started together with proxy to files
+ """
+ res, _ = call_sssd_getpwnam("{0}@implicit_files".format(USER1["name"]))
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_no_files_domain(add_user_with_canary, no_files_domain):
+ """
+ Test that if no files domain is configured, sssd will add the implicit one
+ """
+ res, user = sssd_getpwnam_sync(USER1["name"])
+ assert res == NssReturnCode.SUCCESS
+ assert user == USER1
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_disable_files_domain(add_user_with_canary, disabled_files_domain):
+ """
+ Test disabled files domain
+ """
+ # The local user will not be resolvable through nss_sss now
+ res, user = sssd_getpwnam_sync(USER1["name"])
+ assert res != NssReturnCode.SUCCESS
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_multiple_passwd_group_files(add_user_with_canary,
+ add_group_with_canary,
+ files_multiple_sources):
+ """
+ Test that users and groups can be mirrored from multiple files
+ """
+ alt_pwops, alt_grops = files_multiple_sources
+ alt_pwops.useradd(**ALT_USER1)
+ alt_grops.groupadd(**ALT_GROUP1)
+
+ check_user(USER1)
+ check_user(ALT_USER1)
+
+ check_group(GROUP1)
+ check_group(ALT_GROUP1)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_multiple_files_created_after_startup(add_user_with_canary,
+ add_group_with_canary,
+ files_multiple_sources_nocreate):
+ """
+ Test that users and groups can be mirrored from multiple files,
+ but those files are not created when SSSD starts, only afterwards.
+ """
+ alt_passwd_path, alt_group_path = files_multiple_sources_nocreate
+
+ check_user(USER1)
+ check_group(GROUP1)
+
+ # touch the files
+ for fpath in (alt_passwd_path, alt_group_path):
+ with open(fpath, "w"):
+ os.utime(fpath)
+
+ alt_pwops = PasswdOps(alt_passwd_path)
+ alt_grops = GroupOps(alt_group_path)
+ alt_pwops.useradd(**ALT_USER1)
+ alt_grops.groupadd(**ALT_GROUP1)
+
+ check_user(ALT_USER1)
+ check_group(ALT_GROUP1)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_files_with_domain_resolution_order(add_user_with_canary,
+ domain_resolution_order):
+ """
+ Test that when using domain_resolution_order the user won't be using
+ its fully-qualified name.
+ """
+ check_user(USER1)
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_files_with_default_domain_suffix(add_user_with_canary,
+ default_domain_suffix):
+ """
+ Test that when using domain_resolution_order the user won't be using
+ its fully-qualified name.
+ """
+ ret = poll_canary(call_sssd_getpwuid, CANARY["uid"])
+ if ret is False:
+ return NssReturnCode.NOTFOUND, None
+
+ res, found_user = call_sssd_getpwuid(USER1["uid"])
+ assert res == NssReturnCode.SUCCESS
+ assert found_user == USER1
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_files_with_override_homedir(add_user_with_canary,
+ override_homedir_and_shell):
+ res, user = sssd_getpwnam_sync(USER1["name"])
+ assert res == NssReturnCode.SUCCESS
+ assert user["dir"] == USER1["dir"]
+
+
+@pytest.mark.skipif(not have_files_provider(),
+ reason="'files provider' disabled, skipping")
+def test_files_with_override_shell(add_user_with_canary,
+ override_homedir_and_shell):
+ res, user = sssd_getpwnam_sync(USER1["name"])
+ assert res == NssReturnCode.SUCCESS
+ assert user["shell"] == USER1["shell"]
diff --git a/src/tests/intg/test_infopipe.py b/src/tests/intg/test_infopipe.py
new file mode 100644
index 0000000..f1f6c83
--- /dev/null
+++ b/src/tests/intg/test_infopipe.py
@@ -0,0 +1,855 @@
+#
+# Infopipe integration test
+#
+# Copyright (c) 2017 Red Hat, Inc.
+# Author: Lukas Slebodnik <lslebodn@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+from __future__ import print_function
+
+import os
+import stat
+import pwd
+import signal
+import subprocess
+import errno
+import time
+import ldap
+import ldap.modlist
+import pytest
+import dbus
+import shutil
+
+import config
+import ds_openldap
+import ldap_ent
+from util import unindent, get_call_output
+
+LDAP_BASE_DN = "dc=example,dc=com"
+INTERACTIVE_TIMEOUT = 4
+
+
+class DbusDaemon(object):
+ def __init__(self):
+ self.pid = 0
+
+ def start(self):
+ """Start the SSSD process"""
+ assert self.pid == 0
+
+ dbus_config_path = config.SYSCONFDIR + "/dbus-1/cwrap-dbus-system.conf"
+ dbus_commands = [
+ ["dbus-daemon", "--config-file", dbus_config_path,
+ "--nosyslog", "--fork"],
+ ["dbus-daemon", "--config-file", dbus_config_path, "--fork"],
+ ]
+ dbus_started = False
+ for dbus_command in dbus_commands:
+ try:
+ if subprocess.call(dbus_command) == 0:
+ dbus_started = True
+ break
+ else:
+ print("start failed for %s" % " ".join(dbus_command))
+ except OSError as ex:
+ if ex.errno == errno.ENOENT:
+ print("%s does not exist" % (dbus_command[0]))
+ pass
+
+ if not dbus_started:
+ raise Exception("dbus-daemon start failed")
+ dbus_pid_path = config.RUNSTATEDIR + "/dbus/messagebus.pid"
+ # wait 10 seconds for pidfile
+ wait_time = 10
+ for _ in range(wait_time * 10):
+ if os.path.isfile(dbus_pid_path):
+ break
+ time.sleep(.1)
+
+ assert os.path.isfile(dbus_pid_path)
+ with open(dbus_pid_path, "r") as pid_file:
+ self.pid = int(pid_file.read())
+
+ def stop(self):
+ """Stop the SSSD process and remove its state"""
+
+ # stop process only if running
+ if self.pid != 0:
+ try:
+ os.kill(self.pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(self.pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(.1)
+ except OSError:
+ pass
+
+ # clean pid so we can start service one more time
+ self.pid = 0
+
+ # dbus-daemon 1.2.24 does not clean pid file after itself
+ try:
+ os.unlink(config.RUNSTATEDIR + "/dbus/messagebus.pid")
+ except OSError as ex:
+ if ex.errno != errno.ENOENT:
+ raise
+
+
+@pytest.fixture(scope="module")
+def dbus_system_bus(request):
+ dbus_daemon = DbusDaemon()
+ dbus_daemon.start()
+
+ def cleanup_dbus_process():
+ dbus_daemon.stop()
+ request.addfinalizer(cleanup_dbus_process)
+
+ return dbus.SystemBus()
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(ds_inst.teardown)
+
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(ldap_conn.unbind_s)
+ return ldap_conn
+
+
+def create_ldap_entries(ldap_conn, ent_list=None):
+ """Add LDAP entries from ent_list"""
+ if ent_list is not None:
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+
+def cleanup_ldap_entries(ldap_conn, ent_list=None):
+ """Remove LDAP entries added by create_ldap_entries"""
+ if ent_list is None:
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"):
+ for entry in ldap_conn.search_s(f"ou={ou},"
+ f"{ldap_conn.ds_inst.base_dn}",
+ ldap.SCOPE_ONELEVEL,
+ attrlist=[]):
+ ldap_conn.delete_s(entry[0])
+ else:
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+
+
+def create_ldap_cleanup(request, ldap_conn, ent_list=None):
+ """Add teardown for removing all user/group LDAP entries"""
+ request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list))
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list=None):
+ """Add LDAP entries and add teardown for removing them"""
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, ent_list)
+
+
+SCHEMA_RFC2307 = "rfc2307"
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+
+def format_basic_conf(ldap_conn, schema, config):
+ """Format a basic SSSD configuration"""
+ schema_conf = "ldap_schema = " + schema + "\n"
+ if schema == SCHEMA_RFC2307_BIS:
+ schema_conf += "ldap_group_object_class = groupOfNames\n"
+
+ return unindent("""\
+ [sssd]
+ debug_level = 0xffff
+ domains = LDAP, app
+ services = nss, ifp
+ enable_files_domain = false
+
+ [nss]
+ memcache_timeout = 0
+
+ [ifp]
+ debug_level = 0xffff
+ user_attributes = +extraName
+ ca_db = {config.PAM_CERT_DB_PATH}
+
+ [domain/LDAP]
+ {schema_conf}
+ id_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ ldap_user_extra_attrs = extraName:uid
+ ldap_user_certificate = userCert
+
+ [application/app]
+ inherit_from = LDAP
+ """).format(**locals())
+
+
+def format_certificate_conf(ldap_conn, schema, config):
+ """Format an SSSD configuration with all caches refreshing in 4 seconds"""
+ return \
+ format_basic_conf(ldap_conn, schema, config) + \
+ unindent("""
+ [certmap/LDAP/user1]
+ matchrule = <SUBJECT>.*CN = SSSD test cert 0001.*
+ """).format(**locals())
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ with open(config.CONF_PATH, "w") as conf:
+ conf.write(contents)
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+ """Add teardown for removing sssd.conf"""
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+ create_conf_cleanup(request)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ with open(config.PIDFILE_PATH, "r") as pid_file:
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_cleanup(request):
+ """Add teardown for stopping SSSD and removing its state"""
+ request.addfinalizer(cleanup_sssd_process)
+
+
+def create_sssd_fixture(request):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process()
+ create_sssd_cleanup(request)
+
+
+def backup_ca_db():
+ """Create backup file for ca db"""
+ src = os.path.dirname(config.PAM_CERT_DB_PATH) + "/SSSD_test_CA.pem"
+ dst = os.path.dirname(config.PAM_CERT_DB_PATH) + "/SSSD_test_CA.pem.bp"
+ shutil.copyfile(src, dst)
+
+
+def restore_ca_db():
+ """Restore backup file for ca db"""
+ src = os.path.dirname(config.PAM_CERT_DB_PATH) + "/SSSD_test_CA.pem.bp"
+ dst = os.path.dirname(config.PAM_CERT_DB_PATH) + "/SSSD_test_CA.pem"
+ shutil.copyfile(src, dst)
+ os.remove(src)
+
+
+def create_restore_ca_db(request):
+ """Add teardown for restoring ca_db"""
+ request.addfinalizer(restore_ca_db)
+
+
+def create_ca_db_fixture(request):
+ """
+ Create backup for ca_db and add teardown for restoring it
+ """
+ backup_ca_db()
+ create_restore_ca_db(request)
+
+
+@pytest.fixture
+def sanity_rfc2307(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 2003)
+
+ ent_list.add_group("group1", 2001)
+ ent_list.add_group("group2", 2002)
+ ent_list.add_group("group3", 2003)
+
+ ent_list.add_group("empty_group", 2010)
+
+ ent_list.add_group("single_user_group", 2011, ["user1"])
+ ent_list.add_group("two_user_group", 2012, ["user1", "user2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307, config)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ create_ca_db_fixture(request)
+ return None
+
+
+@pytest.fixture
+def simple_rfc2307(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user('usr\\\\001', 181818, 181818)
+ ent_list.add_group("group1", 181818)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307, config)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ create_ca_db_fixture(request)
+ return None
+
+
+@pytest.fixture
+def auto_private_groups_rfc2307(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+
+ ent_list.add_group("group1", 2001)
+ ent_list.add_group("single_user_group", 2011, ["user1"])
+ ent_list.add_group("two_user_group", 2012, ["user1"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307, config) + \
+ unindent("""
+ [domain/LDAP]
+ auto_private_groups = True
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ create_ca_db_fixture(request)
+ return None
+
+
+@pytest.fixture
+def add_user_with_cert(request, ldap_conn):
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ der_path = os.path.dirname(config.PAM_CERT_DB_PATH)
+ der_path += "/SSSD_test_cert_x509_0001.der"
+ with open(der_path, 'rb') as f:
+ val = f.read()
+ dn = "uid=user1,ou=Users," + LDAP_BASE_DN
+ '''
+ Using 'userCert' instead of 'userCertificate' to hold the user certificate
+ because the default OpenLDAP has syntax and matching rules which are not
+ used in other LDAP servers.
+ '''
+ ldap_conn.modify_s(dn, [(ldap.MOD_ADD, 'userCert', val)])
+
+ conf = format_certificate_conf(ldap_conn, SCHEMA_RFC2307_BIS, config)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ create_ca_db_fixture(request)
+
+ return None
+
+
+def test_ping_raw(dbus_system_bus, ldap_conn, simple_rfc2307):
+ # test with disabled introspection
+ sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe',
+ introspect=False)
+ sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+ # test missing parameter
+ with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+ sssd_interface.Ping()
+ assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+ ex = exc_info.value
+ assert ex.get_dbus_name() == 'sbus.Error.Errno'
+ assert 'Unexpected argument type provided' in ex.get_dbus_message()
+
+ # test wrong parameter type
+ with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+ sssd_interface.Ping(1)
+ assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+ ex = exc_info.value
+ assert ex.get_dbus_name() == 'sbus.Error.Errno'
+ assert 'Unexpected argument type provided' in ex.get_dbus_message()
+
+ # test wrong parameter value
+ with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+ sssd_interface.Ping('test')
+ assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+ ex = exc_info.value
+ assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.InvalidArgs'
+ assert ex.get_dbus_message() == 'Invalid argument'
+
+ # positive test
+ ret = sssd_interface.Ping('ping')
+ assert ret == "PONG"
+
+ # test case insensitive input
+ ret = sssd_interface.Ping('PinG')
+ assert ret == "PONG"
+
+ ret = sssd_interface.Ping('PING')
+ assert ret == "PONG"
+
+
+def test_ping_introspection(dbus_system_bus, ldap_conn, simple_rfc2307):
+ sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe')
+ sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+ # test missing parameter
+ with pytest.raises(TypeError) as exc_info:
+ sssd_interface.Ping()
+ assert exc_info.errisinstance(TypeError)
+
+ ex = exc_info.value
+ assert str(ex) == 'More items found in D-Bus signature than in Python ' \
+ 'arguments'
+
+ # test wrong parameter type
+ with pytest.raises(TypeError) as exc_info:
+ sssd_interface.Ping(1)
+ assert exc_info.errisinstance(TypeError)
+
+ ex = exc_info.value
+ assert str(ex) == 'Expected a string or unicode object'
+
+ # test wrong parameter value
+ with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+ sssd_interface.Ping('test')
+ assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+ ex = exc_info.value
+ assert ex.get_dbus_name() == 'org.freedesktop.DBus.Error.InvalidArgs'
+ assert ex.get_dbus_message() == 'Invalid argument'
+
+ # positive test
+ ret = sssd_interface.Ping('ping')
+ assert ret == "PONG"
+
+ # test case insensitive input
+ ret = sssd_interface.Ping('PinG')
+ assert ret == "PONG"
+
+ ret = sssd_interface.Ping('PING')
+ assert ret == "PONG"
+
+
+def test_special_characters(dbus_system_bus, ldap_conn, simple_rfc2307):
+ sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe')
+ sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+ attributes = ['name', 'uidNumber', 'gidNumber', 'gecos', 'homeDirectory',
+ 'loginShell']
+ expected = dict(name='usr\\001', uidNumber='181818', gidNumber='181818',
+ gecos='181818', homeDirectory='/home/usr\\\\001',
+ loginShell='/bin/bash')
+
+ user_attrs = sssd_interface.GetUserAttr('usr\\001', attributes)
+ assert user_attrs.signature == 'sv'
+ assert user_attrs.variant_level == 0
+
+ assert len(attributes) == len(user_attrs)
+ assert sorted(attributes) == sorted(user_attrs.keys())
+
+ # check values of attributes
+ for attr in user_attrs:
+ assert user_attrs[attr].signature == 's'
+ assert user_attrs[attr].variant_level == 1
+ assert user_attrs[attr][0] == expected[attr]
+
+
+def test_get_user_attr(dbus_system_bus, ldap_conn, sanity_rfc2307):
+ sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe')
+ sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+ # negative test
+ with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+ sssd_interface.GetUserAttr('non_existent_user', ['name'])
+ assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+ ex = exc_info.value
+ assert ex.get_dbus_name() == 'sbus.Error.NotFound'
+ assert ex.get_dbus_message() == 'No such file or directory'
+
+ # test 0 attributes
+ user_attrs = sssd_interface.GetUserAttr('user1', [])
+
+ assert user_attrs.signature == 'sv'
+ assert user_attrs.variant_level == 0
+
+ # expect empty sequence; len(user_attrs) == 0
+ assert not user_attrs
+
+ # positive test
+ attributes = ['name', 'uidNumber', 'gidNumber', 'gecos', 'homeDirectory',
+ 'loginShell']
+ expected = dict(name='user1', uidNumber='1001', gidNumber='2001',
+ gecos='1001', homeDirectory='/home/user1',
+ loginShell='/bin/bash')
+ user_attrs = sssd_interface.GetUserAttr('user1', attributes)
+
+ assert user_attrs.signature == 'sv'
+ assert user_attrs.variant_level == 0
+
+ assert len(attributes) == len(user_attrs)
+ assert sorted(attributes) == sorted(user_attrs.keys())
+
+ # check values of attributes
+ for attr in user_attrs:
+ assert user_attrs[attr].signature == 's'
+ assert user_attrs[attr].variant_level == 1
+ assert user_attrs[attr][0] == expected[attr]
+
+
+def test_get_user_groups(dbus_system_bus, ldap_conn, sanity_rfc2307):
+ sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe')
+ sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+ # negative test
+ with pytest.raises(dbus.exceptions.DBusException) as exc_info:
+ sssd_interface.GetUserGroups('non_existent_user')
+ assert exc_info.errisinstance(dbus.exceptions.DBusException)
+
+ ex = exc_info.value
+ assert ex.get_dbus_name() == 'sbus.Error.NotFound'
+ assert ex.get_dbus_message() == 'No such file or directory'
+
+ # the same test via nss responder
+ with pytest.raises(KeyError):
+ pwd.getpwnam("non_existent_user")
+
+ # 0 groups
+ res = sssd_interface.GetUserGroups('user3')
+ assert res.signature == 's'
+ assert res.variant_level == 0
+
+ # expect empty sequence; len(res) == 0
+ assert not res
+
+ # single group
+ res = sssd_interface.GetUserGroups('user2')
+ assert res.signature == 's'
+ assert res.variant_level == 0
+
+ assert len(res) == 1
+ assert res[0] == 'two_user_group'
+
+ # more groups
+ res = sssd_interface.GetUserGroups('user1')
+ assert res.signature == 's'
+ assert res.variant_level == 0
+
+ assert len(res) == 2
+ assert sorted(res) == ['single_user_group', 'two_user_group']
+
+
+'''
+Given auto_private_groups is enabled
+When GetUserGroups is called
+Then the origPrimaryGroupGidNumber is returned as part of the group memberships
+'''
+
+
+def test_get_user_groups_given_auto_private_groups_enabled(
+ dbus_system_bus,
+ ldap_conn, auto_private_groups_rfc2307):
+ sssd_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe')
+ sssd_interface = dbus.Interface(sssd_obj, 'org.freedesktop.sssd.infopipe')
+
+ res = sssd_interface.GetUserGroups('user1')
+
+ assert sorted(res) == ['group1', 'single_user_group', 'two_user_group']
+
+
+def get_user_property(dbus_system_bus, username, prop_name):
+ users_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe/Users')
+
+ users_iface = dbus.Interface(users_obj,
+ "org.freedesktop.sssd.infopipe.Users")
+
+ user_path = users_iface.FindByName(username)
+ user_object = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ user_path)
+
+ prop_iface = dbus.Interface(user_object, 'org.freedesktop.DBus.Properties')
+ return prop_iface.Get('org.freedesktop.sssd.infopipe.Users.User',
+ prop_name)
+
+
+def get_user_by_attr(dbus_system_bus, attribute, filter):
+ users_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe/Users')
+
+ users_iface = dbus.Interface(users_obj,
+ "org.freedesktop.sssd.infopipe.Users")
+
+ return users_iface.ListByAttr(attribute, filter, 0)
+
+
+def get_user_by_name(dbus_system_bus, filter):
+ users_obj = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe/Users')
+
+ users_iface = dbus.Interface(users_obj,
+ "org.freedesktop.sssd.infopipe.Users")
+
+ return users_iface.ListByName(filter, 0)
+
+
+def test_get_extra_attributes_empty(dbus_system_bus,
+ ldap_conn,
+ sanity_rfc2307):
+ """
+ Make sure the extraAttributes property can be retrieved
+ """
+ extra_attrs = get_user_property(dbus_system_bus,
+ 'user1',
+ 'extraAttributes')
+ assert extra_attrs['extraName'][0] == 'user1'
+
+
+def test_sssctl_domain_list_app_domain(dbus_system_bus,
+ ldap_conn,
+ sanity_rfc2307):
+ output = get_call_output(["sssctl", "domain-list"], subprocess.STDOUT)
+
+ assert "Error" not in output
+ assert output.find("LDAP") != -1
+ assert output.find("app") != -1
+
+
+def test_update_member_list_and_get_all(dbus_system_bus,
+ ldap_conn,
+ sanity_rfc2307):
+ '''
+ Test that UpdateMemberList() and GetAll() return the correct users that are
+ members of a group
+ '''
+ sssd_obj = dbus_system_bus.get_object(
+ 'org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe/Groups')
+ groups_iface = dbus.Interface(sssd_obj,
+ 'org.freedesktop.sssd.infopipe.Groups')
+ group_id = 2011
+ expected_user_result = "/org/freedesktop/sssd/infopipe/Users/LDAP/1001"
+
+ group_path = groups_iface.FindByName('single_user_group')
+
+ group_object = dbus_system_bus.get_object('org.freedesktop.sssd.infopipe',
+ group_path)
+ group_iface = dbus.Interface(group_object,
+ 'org.freedesktop.sssd.infopipe.Groups.Group')
+
+ # update local cache for group
+ try:
+ group_iface.UpdateMemberList(group_id)
+ except dbus.exceptions.DBusException as ex:
+ assert False, "Unexpected DBusException raised: " + ex
+
+ # check members of group
+ prop_iface = dbus.Interface(group_object,
+ 'org.freedesktop.DBus.Properties')
+ res = prop_iface.GetAll('org.freedesktop.sssd.infopipe.Groups.Group')
+ assert str(res.get("users")[0]) == expected_user_result
+
+ # delete group (there's no other way of removing a user from a group) and
+ # wait change to propagate
+ ldap_conn.delete("cn=single_user_group,ou=Groups,dc=example,dc=com")
+ time.sleep(INTERACTIVE_TIMEOUT)
+
+ # add group back but this time without any member
+ ldap_group = ldap_ent.group("dc=example,dc=com", "single_user_group", 2011)
+ ldap_conn.add_s(ldap_group[0], ldap_group[1])
+
+ # invalidate cache
+ subprocess.call(["sss_cache", "-E"])
+
+ # check that group has no members
+ group_iface.UpdateMemberList(group_id)
+ prop_interface = dbus.Interface(group_object,
+ 'org.freedesktop.DBus.Properties')
+ res = prop_interface.GetAll('org.freedesktop.sssd.infopipe.Groups.Group')
+ assert not res.get("users")
+
+
+def test_find_by_valid_certificate(dbus_system_bus,
+ ldap_conn,
+ add_user_with_cert):
+ """test_find_by_valid_certificate
+
+ :id: 3f212e6e-00ce-44ac-95d4-59925cb5a14a
+ :title: SSSD-TC: Infopipe: Find by valid certificate
+ :casecomponent: sssd
+ :subsystemteam: sst_idm_sssd
+ """
+ users_obj = dbus_system_bus.get_object(
+ 'org.freedesktop.sssd.infopipe',
+ '/org/freedesktop/sssd/infopipe/Users')
+ users_iface = dbus.Interface(users_obj,
+ 'org.freedesktop.sssd.infopipe.Users')
+ cert_path = os.path.dirname(config.PAM_CERT_DB_PATH)
+
+ # Valid certificate with user
+ cert_file = cert_path + "/SSSD_test_cert_x509_0001.pem"
+ with open(cert_file, "r") as f:
+ cert = f.read()
+ res = users_iface.FindByValidCertificate(cert)
+ assert res == "/org/freedesktop/sssd/infopipe/Users/app/user1_40app"
+
+ # Valid certificate without user
+ cert_file = cert_path + "/SSSD_test_cert_x509_0002.pem"
+ with open(cert_file, "r") as f:
+ cert = f.read()
+ try:
+ res = users_iface.FindByValidCertificate(cert)
+ assert False, "Previous call should raise an exception"
+ except dbus.exceptions.DBusException as ex:
+ assert str(ex) == "sbus.Error.NotFound: No such file or directory"
+
+ # Valid certificate from another CA
+ cert_file = os.environ['ABS_SRCDIR'] + \
+ "/../test_ECC_CA/SSSD_test_ECC_cert_key_0001.pem"
+ with open(cert_file, "r") as f:
+ cert = f.read()
+ try:
+ res = users_iface.FindByValidCertificate(cert)
+ assert False, "Previous call should raise an exception"
+ except dbus.exceptions.DBusException as ex:
+ assert str(ex) == \
+ "org.freedesktop.DBus.Error.IOError: Input/output error"
+
+ # Invalid certificate
+ cert = "Invalid cert"
+ try:
+ res = users_iface.FindByValidCertificate(cert)
+ assert False, "Previous call should raise an exception"
+ except dbus.exceptions.DBusException as ex:
+ error = "org.freedesktop.DBus.Error.IOError: Input/output error"
+ assert str(ex) == error
+
+ # Remove certificate db
+ cert_db = cert_path + "/SSSD_test_CA.pem"
+ os.remove(cert_db)
+ cert_file = cert_path + "/SSSD_test_cert_x509_0002.pem"
+ with open(cert_file, "r") as f:
+ cert = f.read()
+ try:
+ res = users_iface.FindByValidCertificate(cert)
+ assert False, "Previous call should raise an exception"
+ except dbus.exceptions.DBusException as ex:
+ assert str(ex) == \
+ "sbus.Error.NoCA: Certificate authority file not found"
+
+
+def test_list_by_attr(dbus_system_bus, ldap_conn, sanity_rfc2307):
+ users = get_user_by_attr(dbus_system_bus, "extraName", "user2")
+ assert len(users) == 2
+ assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1002' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/app/user2_40app' in users
+
+ users = get_user_by_attr(dbus_system_bus, "extraName", "user*")
+ assert len(users) == 6
+ assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1001' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1002' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1003' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/app/user1_40app' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/app/user2_40app' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/app/user3_40app' in users
+
+ users = get_user_by_attr(dbus_system_bus, "extraName", "nouser*")
+ assert len(users) == 0
+
+ users = get_user_by_attr(dbus_system_bus, "noattr", "*")
+ assert len(users) == 0
+
+
+def test_list_by_name(dbus_system_bus, ldap_conn, sanity_rfc2307):
+ users = get_user_by_name(dbus_system_bus, "user2")
+ assert len(users) == 2
+ assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1002' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/app/user2_40app' in users
+
+ users = get_user_by_name(dbus_system_bus, "user*")
+ assert len(users) == 6
+ assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1001' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1002' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/LDAP/1003' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/app/user1_40app' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/app/user2_40app' in users
+ assert '/org/freedesktop/sssd/infopipe/Users/app/user3_40app' in users
+
+ users = get_user_by_name(dbus_system_bus, "nouser*")
+ assert len(users) == 0
diff --git a/src/tests/intg/test_kcm.py b/src/tests/intg/test_kcm.py
new file mode 100644
index 0000000..370e2a9
--- /dev/null
+++ b/src/tests/intg/test_kcm.py
@@ -0,0 +1,559 @@
+#
+# KCM responder integration tests
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import os.path
+import stat
+import subprocess
+import pytest
+import socket
+import time
+import signal
+import sys
+from datetime import datetime
+
+import kdc
+import krb5utils
+import config
+from util import unindent
+
+MAX_SECRETS = 10
+
+
+class KcmTestEnv(object):
+ def __init__(self, k5kdc, k5util):
+ self.k5kdc = k5kdc
+ self.k5util = k5util
+ self.counter = 0
+
+ def my_uid(self):
+ s_myuid = os.environ['NON_WRAPPED_UID']
+ return int(s_myuid)
+
+ def ccname(self, my_uid=None):
+ if my_uid is None:
+ my_uid = self.my_uid()
+
+ return "KCM:%d" % my_uid
+
+
+def have_kcm_renewal():
+ return os.environ['KCM_RENEW'] == "enabled"
+
+
+@pytest.fixture(scope="module")
+def kdc_instance(request):
+ """Kerberos server instance fixture"""
+ kdc_instance = kdc.KDC(config.PREFIX, "KCMTEST")
+ try:
+ kdc_instance.set_up()
+ kdc_instance.start_kdc()
+ except Exception:
+ kdc_instance.teardown()
+ raise
+ request.addfinalizer(kdc_instance.teardown)
+ return kdc_instance
+
+
+def create_conf_fixture(request, contents):
+ """Generate sssd.conf and add teardown for removing it"""
+ with open(config.CONF_PATH, "w") as conf:
+ conf.write(contents)
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+ request.addfinalizer(lambda: os.unlink(config.CONF_PATH))
+
+
+def create_sssd_kcm_fixture(sock_path, krb5_conf_path, request):
+ if subprocess.call(['sssd', "--genconf"]) != 0:
+ raise Exception("failed to regenerate confdb")
+
+ resp_path = os.path.join(config.LIBEXEC_PATH, "sssd", "sssd_kcm")
+ if not os.access(resp_path, os.X_OK):
+ # It would be cleaner to use pytest.mark.skipif on the package level
+ # but upstream insists on supporting RHEL-6..
+ pytest.skip("No KCM responder, skipping")
+
+ kcm_pid = os.fork()
+ assert kcm_pid >= 0
+
+ if kcm_pid == 0:
+ my_env = os.environ.copy()
+ my_env["KRB5_CONFIG"] = krb5_conf_path
+ if subprocess.call([resp_path, "--uid=0", "--gid=0"], env=my_env) != 0:
+ print("sssd_kcm failed to start")
+ sys.exit(99)
+ else:
+ abs_sock_path = os.path.join(config.RUNSTATEDIR, sock_path)
+ sck = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ for _ in range(1, 100):
+ try:
+ sck.connect(abs_sock_path)
+ except Exception:
+ time.sleep(0.1)
+ else:
+ break
+ sck.close()
+ assert os.path.exists(abs_sock_path)
+
+ def kcm_teardown():
+ if kcm_pid == 0:
+ return
+ os.kill(kcm_pid, signal.SIGTERM)
+ try:
+ os.unlink(os.path.join(config.SECDB_PATH, "secrets.ldb"))
+ except OSError as osex:
+ if osex.errno == 2:
+ pass
+
+ request.addfinalizer(kcm_teardown)
+ return kcm_pid
+
+
+def create_sssd_conf(kcm_path, ccache_storage, max_secrets=MAX_SECRETS):
+ return unindent("""\
+ [sssd]
+ domains = files
+ services = nss
+
+ [domain/files]
+ id_provider = proxy
+ proxy_lib_name = files
+
+ [kcm]
+ socket_path = {kcm_path}
+ ccache_storage = {ccache_storage}
+ """).format(**locals())
+
+
+def create_sssd_conf_renewals(kcm_path, ccache_storage, renew_lifetime,
+ lifetime, renew_interval,
+ max_secrets=MAX_SECRETS):
+ return unindent("""\
+ [sssd]
+ domains = files
+ services = nss
+
+ [domain/files]
+ id_provider = proxy
+ proxy_lib_name = files
+
+ [kcm]
+ socket_path = {kcm_path}
+ ccache_storage = {ccache_storage}
+ tgt_renewal = true
+ krb5_renewable_lifetime = {renew_lifetime}
+ krb5_lifetime = {lifetime}
+ krb5_renew_interval = {renew_interval}
+ """).format(**locals())
+
+
+def common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf):
+ kcm_socket_include = unindent("""
+ [libdefaults]
+ default_ccache_name = KCM:
+ kcm_socket = {kcm_path}
+ """).format(**locals())
+ kdc_instance.add_config({'kcm_socket': kcm_socket_include})
+
+ create_conf_fixture(request, sssd_conf)
+ create_sssd_kcm_fixture(kcm_path, kdc_instance.krb5_conf_path, request)
+
+ k5util = krb5utils.Krb5Utils(kdc_instance.krb5_conf_path)
+
+ return KcmTestEnv(kdc_instance, k5util)
+
+
+@pytest.fixture
+def setup_for_kcm_mem(request, kdc_instance):
+ """
+ Just set up the files provider for tests and enable the KCM
+ responder
+ """
+ kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket")
+ sssd_conf = create_sssd_conf(kcm_path, "memory")
+ return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf)
+
+
+@pytest.fixture
+def setup_for_kcm_secdb(request, kdc_instance):
+ """
+ Set up the KCM responder backed by libsss_secrets
+ """
+ kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket")
+ sssd_conf = create_sssd_conf(kcm_path, "secdb")
+ return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf)
+
+
+@pytest.fixture
+def setup_for_kcm_renewals_secdb(passwd_ops_setup, request, kdc_instance):
+ """
+ Set up the KCM renewals backed by libsss_secrets
+ """
+ kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket")
+ sssd_conf = create_sssd_conf_renewals(kcm_path, "secdb",
+ "10d", "60s", "10s")
+
+ testenv = common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf)
+
+ user = dict(name='user1', passwd='x',
+ uid=testenv.my_uid(), gid=testenv.my_uid(),
+ gecos='User for tests',
+ dir='/home/user1',
+ shell='/bin/bash')
+
+ passwd_ops_setup.useradd(**user)
+
+ return testenv
+
+
+def kcm_init_list_destroy(testenv):
+ """
+ Test that kinit, kdestroy and klist work with KCM
+ """
+ testenv.k5kdc.add_principal("kcmtest", "Secret123")
+
+ ok = testenv.k5util.has_principal("kcmtest@KCMTEST")
+ assert ok is False
+ nprincs = testenv.k5util.num_princs()
+ assert nprincs == 0
+
+ out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123")
+ assert out == 0
+ nprincs = testenv.k5util.num_princs()
+ assert nprincs == 1
+
+ exp_ccname = testenv.ccname()
+ ok = testenv.k5util.has_principal("kcmtest@KCMTEST", exp_ccname)
+ assert ok is True
+
+ out = testenv.k5util.kdestroy()
+ assert out == 0
+
+ ok = testenv.k5util.has_principal("kcmtest@KCMTEST")
+ assert ok is False
+ nprincs = testenv.k5util.num_princs()
+ assert nprincs == 0
+
+
+def test_kcm_mem_init_list_destroy(setup_for_kcm_mem):
+ testenv = setup_for_kcm_mem
+ kcm_init_list_destroy(testenv)
+
+
+def test_kcm_secdb_init_list_destroy(setup_for_kcm_secdb):
+ testenv = setup_for_kcm_secdb
+ kcm_init_list_destroy(testenv)
+
+
+def kcm_overwrite(testenv):
+ """
+ Test that reusing a ccache reinitializes the cache and doesn't
+ add the same principal twice
+ """
+ testenv.k5kdc.add_principal("kcmtest", "Secret123")
+ exp_ccache = {'kcmtest@KCMTEST': ['krbtgt/KCMTEST@KCMTEST']}
+
+ assert testenv.k5util.num_princs() == 0
+
+ out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123")
+ assert out == 0
+ assert exp_ccache == testenv.k5util.list_all_princs()
+
+ out, _, _ = testenv.k5util.kinit("kcmtest", "Secret123")
+ assert out == 0
+ assert exp_ccache == testenv.k5util.list_all_princs()
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_overwrite')
+def test_kcm_mem_overwrite(setup_for_kcm_mem):
+ testenv = setup_for_kcm_mem
+ kcm_overwrite(testenv)
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_overwrite')
+def test_kcm_secdb_overwrite(setup_for_kcm_secdb):
+ testenv = setup_for_kcm_secdb
+ kcm_overwrite(testenv)
+
+
+def collection_init_list_destroy(testenv):
+ """
+ Test that multiple principals and service tickets can be stored
+ in a collection.
+ """
+ testenv.k5kdc.add_principal("alice", "alicepw")
+ testenv.k5kdc.add_principal("bob", "bobpw")
+ testenv.k5kdc.add_principal("carol", "carolpw")
+ testenv.k5kdc.add_principal("host/somehostname")
+
+ assert testenv.k5util.num_princs() == 0
+
+ out, _, _ = testenv.k5util.kinit("alice", "alicepw")
+ assert out == 0
+ assert testenv.k5util.default_principal() == 'alice@KCMTEST'
+ cc_coll = testenv.k5util.list_all_princs()
+ assert len(cc_coll) == 1
+ assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert 'bob@KCMTEST' not in cc_coll
+ assert 'carol@KCMTEST' not in cc_coll
+
+ out, _, _ = testenv.k5util.kinit("bob", "bobpw")
+ assert out == 0
+ assert testenv.k5util.default_principal() == 'bob@KCMTEST'
+ cc_coll = testenv.k5util.list_all_princs()
+ assert len(cc_coll) == 2
+ assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert 'carol@KCMTEST' not in cc_coll
+
+ out, _, _ = testenv.k5util.kinit("carol", "carolpw")
+ assert out == 0
+ assert testenv.k5util.default_principal() == 'carol@KCMTEST'
+ cc_coll = testenv.k5util.list_all_princs()
+ assert len(cc_coll) == 3
+ assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert cc_coll['carol@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+
+ out, _, _ = testenv.k5util.kvno('host/somehostname')
+ assert out == 0
+ cc_coll = testenv.k5util.list_all_princs()
+ assert len(cc_coll) == 3
+ assert set(cc_coll['carol@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+ 'host/somehostname@KCMTEST'])
+
+ out = testenv.k5util.kdestroy()
+ assert out == 0
+ # If the default is removed, KCM just uses whetever is the first entry
+ # in the collection as the default. And sine the KCM back ends don't
+ # guarantee if they are FIFO or LIFO, just check for either alice or bob
+ assert testenv.k5util.default_principal() in \
+ ['alice@KCMTEST', 'bob@KCMTEST']
+ cc_coll = testenv.k5util.list_all_princs()
+ assert len(cc_coll) == 2
+ assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert 'carol@KCMTEST' not in cc_coll
+
+ # Let's kinit a 3rd principal
+ out, _, _ = testenv.k5util.kinit("carol", "carolpw")
+ assert out == 0
+ cc_coll = testenv.k5util.list_all_princs()
+ assert len(cc_coll) == 3
+ assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert cc_coll['carol@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+
+ # Let's ensure `kdestroy -A` works with more than 2 principals
+ # https://github.com/SSSD/sssd/issues/4440
+ out = testenv.k5util.kdestroy(all_ccaches=True)
+ assert out == 0
+ assert testenv.k5util.num_princs() == 0
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_collection')
+def test_kcm_mem_collection_init_list_destroy(setup_for_kcm_mem):
+ testenv = setup_for_kcm_mem
+ collection_init_list_destroy(testenv)
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_collection')
+def test_kcm_secdb_collection_init_list_destroy(setup_for_kcm_secdb):
+ testenv = setup_for_kcm_secdb
+ collection_init_list_destroy(testenv)
+
+
+def exercise_kswitch(testenv):
+ """
+ Test switching between principals
+ """
+ testenv.k5kdc.add_principal("alice", "alicepw")
+ testenv.k5kdc.add_principal("bob", "bobpw")
+ testenv.k5kdc.add_principal("host/somehostname")
+ testenv.k5kdc.add_principal("host/differenthostname")
+
+ out, _, _ = testenv.k5util.kinit("alice", "alicepw")
+ assert out == 0
+ assert testenv.k5util.default_principal() == 'alice@KCMTEST'
+
+ out, _, _ = testenv.k5util.kinit("bob", "bobpw")
+ assert out == 0
+ assert testenv.k5util.default_principal() == 'bob@KCMTEST'
+
+ cc_coll = testenv.k5util.list_all_princs()
+ assert len(cc_coll) == 2
+ assert cc_coll['alice@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+ assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+
+ out = testenv.k5util.kswitch("alice@KCMTEST")
+ assert testenv.k5util.default_principal() == 'alice@KCMTEST'
+ out, _, _ = testenv.k5util.kvno('host/somehostname')
+ assert out == 0
+ cc_coll = testenv.k5util.list_all_princs()
+ assert len(cc_coll) == 2
+ assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+ 'host/somehostname@KCMTEST'])
+ assert cc_coll['bob@KCMTEST'] == ['krbtgt/KCMTEST@KCMTEST']
+
+ out = testenv.k5util.kswitch("bob@KCMTEST")
+ assert testenv.k5util.default_principal() == 'bob@KCMTEST'
+ out, _, _ = testenv.k5util.kvno('host/differenthostname')
+ assert out == 0
+ cc_coll = testenv.k5util.list_all_princs()
+ assert len(cc_coll) == 2
+ assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+ 'host/somehostname@KCMTEST'])
+ assert set(cc_coll['bob@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+ 'host/differenthostname@KCMTEST'])
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_switch')
+def test_kcm_mem_kswitch(setup_for_kcm_mem):
+ testenv = setup_for_kcm_mem
+ exercise_kswitch(testenv)
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__kinit_switch')
+def test_kcm_secdb_kswitch(setup_for_kcm_secdb):
+ testenv = setup_for_kcm_secdb
+ exercise_kswitch(testenv)
+
+
+def exercise_subsidiaries(testenv):
+ """
+ Test that subsidiary caches are usable and KCM: without specifying UID
+ can be used to identify the collection
+ """
+ testenv.k5kdc.add_principal("alice", "alicepw")
+ testenv.k5kdc.add_principal("bob", "bobpw")
+ testenv.k5kdc.add_principal("host/somehostname")
+ testenv.k5kdc.add_principal("host/differenthostname")
+
+ out, _, _ = testenv.k5util.kinit("alice", "alicepw")
+ assert out == 0
+ out, _, _ = testenv.k5util.kvno('host/somehostname')
+
+ out, _, _ = testenv.k5util.kinit("bob", "bobpw")
+ assert out == 0
+ out, _, _ = testenv.k5util.kvno('host/differenthostname')
+
+ exp_cc_coll = dict()
+ exp_cc_coll['alice@KCMTEST'] = 'host/somehostname@KCMTEST'
+ exp_cc_coll['bob@KCMTEST'] = 'host/differenthostname@KCMTEST'
+
+ klist_l = testenv.k5util.list_princs()
+ princ_ccache = dict()
+ for line in klist_l:
+ princ, subsidiary = line.split()
+ princ_ccache[princ] = subsidiary
+
+ for princ, subsidiary in princ_ccache.items():
+ env = {'KRB5CCNAME': subsidiary}
+ cc_coll = testenv.k5util.list_all_princs(env=env)
+ assert len(cc_coll) == 1
+ assert princ in cc_coll
+ assert exp_cc_coll[princ] in cc_coll[princ]
+
+ cc_coll = testenv.k5util.list_all_princs(env={'KRB5CCNAME': 'KCM:'})
+ assert len(cc_coll) == 2
+ assert set(cc_coll['alice@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+ 'host/somehostname@KCMTEST'])
+ assert set(cc_coll['bob@KCMTEST']) == set(['krbtgt/KCMTEST@KCMTEST',
+ 'host/differenthostname@KCMTEST'])
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__subsidiaries')
+def test_kcm_mem_subsidiaries(setup_for_kcm_mem):
+ testenv = setup_for_kcm_mem
+ exercise_subsidiaries(testenv)
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__subsidiaries')
+def test_kcm_secdb_subsidiaries(setup_for_kcm_secdb):
+ testenv = setup_for_kcm_secdb
+ exercise_subsidiaries(testenv)
+
+
+def kdestroy_nocache(testenv):
+ """
+ Destroying a non-existing ccache should not throw an error
+ """
+ testenv.k5kdc.add_principal("alice", "alicepw")
+ out, _, _ = testenv.k5util.kinit("alice", "alicepw")
+ assert out == 0
+
+ testenv.k5util.kdestroy()
+ assert out == 0
+ out = testenv.k5util.kdestroy()
+ assert out == 0
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__kdestroy_nocache')
+def test_kcm_mem_kdestroy_nocache(setup_for_kcm_mem):
+ testenv = setup_for_kcm_mem
+ exercise_subsidiaries(testenv)
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__kdestroy_nocache')
+def test_kcm_secdb_kdestroy_nocache(setup_for_kcm_secdb):
+ testenv = setup_for_kcm_secdb
+ exercise_subsidiaries(testenv)
+
+
+def get_secrets_socket():
+ return os.path.join(config.RUNSTATEDIR, "secrets.socket")
+
+
+@pytest.mark.converted('test_kcm.py', 'test_kcm__tgt_renewal')
+@pytest.mark.skipif(not have_kcm_renewal(),
+ reason="KCM renewal disabled, skipping")
+def test_kcm_renewals(setup_for_kcm_renewals_secdb):
+ """
+ Test that basic KCM renewal works
+ """
+ if "LC_TIME" in os.environ:
+ del os.environ["LC_TIME"]
+ testenv = setup_for_kcm_renewals_secdb
+ testenv.k5kdc.add_principal("user1", "Secret123")
+
+ ok = testenv.k5util.has_principal("user1@KCMTEST")
+ assert ok is False
+ nprincs = testenv.k5util.num_princs()
+ assert nprincs == 0
+
+ # Renewal is only performed after half of lifetime exceeded,
+ # see kcm_renew_all_tgts()
+ options = ["-r", "15s", "-l", "15s"]
+ out, _, _ = testenv.k5util.kinit("user1", "Secret123", options)
+ assert out == 0
+ nprincs = testenv.k5util.num_princs()
+ assert nprincs == 1
+
+ timestr_fmt = "%m/%d/%y %H:%M:%S"
+ initial_times = testenv.k5util.list_times()
+
+ # Wait for renewal to trigger once, after renew interval
+ time.sleep(15)
+
+ renewed_times = testenv.k5util.list_times()
+
+ init_times = initial_times.split()[0] + ' ' + initial_times.split()[1]
+ renew_times = renewed_times.split()[0] + ' ' + renewed_times.split()[1]
+ dt_init = datetime.strptime(init_times, timestr_fmt)
+ dt_renew = datetime.strptime(renew_times, timestr_fmt)
+ assert dt_renew > dt_init
diff --git a/src/tests/intg/test_ldap.py b/src/tests/intg/test_ldap.py
new file mode 100644
index 0000000..81c4f31
--- /dev/null
+++ b/src/tests/intg/test_ldap.py
@@ -0,0 +1,2106 @@
+#
+# LDAP integration test
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import stat
+import pwd
+import grp
+import signal
+import subprocess
+import time
+import ldap
+import ldap.modlist
+import pytest
+
+import config
+import ds_openldap
+import ent
+import ldap_ent
+import sssd_id
+import sssd_ldb
+from util import unindent
+from sssd_nss import NssReturnCode
+from sssd_passwd import call_sssd_getpwnam, call_sssd_getpwuid
+from sssd_group import call_sssd_getgrnam, call_sssd_getgrgid
+
+LDAP_BASE_DN = "dc=example,dc=com"
+INTERACTIVE_TIMEOUT = 4
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(ds_inst.teardown)
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(ldap_conn.unbind_s)
+ return ldap_conn
+
+
+def create_ldap_entries(ldap_conn, ent_list=None):
+ """Add LDAP entries from ent_list"""
+ if ent_list is not None:
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+
+def cleanup_ldap_entries(ldap_conn, ent_list=None):
+ """Remove LDAP entries added by create_ldap_entries"""
+ if ent_list is None:
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"):
+ for entry in ldap_conn.search_s(f"ou={ou},"
+ f"{ldap_conn.ds_inst.base_dn}",
+ ldap.SCOPE_ONELEVEL,
+ attrlist=[]):
+ ldap_conn.delete_s(entry[0])
+ else:
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+
+
+def create_ldap_cleanup(request, ldap_conn, ent_list=None):
+ """Add teardown for removing all user/group LDAP entries"""
+ request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list))
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list=None, cleanup=True):
+ """Add LDAP entries and add teardown for removing them"""
+ create_ldap_entries(ldap_conn, ent_list)
+ if cleanup:
+ create_ldap_cleanup(request, ldap_conn, ent_list)
+
+
+SCHEMA_RFC2307 = "rfc2307"
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+
+def format_basic_conf(ldap_conn, schema):
+ """Format a basic SSSD configuration"""
+ schema_conf = "ldap_schema = " + schema + "\n"
+ if schema == SCHEMA_RFC2307_BIS:
+ schema_conf += "ldap_group_object_class = groupOfNames\n"
+ return unindent("""\
+ [sssd]
+ debug_level = 0xffff
+ domains = LDAP
+ services = nss, pam
+ enable_files_domain = false
+
+ [nss]
+ debug_level = 0xffff
+ memcache_timeout = 0
+ entry_negative_timeout = 1
+
+ [pam]
+ debug_level = 0xffff
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ debug_level = 0xffff
+ {schema_conf}
+ id_provider = ldap
+ auth_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+
+
+def format_interactive_conf(ldap_conn, schema):
+ """Format an SSSD configuration with all caches refreshing in 4 seconds"""
+ return \
+ format_basic_conf(ldap_conn, schema) + \
+ unindent("""
+ [nss]
+ memcache_timeout = 0
+ entry_negative_timeout = 0
+
+ [domain/LDAP]
+ ldap_purge_cache_timeout = 1
+ entry_cache_timeout = {0}
+ """).format(INTERACTIVE_TIMEOUT)
+
+
+def format_rfc2307bis_deref_conf(ldap_conn, schema):
+ """Format an SSSD configuration with all caches refreshing in 4 seconds"""
+ return \
+ format_basic_conf(ldap_conn, schema) + \
+ unindent("""
+ [nss]
+ memcache_timeout = 0
+ entry_negative_timeout = 0
+
+ [domain/LDAP]
+ entry_cache_timeout = {0}
+ ldap_deref_threshold = 1
+ """).format(INTERACTIVE_TIMEOUT)
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+ """Add teardown for removing sssd.conf"""
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+ create_conf_cleanup(request)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_cleanup(request):
+ """Add teardown for stopping SSSD and removing its state"""
+ request.addfinalizer(cleanup_sssd_process)
+
+
+def create_sssd_fixture(request):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process()
+ create_sssd_cleanup(request)
+
+
+@pytest.fixture
+def sanity_rfc2307(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 2003)
+
+ ent_list.add_group("group1", 2001)
+ ent_list.add_group("group2", 2002)
+ ent_list.add_group("group3", 2003)
+
+ ent_list.add_group("empty_group", 2010)
+
+ ent_list.add_group("two_user_group", 2012, ["user1", "user2"])
+
+ ent_list.add_user("t(u)ser", 5000, 5001)
+ ent_list.add_group("group(_u)ser1", 5001, ["t(u)ser"])
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def simple_rfc2307(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user('usr\\\\001', 181818, 181818)
+ ent_list.add_group("group1", 181818)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def sanity_rfc2307_bis(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 2003)
+
+ ent_list.add_group_bis("group1", 2001)
+ ent_list.add_group_bis("group2", 2002)
+ ent_list.add_group_bis("group3", 2003)
+
+ ent_list.add_group_bis("empty_group1", 2010)
+ ent_list.add_group_bis("empty_group2", 2011)
+
+ ent_list.add_group_bis("two_user_group", 2012, ["user1", "user2"])
+ ent_list.add_group_bis("group_empty_group", 2013, [], ["empty_group1"])
+ ent_list.add_group_bis("group_two_empty_groups", 2014,
+ [], ["empty_group1", "empty_group2"])
+ ent_list.add_group_bis("one_user_group1", 2015, ["user1"])
+ ent_list.add_group_bis("one_user_group2", 2016, ["user2"])
+ ent_list.add_group_bis("group_one_user_group", 2017,
+ [], ["one_user_group1"])
+ ent_list.add_group_bis("group_two_user_group", 2018,
+ [], ["two_user_group"])
+ ent_list.add_group_bis("group_two_one_user_groups", 2019,
+ [], ["one_user_group1", "one_user_group2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def member_with_different_cases_rfc2307_bis(request, ldap_conn):
+ """
+ Create a group where the user DN values of the RFC2307bis member attribute
+ differ in case from the original DN of the user object.
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+
+ ent_list.add_group_bis("two_user_group", 2012, ["USER1", "uSeR2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def expected_list_to_name_dict(entries):
+ return dict((u["name"], u) for u in entries)
+
+
+def test_regression_ticket2163(ldap_conn, simple_rfc2307):
+ ent.assert_passwd_by_name(
+ 'usr\\001',
+ dict(name='usr\\001', passwd='*', uid=181818, gid=181818,
+ gecos='181818', shell='/bin/bash'))
+
+
+def test_sanity_rfc2307(ldap_conn, sanity_rfc2307):
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001',
+ dir='/home/user1', shell='/bin/bash'),
+ dict(name='user2', passwd='*', uid=1002, gid=2002, gecos='1002',
+ dir='/home/user2', shell='/bin/bash'),
+ dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003',
+ dir='/home/user3', shell='/bin/bash')
+ ])
+ ent.assert_each_passwd_by_name(passwd_pattern)
+
+ group_pattern = expected_list_to_name_dict([
+ dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()),
+ dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()),
+ dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()),
+ dict(name='empty_group', passwd='*', gid=2010,
+ mem=ent.contains_only()),
+ dict(name='two_user_group', passwd='*', gid=2012,
+ mem=ent.contains_only("user1", "user2"))
+ ])
+ ent.assert_each_group_by_name(group_pattern)
+
+ with pytest.raises(KeyError):
+ pwd.getpwnam("non_existent_user")
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1)
+ with pytest.raises(KeyError):
+ grp.getgrnam("non_existent_group")
+ with pytest.raises(KeyError):
+ grp.getgrgid(1)
+
+
+def test_sanity_rfc2307_bis(ldap_conn, sanity_rfc2307_bis):
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001',
+ dir='/home/user1', shell='/bin/bash'),
+ dict(name='user2', passwd='*', uid=1002, gid=2002, gecos='1002',
+ dir='/home/user2', shell='/bin/bash'),
+ dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003',
+ dir='/home/user3', shell='/bin/bash')
+ ])
+ ent.assert_each_passwd_by_name(passwd_pattern)
+
+ group_pattern = expected_list_to_name_dict([
+ dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()),
+ dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()),
+ dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()),
+ dict(name='empty_group1', passwd='*', gid=2010,
+ mem=ent.contains_only()),
+ dict(name='empty_group2', passwd='*', gid=2011,
+ mem=ent.contains_only()),
+ dict(name='two_user_group', passwd='*', gid=2012,
+ mem=ent.contains_only("user1", "user2")),
+ dict(name='group_empty_group', passwd='*', gid=2013,
+ mem=ent.contains_only()),
+ dict(name='group_two_empty_groups', passwd='*', gid=2014,
+ mem=ent.contains_only()),
+ dict(name='one_user_group1', passwd='*', gid=2015,
+ mem=ent.contains_only("user1")),
+ dict(name='one_user_group2', passwd='*', gid=2016,
+ mem=ent.contains_only("user2")),
+ dict(name='group_one_user_group', passwd='*', gid=2017,
+ mem=ent.contains_only("user1")),
+ dict(name='group_two_user_group', passwd='*', gid=2018,
+ mem=ent.contains_only("user1", "user2")),
+ dict(name='group_two_one_user_groups', passwd='*', gid=2019,
+ mem=ent.contains_only("user1", "user2"))
+ ])
+ ent.assert_each_group_by_name(group_pattern)
+
+ with pytest.raises(KeyError):
+ pwd.getpwnam("non_existent_user")
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1)
+ with pytest.raises(KeyError):
+ grp.getgrnam("non_existent_group")
+ with pytest.raises(KeyError):
+ grp.getgrgid(1)
+
+
+def test_member_with_different_cases_rfc2307_bis(
+ ldap_conn,
+ member_with_different_cases_rfc2307_bis):
+ """
+ Regression test for https://bugzilla.redhat.com/show_bug.cgi?id=1817122
+ Make sure that group members are added properly to the group even if the
+ user DN in the RFC2307bis member attribute differs in case from the
+ original DN of the user object.
+ """
+ group_pattern = expected_list_to_name_dict([
+ dict(name='two_user_group', passwd='*', gid=2012,
+ mem=ent.contains_only("user1", "user2")),
+ ])
+ ent.assert_each_group_by_name(group_pattern)
+
+
+@pytest.fixture
+def refresh_after_cleanup_task(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+
+ ent_list.add_group_bis("group1", 2001, ["user1"])
+ ent_list.add_group_bis("group2", 2002, [], ["group1"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [domain/LDAP]
+ entry_cache_user_timeout = 1
+ entry_cache_group_timeout = 5000
+ ldap_purge_cache_timeout = 3
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_refresh_after_cleanup_task(ldap_conn, refresh_after_cleanup_task):
+ """
+ Regression test for ticket:
+ https://fedorahosted.org/sssd/ticket/2676
+ """
+ ent.assert_group_by_name(
+ "group2",
+ dict(mem=ent.contains_only("user1")))
+
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ time.sleep(15)
+
+ ent.assert_group_by_name(
+ "group2",
+ dict(mem=ent.contains_only("user1")))
+
+
+@pytest.fixture
+def update_ts_after_cleanup_task(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2001)
+
+ ent_list.add_group_bis("group1", 2001, ["user1", "user2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [domain/LDAP]
+ ldap_purge_cache_timeout = 3
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_update_ts_cache_after_cleanup_task(ldap_conn,
+ update_ts_after_cleanup_task):
+ """
+ Regression test for ticket:
+ https://fedorahosted.org/sssd/ticket/2676
+ """
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1", "user2")))
+
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ 'user2',
+ dict(name='user2', passwd='*', uid=1002, gid=2001,
+ gecos='1002', shell='/bin/bash'))
+
+ if subprocess.call(["sss_cache", "-u", "user1"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ # The cleanup task runs every 3 seconds, so sleep for 6
+ # so that we know the cleanup task ran at least once
+ # even if we start sleeping during the first one
+ time.sleep(6)
+
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1", "user2")))
+
+
+@pytest.fixture
+def blank_rfc2307(request, ldap_conn):
+ """Create blank RFC2307 directory fixture with interactive SSSD conf"""
+ create_ldap_cleanup(request, ldap_conn)
+ create_conf_fixture(request,
+ format_interactive_conf(ldap_conn, SCHEMA_RFC2307))
+ create_sssd_fixture(request)
+
+
+@pytest.fixture
+def blank_rfc2307_bis(request, ldap_conn):
+ """Create blank RFC2307bis directory fixture with interactive SSSD conf"""
+ create_ldap_cleanup(request, ldap_conn)
+ create_conf_fixture(request,
+ format_interactive_conf(ldap_conn, SCHEMA_RFC2307_BIS))
+ create_sssd_fixture(request)
+
+
+@pytest.fixture
+def user_and_group_rfc2307(request, ldap_conn):
+ """
+ Create an RFC2307 directory fixture with interactive SSSD conf,
+ one user and one group
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user", 1001, 2000)
+ ent_list.add_group("group", 2001)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ create_conf_fixture(request,
+ format_interactive_conf(ldap_conn, SCHEMA_RFC2307))
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def user_and_groups_rfc2307_bis(request, ldap_conn):
+ """
+ Create an RFC2307bis directory fixture with interactive SSSD conf,
+ one user and two groups
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user", 1001, 2000)
+ ent_list.add_group_bis("group1", 2001)
+ ent_list.add_group_bis("group2", 2002)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ create_conf_fixture(request,
+ format_interactive_conf(ldap_conn, SCHEMA_RFC2307_BIS))
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def rfc2307bis_deref_group_with_users(request, ldap_conn):
+ """
+ Create an RFC2307bis directory fixture with interactive SSSD conf,
+ one user and two groups
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2000)
+ ent_list.add_user("user2", 1001, 2000)
+ ent_list.add_user("user3", 1001, 2000)
+ ent_list.add_group_bis("group1", 20000, member_uids=("user1", "user2"))
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ create_conf_fixture(request,
+ format_rfc2307bis_deref_conf(
+ ldap_conn,
+ SCHEMA_RFC2307_BIS))
+ create_sssd_fixture(request)
+ return None
+
+
+def test_ldap_group_dereference(ldap_conn, rfc2307bis_deref_group_with_users):
+ ent.assert_group_by_name("group1",
+ dict(mem=ent.contains_only("user1", "user2")))
+
+
+@pytest.fixture
+def override_homedir(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_homedir_A", 1001, 2001,
+ homeDirectory="/home/A")
+ ent_list.add_user("user_with_homedir_B", 1002, 2002,
+ homeDirectory="/home/B")
+ ent_list.add_user("user_with_empty_homedir", 1003, 2003,
+ homeDirectory="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ override_homedir = /home/B
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_override_homedir(override_homedir):
+ """Test the effect of the "override_homedir" option"""
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name="user_with_homedir_A", uid=1001, dir="/home/B"),
+ dict(name="user_with_homedir_B", uid=1002, dir="/home/B"),
+ dict(name="user_with_empty_homedir", uid=1003, dir="/home/B")
+ ])
+
+ ent.assert_each_passwd_by_name(passwd_pattern)
+
+
+@pytest.fixture
+def fallback_homedir(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_homedir_A", 1001, 2001,
+ homeDirectory="/home/A")
+ ent_list.add_user("user_with_homedir_B", 1002, 2002,
+ homeDirectory="/home/B")
+ ent_list.add_user("user_with_empty_homedir", 1003, 2003,
+ homeDirectory="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ fallback_homedir = /home/B
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_fallback_homedir(fallback_homedir):
+ """Test the effect of the "fallback_homedir" option"""
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name="user_with_homedir_A", uid=1001, dir="/home/A"),
+ dict(name="user_with_homedir_B", uid=1002, dir="/home/B"),
+ dict(name="user_with_empty_homedir", uid=1003, dir="/home/B")
+ ])
+
+ ent.assert_each_passwd_by_name(passwd_pattern)
+
+
+@pytest.fixture
+def override_shell(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_shell_A", 1001, 2001,
+ loginShell="/bin/A")
+ ent_list.add_user("user_with_shell_B", 1002, 2002,
+ loginShell="/bin/B")
+ ent_list.add_user("user_with_empty_shell", 1003, 2003,
+ loginShell="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ override_shell = /bin/B
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_override_shell(override_shell):
+ """Test the effect of the "override_shell" option"""
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name="user_with_shell_A", uid=1001, shell="/bin/B"),
+ dict(name="user_with_shell_B", uid=1002, shell="/bin/B"),
+ dict(name="user_with_empty_shell", uid=1003, shell="/bin/B")
+ ])
+
+ ent.assert_each_passwd_by_name(passwd_pattern)
+
+
+@pytest.fixture
+def shell_fallback(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_sh_shell", 1001, 2001,
+ loginShell="/bin/sh")
+ ent_list.add_user("user_with_not_installed_shell", 1002, 2002,
+ loginShell="/bin/not_installed")
+ ent_list.add_user("user_with_empty_shell", 1003, 2003,
+ loginShell="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ shell_fallback = /bin/fallback
+ allowed_shells = /bin/not_installed
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_shell_fallback(shell_fallback):
+ """Test the effect of the "shell_fallback" option"""
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"),
+ dict(name="user_with_not_installed_shell", uid=1002,
+ shell="/bin/fallback"),
+ dict(name="user_with_empty_shell", uid=1003, shell="")
+ ])
+
+ ent.assert_each_passwd_by_name(passwd_pattern)
+
+
+@pytest.fixture
+def default_shell(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_sh_shell", 1001, 2001,
+ loginShell="/bin/sh")
+ ent_list.add_user("user_with_not_installed_shell", 1002, 2002,
+ loginShell="/bin/not_installed")
+ ent_list.add_user("user_with_empty_shell", 1003, 2003,
+ loginShell="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ default_shell = /bin/default
+ allowed_shells = /bin/default, /bin/not_installed
+ shell_fallback = /bin/fallback
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_default_shell(default_shell):
+ """Test the effect of the "default_shell" option"""
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"),
+ dict(name="user_with_not_installed_shell", uid=1002,
+ shell="/bin/fallback"),
+ dict(name="user_with_empty_shell", uid=1003,
+ shell="/bin/default")
+ ])
+
+ ent.assert_each_passwd_by_name(passwd_pattern)
+
+
+@pytest.fixture
+def vetoed_shells(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user_with_sh_shell", 1001, 2001,
+ loginShell="/bin/sh")
+ ent_list.add_user("user_with_vetoed_shell", 1002, 2002,
+ loginShell="/bin/vetoed")
+ ent_list.add_user("user_with_empty_shell", 1003, 2003,
+ loginShell="")
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [nss]
+ default_shell = /bin/default
+ vetoed_shells = /bin/vetoed
+ shell_fallback = /bin/fallback
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_vetoed_shells(vetoed_shells):
+ """Test the effect of the "vetoed_shells" option"""
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name="user_with_sh_shell", uid=1001, shell="/bin/sh"),
+ dict(name="user_with_vetoed_shell", uid=1002,
+ shell="/bin/fallback"),
+ dict(name="user_with_empty_shell", uid=1003,
+ shell="/bin/default")
+ ])
+
+ ent.assert_each_passwd_by_name(passwd_pattern)
+
+
+def test_user_2307bis_nested_groups(ldap_conn,
+ sanity_rfc2307_bis):
+ """
+ Test nested groups.
+
+ Regression test for ticket:
+ https://fedorahosted.org/sssd/ticket/3093
+ """
+ primary_gid = 2001
+ # group1, two_user_group, one_user_group1, group_one_user_group,
+ # group_two_user_group, group_two_one_user_groups
+ expected_gids = [2001, 2012, 2015, 2017, 2018, 2019]
+
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001,
+ gid=primary_gid))
+
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user1", primary_gid)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(expected_gids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(expected_gids)])
+ )
+
+
+def test_special_characters_in_names(ldap_conn, sanity_rfc2307):
+ """
+ Test special characters which could cause malformed filter
+ in ldb_seach.
+
+ Regression test for ticket:
+ https://fedorahosted.org/sssd/ticket/3121
+ """
+ ent.assert_passwd_by_name(
+ "t(u)ser",
+ dict(name="t(u)ser", passwd="*", uid=5000, gid=5001,
+ gecos="5000", shell="/bin/bash"))
+
+ ent.assert_group_by_name(
+ "group(_u)ser1",
+ dict(name="group(_u)ser1", passwd="*", gid=5001,
+ mem=ent.contains_only("t(u)ser")))
+
+
+@pytest.fixture
+def extra_attributes(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user", 2001, 2000)
+ ent_list.add_group("group", 2000)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [domain/LDAP]
+ ldap_user_extra_attrs = mail, name:uid, givenName
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_extra_attribute_already_exists(ldap_conn, extra_attributes):
+ """Test the effect of the "vetoed_shells" option"""
+
+ user = 'user'
+ extra_attribute = 'givenName'
+ given_name = b'unix_user'
+
+ user_dn = "uid=" + user + ",ou=Users," + ldap_conn.ds_inst.base_dn
+
+ old = {'objectClass': [b'top', b'inetOrgPerson', b'posixAccount']}
+ new = {'objectClass': [b'top', b'inetOrgPerson', b'posixAccount',
+ b'extensibleObject']}
+ ldif = ldap.modlist.modifyModlist(old, new)
+
+ ldap_conn.modify_s(user_dn, ldif)
+ ldap_conn.modify_s(user_dn, [(ldap.MOD_ADD, extra_attribute, given_name)])
+
+ ent.assert_passwd_by_name(
+ user,
+ dict(name="user", uid=2001, gid=2000, shell="/bin/bash"),
+ )
+
+ domain = 'LDAP'
+ ldb_conn = sssd_ldb.SssdLdb('LDAP')
+ val = ldb_conn.get_entry_attr(sssd_ldb.CacheType.sysdb,
+ sssd_ldb.TsCacheEntry.user,
+ user, domain, extra_attribute)
+
+ assert val == given_name
+
+
+@pytest.fixture
+def add_user_to_group(request, ldap_conn):
+ """
+ Adding user to group
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_group_bis("group1", 20001, member_uids=["user1"])
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ create_conf_fixture(request,
+ format_rfc2307bis_deref_conf(
+ ldap_conn,
+ SCHEMA_RFC2307_BIS))
+ create_sssd_fixture(request)
+ return None
+
+
+def test_add_user_to_group(ldap_conn, add_user_to_group):
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001))
+ ent.assert_group_by_name("group1", dict(mem=ent.contains_only("user1")))
+
+
+@pytest.fixture
+def remove_user_from_group(request, ldap_conn):
+ """
+ Adding user to group
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_group_bis("group1", 20001, member_uids=["user1", "user2"])
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ create_conf_fixture(request,
+ format_rfc2307bis_deref_conf(
+ ldap_conn,
+ SCHEMA_RFC2307_BIS))
+ create_sssd_fixture(request)
+ return None
+
+
+def test_remove_user_from_group(ldap_conn, remove_user_from_group):
+ """
+ Removing two users from group, step by step
+ """
+ group1_dn = 'cn=group1,ou=Groups,' + ldap_conn.ds_inst.base_dn
+
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001))
+ ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002))
+ ent.assert_group_by_name("group1",
+ dict(mem=ent.contains_only("user1", "user2")))
+
+ # removing of user2 from group1
+ old = {'member': [b"uid=user1,ou=Users,dc=example,dc=com",
+ b"uid=user2,ou=Users,dc=example,dc=com"]}
+ new = {'member': [b"uid=user1,ou=Users,dc=example,dc=com"]}
+
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(group1_dn, ldif)
+
+ if subprocess.call(["sss_cache", "-GU"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001))
+ ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002))
+ ent.assert_group_by_name("group1", dict(mem=ent.contains_only("user1")))
+
+ # removing of user1 from group1
+ old = {'member': [b"uid=user1,ou=Users,dc=example,dc=com"]}
+ new = {'member': []}
+
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(group1_dn, ldif)
+
+ if subprocess.call(["sss_cache", "-GU"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001))
+ ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002))
+ ent.assert_group_by_name("group1", dict(mem=ent.contains_only()))
+
+
+@pytest.fixture
+def remove_user_from_nested_group(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_group_bis("group1", 20001, member_uids=["user1"])
+ ent_list.add_group_bis("group2", 20002, member_uids=["user2"])
+ ent_list.add_group_bis("group3", 20003, member_gids=["group1", "group2"])
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ create_conf_fixture(request,
+ format_rfc2307bis_deref_conf(
+ ldap_conn,
+ SCHEMA_RFC2307_BIS))
+ create_sssd_fixture(request)
+ return None
+
+
+def test_remove_user_from_nested_group(ldap_conn,
+ remove_user_from_nested_group):
+
+ group3_dn = 'cn=group3,ou=Groups,' + ldap_conn.ds_inst.base_dn
+
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001))
+ ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002))
+
+ ent.assert_group_by_name("group1",
+ dict(mem=ent.contains_only("user1")))
+ ent.assert_group_by_name("group2",
+ dict(mem=ent.contains_only("user2")))
+
+ ent.assert_group_by_name("group3",
+ dict(mem=ent.contains_only("user1",
+ "user2")))
+
+ # removing of group2 from group3
+ old = {'member': [b"cn=group1,ou=Groups,dc=example,dc=com",
+ b"cn=group2,ou=Groups,dc=example,dc=com"]}
+ new = {'member': [b"cn=group1,ou=Groups,dc=example,dc=com"]}
+
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(group3_dn, ldif)
+
+ if subprocess.call(["sss_cache", "-GU"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001))
+ ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002))
+
+ ent.assert_group_by_name("group1",
+ dict(mem=ent.contains_only("user1")))
+ ent.assert_group_by_name("group2",
+ dict(mem=ent.contains_only("user2")))
+ ent.assert_group_by_name("group3",
+ dict(mem=ent.contains_only("user1")))
+
+ # removing of group1 from group3
+ old = {'member': [b"cn=group1,ou=Groups,dc=example,dc=com"]}
+ new = {'member': []}
+
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(group3_dn, ldif)
+
+ if subprocess.call(["sss_cache", "-GU"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=2001))
+ ent.assert_passwd_by_name("user2", dict(name="user2", uid=1002, gid=2002))
+
+ ent.assert_group_by_name("group1",
+ dict(mem=ent.contains_only("user1")))
+ ent.assert_group_by_name("group2",
+ dict(mem=ent.contains_only("user2")))
+ ent.assert_group_by_name("group3",
+ dict(mem=ent.contains_only()))
+
+
+def zero_nesting_sssd_conf(ldap_conn, schema):
+ """Format an SSSD configuration with group nesting disabled"""
+ return \
+ format_basic_conf(ldap_conn, schema) + \
+ unindent("""
+ [domain/LDAP]
+ ldap_group_nesting_level = 0
+ """).format(INTERACTIVE_TIMEOUT)
+
+
+@pytest.fixture
+def rfc2307bis_no_nesting(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_group_bis("primarygroup", 2001)
+ ent_list.add_group_bis("parentgroup", 2010, member_uids=["user1"])
+ ent_list.add_group_bis("nestedgroup", 2011, member_gids=["parentgroup"])
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ create_conf_fixture(request,
+ zero_nesting_sssd_conf(
+ ldap_conn,
+ SCHEMA_RFC2307_BIS))
+ create_sssd_fixture(request)
+ return None
+
+
+def test_zero_nesting_level(ldap_conn, rfc2307bis_no_nesting):
+ """
+ Test initgroups operation with rfc2307bis schema asserting
+ only primary group and parent groups are included in group
+ list. No parent groups of groups should be returned with zero
+ group nesting level.
+ """
+ ent.assert_group_by_name("parentgroup",
+ dict(mem=ent.contains_only("user1")))
+ ent.assert_group_by_name("nestedgroup",
+ dict(mem=ent.contains_only()))
+
+ (res, errno, grp_list) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+
+ # test nestedgroup is not returned in group list
+ assert sorted(grp_list) == sorted(["primarygroup", "parentgroup"])
+
+
+@pytest.fixture
+def sanity_nss_filter(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 2003)
+
+ ent_list.add_group_bis("group1", 2001)
+ ent_list.add_group_bis("group2", 2002)
+ ent_list.add_group_bis("group3", 2003)
+
+ ent_list.add_group_bis("empty_group1", 2010)
+ ent_list.add_group_bis("empty_group2", 2011)
+
+ ent_list.add_group_bis("two_user_group", 2012, ["user1", "user2"])
+ ent_list.add_group_bis("group_empty_group", 2013, [], ["empty_group1"])
+ ent_list.add_group_bis("group_two_empty_groups", 2014,
+ [], ["empty_group1", "empty_group2"])
+ ent_list.add_group_bis("one_user_group1", 2015, ["user1"])
+ ent_list.add_group_bis("one_user_group2", 2016, ["user2"])
+ ent_list.add_group_bis("group_one_user_group", 2017,
+ [], ["one_user_group1"])
+ ent_list.add_group_bis("group_two_user_group", 2018,
+ [], ["two_user_group"])
+ ent_list.add_group_bis("group_two_one_user_groups", 2019,
+ [], ["one_user_group1", "one_user_group2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [nss]
+ filter_users = user2
+ filter_groups = group_two_one_user_groups
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_nss_filters(ldap_conn, sanity_nss_filter):
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001',
+ dir='/home/user1', shell='/bin/bash'),
+ dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003',
+ dir='/home/user3', shell='/bin/bash')
+ ])
+
+ # test filtered user
+ ent.assert_each_passwd_by_name(passwd_pattern)
+ with pytest.raises(KeyError):
+ pwd.getpwnam("user2")
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1002)
+
+ group_pattern = expected_list_to_name_dict([
+ dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()),
+ dict(name='group2', passwd='*', gid=2002, mem=ent.contains_only()),
+ dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()),
+ dict(name='empty_group1', passwd='*', gid=2010,
+ mem=ent.contains_only()),
+ dict(name='empty_group2', passwd='*', gid=2011,
+ mem=ent.contains_only()),
+ dict(name='two_user_group', passwd='*', gid=2012,
+ mem=ent.contains_only("user1")),
+ dict(name='group_empty_group', passwd='*', gid=2013,
+ mem=ent.contains_only()),
+ dict(name='group_two_empty_groups', passwd='*', gid=2014,
+ mem=ent.contains_only()),
+ dict(name='one_user_group1', passwd='*', gid=2015,
+ mem=ent.contains_only("user1")),
+ dict(name='one_user_group2', passwd='*', gid=2016,
+ mem=ent.contains_only()),
+ dict(name='group_one_user_group', passwd='*', gid=2017,
+ mem=ent.contains_only("user1")),
+ dict(name='group_two_user_group', passwd='*', gid=2018,
+ mem=ent.contains_only("user1")),
+ ])
+
+ # test filtered group
+ ent.assert_each_group_by_name(group_pattern)
+ with pytest.raises(KeyError):
+ grp.getgrnam("group_two_one_user_groups")
+ with pytest.raises(KeyError):
+ grp.getgrgid(2019)
+
+ # test non-existing user/group
+ with pytest.raises(KeyError):
+ pwd.getpwnam("non_existent_user")
+ with pytest.raises(KeyError):
+ pwd.getpwuid(9)
+ with pytest.raises(KeyError):
+ grp.getgrnam("non_existent_group")
+ with pytest.raises(KeyError):
+ grp.getgrgid(14)
+
+ # test initgroups - user1 is member of group_two_one_user_groups (2019)
+ # which is filtered out
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user1", 2001)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ user_with_group_ids = [2001, 2012, 2015, 2017, 2018]
+ assert sorted(gids) == sorted(user_with_group_ids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(user_with_group_ids)])
+ )
+
+
+@pytest.fixture
+def sanity_nss_filter_cached(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 2003)
+ ent_list.add_user("root", 1004, 2004)
+ ent_list.add_user("zerouid", 0, 0)
+
+ ent_list.add_group_bis("group1", 2001)
+ ent_list.add_group_bis("group2", 2002)
+ ent_list.add_group_bis("group3", 2003)
+ ent_list.add_group_bis("root", 2004)
+ ent_list.add_group_bis("zerogid", 0)
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [nss]
+ filter_users = user2
+ filter_groups = group2
+ entry_negative_timeout = 1
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_nss_filters_cached(ldap_conn, sanity_nss_filter_cached):
+ passwd_pattern = expected_list_to_name_dict([
+ dict(name='user1', passwd='*', uid=1001, gid=2001, gecos='1001',
+ dir='/home/user1', shell='/bin/bash'),
+ dict(name='user3', passwd='*', uid=1003, gid=2003, gecos='1003',
+ dir='/home/user3', shell='/bin/bash')
+ ])
+ ent.assert_each_passwd_by_name(passwd_pattern)
+
+ # test filtered user
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1002)
+ time.sleep(2)
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1002)
+
+ group_pattern = expected_list_to_name_dict([
+ dict(name='group1', passwd='*', gid=2001, mem=ent.contains_only()),
+ dict(name='group3', passwd='*', gid=2003, mem=ent.contains_only()),
+ ])
+ ent.assert_each_group_by_name(group_pattern)
+
+ # test filtered group
+ with pytest.raises(KeyError):
+ grp.getgrgid(2002)
+ time.sleep(2)
+ with pytest.raises(KeyError):
+ grp.getgrgid(2002)
+
+ # test that root is always filtered even if filter_users contains other
+ # entries. This is a regression test for upstream ticket #3460
+ res, _ = call_sssd_getpwnam("root")
+ assert res == NssReturnCode.NOTFOUND
+
+ res, _ = call_sssd_getgrnam("root")
+ assert res == NssReturnCode.NOTFOUND
+
+ res, _ = call_sssd_getpwuid(0)
+ assert res == NssReturnCode.NOTFOUND
+
+ res, _ = call_sssd_getgrgid(0)
+ assert res == NssReturnCode.NOTFOUND
+
+
+@pytest.fixture
+def mpg_setup(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 2003)
+
+ ent_list.add_group_bis("group1", 2001)
+ ent_list.add_group_bis("group2", 2002)
+ ent_list.add_group_bis("group3", 2003)
+
+ ent_list.add_group_bis("two_user_group", 2012, ["user1", "user2"])
+ ent_list.add_group_bis("one_user_group1", 2015, ["user1"])
+ ent_list.add_group_bis("one_user_group2", 2016, ["user2"])
+
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, None)
+
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [domain/LDAP]
+ auto_private_groups = True
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_ldap_auto_private_groups_direct(ldap_conn, mpg_setup):
+ """
+ Integration test for auto_private_groups
+
+ See also ticket https://github.com/SSSD/sssd/issues/2914
+ """
+ # Make sure the user's GID is taken from their uidNumber
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=1001))
+ # Make sure the private group is resolvable by name and by GID
+ ent.assert_group_by_name("user1", dict(gid=1001, mem=ent.contains_only()))
+ ent.assert_group_by_gid(1001, dict(name="user1", mem=ent.contains_only()))
+
+ # The group referenced in user's gidNumber attribute should be still
+ # visible, but it's fine that it doesn't contain the user as a member
+ # as the group is currently added during the initgroups operation only
+ ent.assert_group_by_name("group1", dict(gid=2001, mem=ent.contains_only()))
+ ent.assert_group_by_gid(2001, dict(name="group1", mem=ent.contains_only()))
+
+ # The user's secondary groups list must be correct as well
+ # Note that the original GID is listed as well -- this is correct and
+ # expected because we save the original GID in the
+ # SYSDB_PRIMARY_GROUP_GIDNUM attribute
+ user1_expected_gids = [1001, 2001, 2012, 2015]
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user1", 1001)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(user1_expected_gids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(user1_expected_gids)])
+ )
+
+ # Request user2's private group by GID without resolving the user first.
+ # This must trigger user resolution through by-GID resolution, since the
+ # GID doesn't exist on its own in LDAP
+ ent.assert_group_by_gid(1002, dict(name="user2", mem=ent.contains_only()))
+
+ # Test supplementary groups for user2 as well
+ user1_expected_gids = [1002, 2002, 2012, 2016]
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user2", 1002)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(user1_expected_gids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(user1_expected_gids)])
+ )
+
+ # Request user3's private group by name without resolving the user first
+ # This must trigger user resolution through by-name resolution, since the
+ # name doesn't exist on its own in LDAP
+ ent.assert_group_by_name("user3", dict(gid=1003, mem=ent.contains_only()))
+
+ # Remove entries and request them again to make sure they are not
+ # resolvable anymore
+ cleanup_ldap_entries(ldap_conn, None)
+
+ if subprocess.call(["sss_cache", "-GU"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ with pytest.raises(KeyError):
+ pwd.getpwnam("user1")
+ with pytest.raises(KeyError):
+ grp.getgrnam("user1")
+ with pytest.raises(KeyError):
+ grp.getgrgid(1002)
+ with pytest.raises(KeyError):
+ grp.getgrnam("user3")
+
+
+@pytest.fixture
+def mpg_setup_conflict(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 1003)
+ ent_list.add_group_bis("group1", 1001)
+ ent_list.add_group_bis("group2", 1002)
+ ent_list.add_group_bis("group3", 1003)
+ ent_list.add_group_bis("supp_group", 2015, ["user3"])
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [domain/LDAP]
+ auto_private_groups = True
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_ldap_auto_private_groups_conflict(ldap_conn, mpg_setup_conflict):
+ """
+ Make sure that conflicts between groups that are auto-created with the
+ help of the auto_private_groups option and between 'real' LDAP groups
+ are handled in a predictable manner.
+ """
+ # Make sure the user's GID is taken from their uidNumber
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=1001))
+ # Make sure the private group is resolvable by name and by GID
+ ent.assert_group_by_name("user1", dict(gid=1001, mem=ent.contains_only()))
+ ent.assert_group_by_gid(1001, dict(name="user1", mem=ent.contains_only()))
+
+ # Let's request the group with the same ID as user2's private group
+ # The request should match the 'real' group
+ ent.assert_group_by_gid(1002, dict(name="group2", mem=ent.contains_only()))
+ # But because of the GID conflict, the user cannot be resolved
+ with pytest.raises(KeyError):
+ pwd.getpwnam("user2")
+
+ # This user's GID is the same as the UID in this entry. The most important
+ # thing here is that the supplementary groups are correct and the GID
+ # resolves to the private group (as long as the user was requested first)
+ user3_expected_gids = [1003, 2015]
+ ent.assert_passwd_by_name("user3", dict(name="user3", uid=1003, gid=1003))
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user3", 1003)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(user3_expected_gids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(user3_expected_gids)])
+ )
+ # Make sure the private group is resolvable by name and by GID
+ ent.assert_group_by_gid(1003, dict(name="user3", mem=ent.contains_only()))
+ ent.assert_group_by_name("user3", dict(gid=1003, mem=ent.contains_only()))
+
+
+@pytest.fixture
+def mpg_setup_no_gid(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+
+ ent_list.add_group_bis("group1", 2001)
+ ent_list.add_group_bis("one_user_group1", 2015, ["user1"])
+
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, None)
+
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [domain/LDAP]
+ auto_private_groups = True
+ ldap_user_gid_number = no_such_attribute
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_ldap_auto_private_groups_direct_no_gid(ldap_conn, mpg_setup_no_gid):
+ """
+ Integration test for auto_private_groups - test that even a user with
+ no GID assigned at all can be resolved including their autogenerated
+ primary group.
+
+ See also ticket https://github.com/SSSD/sssd/issues/2914
+ """
+ # Make sure the user's GID is taken from their uidNumber
+ ent.assert_passwd_by_name("user1", dict(name="user1", uid=1001, gid=1001))
+ # Make sure the private group is resolvable by name and by GID
+ ent.assert_group_by_name("user1", dict(gid=1001, mem=ent.contains_only()))
+ ent.assert_group_by_gid(1001, dict(name="user1", mem=ent.contains_only()))
+
+ # The group referenced in user's gidNumber attribute should be still
+ # visible, but shouldn't have any relation to the user
+ ent.assert_group_by_name("group1", dict(gid=2001, mem=ent.contains_only()))
+ ent.assert_group_by_gid(2001, dict(name="group1", mem=ent.contains_only()))
+
+ # The user's secondary groups list must be correct as well. This time only
+ # the generated group and the explicit secondary group are added, since
+ # there is no original GID
+ user1_expected_gids = [1001, 2015]
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user1", 1001)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(user1_expected_gids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(user1_expected_gids)])
+ )
+
+
+@pytest.fixture
+def mpg_setup_hybrid(request, ldap_conn):
+ """
+ This setup creates two users - one with a GID that corresponds to
+ a group and another with GID that does not.
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ ent_list.add_user("user_with_group", 1001, 2001)
+ ent_list.add_group_bis("user_with_group_pvt", 2001)
+ ent_list.add_group_bis("with_group_group1", 10010, ["user_with_group"])
+ ent_list.add_group_bis("with_group_group2", 10011, ["user_with_group"])
+
+ ent_list.add_user("user_with_no_group", 1002, 1002)
+ # Note - there is no group for the GID 1002
+ ent_list.add_group_bis("no_group_group1", 10020, ["user_with_no_group"])
+ ent_list.add_group_bis("no_group_group2", 10021, ["user_with_no_group"])
+
+ # This user has their gid different from the UID, but there is
+ # no group with gid 2003
+ ent_list.add_user("user_with_unresolvable_gid", 1003, 2003)
+ ent_list.add_group_bis("unresolvable_group1",
+ 10030,
+ ["user_with_unresolvable_gid"])
+ ent_list.add_group_bis("unresolvable_group2",
+ 10031,
+ ["user_with_unresolvable_gid"])
+
+ # This user's autogenerated private group should be shadowed
+ # by the real one
+ ent_list.add_user("user_with_real_group", 1004, 1004)
+ ent_list.add_user("user_in_pvt_group", 1005, 1005)
+ ent_list.add_group_bis("user_with_real_group_pvt",
+ 1004,
+ ['user_in_pvt_group'])
+ ent_list.add_group_bis("with_real_group_group1",
+ 10040,
+ ["user_with_real_group"])
+ ent_list.add_group_bis("with_real_group_group2",
+ 10041,
+ ["user_with_real_group"])
+
+ # Test shadowing again, but this time with the same name
+ ent_list.add_user("u_g_same_name", 1006, 1006)
+ ent_list.add_group_bis("u_g_same_name",
+ 1006,
+ ['user_in_pvt_group'])
+ ent_list.add_group_bis("u_g_same_g1",
+ 10060,
+ ["u_g_same_name"])
+ ent_list.add_group_bis("u_g_same_g2",
+ 10061,
+ ["u_g_same_name"])
+
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, None)
+
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [domain/LDAP]
+ auto_private_groups = hybrid
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_ldap_auto_private_groups_hybrid_direct(ldap_conn, mpg_setup_hybrid):
+ """
+ Integration test for auto_private_groups=hybrid. This test checks the
+ resolution of the users and their groups.
+
+ See also ticket https://github.com/SSSD/sssd/issues/2914
+ """
+ # Make sure the user's GID is taken from their gidNumber, if available
+ ent.assert_passwd_by_name("user_with_group",
+ dict(name="user_with_group", uid=1001, gid=2001))
+
+ # The user's secondary groups list must be correct as well and include
+ # the primary gid, too
+ user_with_group_ids = [2001, 10010, 10011]
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_group", 2001)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(user_with_group_ids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(user_with_group_ids)])
+ )
+
+ # On the other hand, if the gidNumber is the same as UID, SSSD should
+ # just autogenerate the private group on its own
+ ent.assert_passwd_by_name("user_with_no_group",
+ dict(name="user_with_no_group",
+ uid=1002, gid=1002))
+
+ # The user's secondary groups list must be correct as well. Since there was
+ # no original GID, it is not added to the list
+ user_without_group_ids = [1002, 10020, 10021]
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_no_group",
+ 1002)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(user_without_group_ids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(user_without_group_ids)])
+ )
+
+ ent.assert_passwd_by_name("user_with_unresolvable_gid",
+ dict(name="user_with_unresolvable_gid",
+ uid=1003, gid=2003))
+ unresolvable_group_ids = [2003, 10030, 10031]
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_unresolvable_gid", 2003)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(unresolvable_group_ids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(unresolvable_group_ids)])
+ )
+
+ ent.assert_passwd_by_name("user_with_real_group",
+ dict(name="user_with_real_group",
+ uid=1004, gid=1004))
+ with_real_group_ids = [1004, 10040, 10041]
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_real_group", 1004)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(with_real_group_ids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(with_real_group_ids)])
+ )
+
+
+def test_ldap_auto_private_groups_hybrid_priv_group_byname(ldap_conn,
+ mpg_setup_hybrid):
+ """
+ Integration test for auto_private_groups=hybrid. This test checks the
+ resolution of user private groups by name.
+
+ See also ticket https://github.com/SSSD/sssd/issues/2914
+ """
+ # gidNumber is resolvable by name..
+ ent.assert_group_by_name("user_with_group_pvt",
+ dict(gid=2001,
+ mem=ent.contains_only()))
+
+ # ..but since this user /has/ a gidNumber set, their autogenerated group
+ # should not be resolvable
+ with pytest.raises(KeyError):
+ grp.getgrnam("user_with_group")
+
+ # Finally, the autogenerated group for the user with
+ # uidNumber==gidNumber must be resolvable
+ ent.assert_group_by_name("user_with_no_group",
+ dict(gid=1002,
+ mem=ent.contains_only()))
+
+ # A gid that is different from an UID must not resolve to a private
+ # group even if the private group does not exist
+ with pytest.raises(KeyError):
+ grp.getgrnam("user_with_unresolvable_gid")
+
+ # If there is a user with the same UID and GID but there is a real
+ # group corresponding to the primary GID, the real group should take
+ # precedence and the automatic group should not be resolvable
+ ent.assert_group_by_name("user_with_real_group_pvt",
+ dict(gid=1004,
+ mem=ent.contains_only('user_in_pvt_group',)))
+
+ # getgrnam should not return
+ with pytest.raises(KeyError):
+ grp.getgrnam("user_with_real_group")
+
+
+def test_ldap_auto_private_groups_hybrid_priv_group_byid(ldap_conn,
+ mpg_setup_hybrid):
+ """
+ Integration test for auto_private_groups=hybrid. This test checks the
+ resolution of user private groups by name.
+
+ See also ticket https://github.com/SSSD/sssd/issues/2914
+ """
+ # Make sure the private group of user who has this group set in their
+ # gidNumber is resolvable by ID
+ ent.assert_group_by_gid(2001,
+ dict(name="user_with_group_pvt",
+ mem=ent.contains_only()))
+
+ # ..but since this user /has/ a gidNumber set different from the uidNumber,
+ # their autogenerated group
+ # should not be resolvable
+ with pytest.raises(KeyError):
+ grp.getgrgid(1001)
+
+ # Finally, the autogenerated group for the user with
+ # uidNumber==gidNumber must be resolvable
+ ent.assert_group_by_gid(1002,
+ dict(name="user_with_no_group",
+ mem=ent.contains_only()))
+
+ # A gid that is different from an UID must not resolve to a private
+ # group even if the private group does not exist
+ with pytest.raises(KeyError):
+ grp.getgrgid(2003)
+
+ # Conversely, a GID that corresponds to a group must not resolve to
+ # the autogenerated group (IOW, the autogenerated group should not
+ # shadow the real one
+ ent.assert_group_by_gid(1004,
+ dict(name="user_with_real_group_pvt",
+ mem=ent.contains_only('user_in_pvt_group')))
+
+
+def test_ldap_auto_private_groups_hybrid_name_gid_identical(ldap_conn,
+ mpg_setup_hybrid):
+ """
+ See also ticket https://github.com/SSSD/sssd/issues/2914
+ """
+ ent.assert_passwd_by_name("u_g_same_name",
+ dict(name="u_g_same_name",
+ uid=1006, gid=1006))
+ user_without_group_ids = [1006, 10060, 10061]
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("u_g_same_name",
+ 1006)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(user_without_group_ids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(user_without_group_ids)])
+ )
+ ent.assert_group_by_gid(1006,
+ dict(name="u_g_same_name",
+ mem=ent.contains_only('user_in_pvt_group')))
+
+
+def test_ldap_auto_private_groups_hybrid_initgr(ldap_conn, mpg_setup_hybrid):
+ """
+ See also ticket https://github.com/SSSD/sssd/issues/2914
+ """
+ user_without_group_ids = [1004, 10040, 10041]
+ (res, errno, gids) = sssd_id.call_sssd_initgroups("user_with_real_group",
+ 1004)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ assert sorted(gids) == sorted(user_without_group_ids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(user_without_group_ids)])
+ )
+
+ ent.assert_group_by_gid(1004,
+ dict(name="user_with_real_group_pvt",
+ mem=ent.contains_only('user_in_pvt_group')))
+
+
+def rename_setup_no_cleanup(request, ldap_conn, cleanup_ent=None):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_group_bis("user1_private", 2001)
+
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_group_bis("user2_private", 2002)
+
+ ent_list.add_group_bis("group1", 2015, ["user1", "user2"])
+
+ if cleanup_ent is None:
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ else:
+ # Since the entries were renamed, we need to clean up
+ # the renamed entries..
+ create_ldap_fixture(request, ldap_conn, ent_list, cleanup=False)
+ create_ldap_cleanup(request, ldap_conn, None)
+
+
+@pytest.fixture
+def rename_setup_cleanup(request, ldap_conn):
+ cleanup_ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ cleanup_ent_list.add_user("user1", 1001, 2001)
+ cleanup_ent_list.add_group_bis("new_user1_private", 2001)
+
+ cleanup_ent_list.add_user("user2", 1002, 2002)
+ cleanup_ent_list.add_group_bis("new_user2_private", 2002)
+
+ cleanup_ent_list.add_group_bis("new_group1", 2015, ["user1", "user2"])
+
+ rename_setup_no_cleanup(request, ldap_conn, cleanup_ent_list)
+
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def rename_setup_with_name(request, ldap_conn):
+ rename_setup_no_cleanup(request, ldap_conn)
+
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS) + \
+ unindent("""
+ [nss]
+ [domain/LDAP]
+ ldap_group_name = name
+ timeout = 3000
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_rename_incomplete_group_same_dn(ldap_conn, rename_setup_with_name):
+ """
+ Test that if a group's name attribute changes, but the DN stays the same,
+ the incomplete group object will be renamed.
+
+ Because the RDN attribute must be present in the entry, we add another
+ attribute "name" that is purposefully different from the CN and make
+ sure the group names are reflected in name
+
+ Regression test for https://github.com/SSSD/sssd/issues/4315
+ """
+ pvt_dn1 = 'cn=user1_private,ou=Groups,' + ldap_conn.ds_inst.base_dn
+ pvt_dn2 = 'cn=user2_private,ou=Groups,' + ldap_conn.ds_inst.base_dn
+ group1_dn = 'cn=group1,ou=Groups,' + ldap_conn.ds_inst.base_dn
+
+ # Add the name we want for both private and secondary group
+ old = {'name': []}
+ new = {'name': [b"user1_group1"]}
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(group1_dn, ldif)
+
+ new = {'name': [b"pvt_user1"]}
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(pvt_dn1, ldif)
+
+ new = {'name': [b"pvt_user2"]}
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(pvt_dn2, ldif)
+
+ # Make sure the old name shows up in the id output
+ (res, errno, grp_list) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+
+ assert sorted(grp_list) == sorted(["pvt_user1", "user1_group1"])
+
+ # Rename the group by changing the cn attribute, but keep the DN the same
+ old = {'name': [b"user1_group1"]}
+ new = {'name': [b"new_user1_group1"]}
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(group1_dn, ldif)
+
+ (res, errno, grp_list) = sssd_id.get_user_groups("user2")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user2, %d" % errno
+
+ assert sorted(grp_list) == sorted(["pvt_user2", "new_user1_group1"])
+
+ (res, errno, grp_list) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+
+ assert sorted(grp_list) == sorted(["pvt_user1", "new_user1_group1"])
+
+
+def test_rename_incomplete_group_rdn_changed(ldap_conn, rename_setup_cleanup):
+ """
+ If a group is renamed and also some attributes changed and gid remains the
+ same then existing group in the cache is overridden with the new attributes
+ and name.
+ """
+ pvt_dn = 'cn=user1_private,ou=Groups,' + ldap_conn.ds_inst.base_dn
+ group1_dn = 'cn=group1,ou=Groups,' + ldap_conn.ds_inst.base_dn
+
+ # Make sure the old name shows up in the id output
+ (res, errno, grp_list) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+
+ assert sorted(grp_list) == sorted(["user1_private", "group1"])
+
+ # Rename the groups, changing the RDN
+ ldap_conn.rename_s(group1_dn, "cn=new_group1")
+ ldap_conn.rename_s(pvt_dn, "cn=new_user1_private")
+
+ (res, errno, grp_list) = sssd_id.get_user_groups("user2")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user2, %d" % errno
+
+ # The initgroups succeeds, but because saving the new group fails,
+ # SSSD will revert to the cache contents and return what's in the cache
+ assert sorted(grp_list) == sorted(["user2_private", "new_group1"])
+
+
+@pytest.fixture
+def find_local_user_and_group():
+ f = open("/etc/passwd")
+ for line in f:
+ passwd_user = line.split(':')
+ passwd_user[2] = int(passwd_user[2])
+ if passwd_user[2] != 0:
+ break
+ f.close()
+ assert passwd_user[2] != 0
+
+ f = open("/etc/group")
+ for line in f:
+ passwd_group = line.split(':')
+ passwd_group[2] = int(passwd_group[2])
+ if passwd_group[2] != 0:
+ break
+ f.close()
+ assert passwd_group[2] != 0
+
+ return (passwd_user, passwd_group)
+
+
+@pytest.fixture
+def user_and_group_rfc2307_lcl(find_local_user_and_group,
+ user_and_group_rfc2307):
+ return find_local_user_and_group
+
+
+def test_local_negative_timeout_enabled_by_default(ldap_conn,
+ user_and_group_rfc2307_lcl):
+ """
+ Test that with the default local_negative_timeout value, a user who can't
+ be resolved through SSSD but can be resolved in LDAP is negatively cached
+ """
+ # sanity check - try resolving an LDAP user
+ ent.assert_passwd_by_name("user", dict(name="user", uid=1001, gid=2000))
+
+ passwd_user, passwd_group = user_and_group_rfc2307_lcl
+
+ # resolve a user who is not in LDAP, but exists locally
+ res, _ = call_sssd_getpwnam(passwd_user[0])
+ assert res == NssReturnCode.NOTFOUND
+ # Do the same by UID
+ res, _ = call_sssd_getpwuid(passwd_user[2])
+ assert res == NssReturnCode.NOTFOUND
+
+ # Do the same for a group both by name and by ID
+ res, _ = call_sssd_getgrnam(passwd_group[0])
+ assert res == NssReturnCode.NOTFOUND
+ res, _ = call_sssd_getgrgid(passwd_group[2])
+ assert res == NssReturnCode.NOTFOUND
+
+ # add the user and the group to LDAP
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user(passwd_user[0], passwd_user[2], 2000)
+ ent_list.add_group(passwd_group[0], passwd_group[2])
+ create_ldap_entries(ldap_conn, ent_list)
+
+ # Make sure the negative cache would expire if global timeout was used
+ time.sleep(2)
+
+ # The user is now negatively cached and can't be resolved by either
+ # name or UID
+ res, _ = call_sssd_getpwnam(passwd_group[0])
+ assert res == NssReturnCode.NOTFOUND
+ res, _ = call_sssd_getpwuid(passwd_group[2])
+ assert res == NssReturnCode.NOTFOUND
+
+ res, _ = call_sssd_getgrnam(passwd_group[0])
+ assert res == NssReturnCode.NOTFOUND
+ res, _ = call_sssd_getgrgid(passwd_group[2])
+ assert res == NssReturnCode.NOTFOUND
+
+ cleanup_ldap_entries(ldap_conn, ent_list)
+
+
+@pytest.fixture
+def usr_and_grp_rfc2307_no_local_ncache(request, find_local_user_and_group,
+ ldap_conn):
+ """
+ Create an RFC2307 directory fixture with interactive SSSD conf,
+ one user and one group but with the local negative timeout
+ disabled
+ """
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user", 1001, 2000)
+ ent_list.add_group("group", 2001)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_interactive_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""
+ [nss]
+ local_negative_timeout = 0
+ """)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return find_local_user_and_group
+
+
+def test_local_negative_timeout_disabled(ldap_conn,
+ usr_and_grp_rfc2307_no_local_ncache):
+ """
+ Test that with the local negative cache disabled, a user who is in both
+ LDAP and files can be resolved once the negative cache expires
+ """
+ # sanity check - try resolving an LDAP user
+ ent.assert_passwd_by_name("user", dict(name="user", uid=1001, gid=2000))
+
+ passwd_user, passwd_group = usr_and_grp_rfc2307_no_local_ncache
+
+ # resolve a user who is not in LDAP, but exists locally
+ res, _ = call_sssd_getpwnam(passwd_user[0])
+ assert res == NssReturnCode.NOTFOUND
+ # Do the same by UID
+ res, _ = call_sssd_getpwuid(passwd_user[2])
+ assert res == NssReturnCode.NOTFOUND
+
+ # Do the same for a group both by name and by ID
+ res, _ = call_sssd_getgrnam(passwd_group[0])
+ assert res == NssReturnCode.NOTFOUND
+ res, _ = call_sssd_getgrgid(passwd_group[2])
+ assert res == NssReturnCode.NOTFOUND
+
+ # add the user and the group to LDAP
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user(passwd_user[0], passwd_user[2], 2000)
+ ent_list.add_group(passwd_group[0], passwd_group[2])
+ create_ldap_entries(ldap_conn, ent_list)
+
+ # Make sure the negative cache expired
+ time.sleep(2)
+
+ # The user can now be resolved
+ res, _ = call_sssd_getpwnam(passwd_user[0])
+ assert res == NssReturnCode.SUCCESS
+ # Do the same by UID
+ res, _ = call_sssd_getpwuid(passwd_user[2])
+ assert res == NssReturnCode.SUCCESS
+
+ res, _ = call_sssd_getgrnam(passwd_group[0])
+ assert res == NssReturnCode.SUCCESS
+ res, _ = call_sssd_getgrgid(passwd_group[2])
+ assert res == NssReturnCode.SUCCESS
+
+ cleanup_ldap_entries(ldap_conn, ent_list)
+
+
+def users_with_email_setup(request, ldap_conn, cache_first):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001, mail="user1.email@LDAP")
+
+ ent_list.add_user("emailuser", 1002, 2002)
+ ent_list.add_user("emailuser2", 1003, 2003, mail="emailuser@LDAP")
+
+ ent_list.add_user("userx", 1004, 2004, mail="userxy@LDAP")
+ ent_list.add_user("usery", 1005, 2005, mail="userxy@LDAP")
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+
+ conf += unindent("""
+ [nss]
+ cache_first = {0}
+ """).format(str(cache_first))
+
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+@pytest.mark.parametrize('cache_first', [True, False])
+def test_lookup_by_email(request, ldap_conn, cache_first):
+ """
+ Test the simple case of looking up a user by e-mail
+ """
+ users_with_email_setup(request, ldap_conn, cache_first)
+ ent.assert_passwd_by_name("user1.email@LDAP",
+ dict(name="user1", uid=1001, gid=2001))
+
+
+@pytest.mark.parametrize('cache_first', [True, False])
+def test_conflicting_mail_addresses_and_fqdn(request, ldap_conn, cache_first):
+ """
+ Test that we handle the case where one user's mail address is the
+ same as another user's FQDN
+
+ This is a regression test for https://github.com/SSSD/sssd/issues/4630
+ """
+ users_with_email_setup(request, ldap_conn, cache_first)
+ # With #3607 unfixed, these two lookups would prime the cache with
+ # nameAlias: emailuser@LDAP for both entries..
+ ent.assert_passwd_by_name("emailuser@LDAP",
+ dict(name="emailuser", uid=1002, gid=2002))
+ ent.assert_passwd_by_name("emailuser2@LDAP",
+ dict(name="emailuser2", uid=1003, gid=2003))
+
+ # ..and subsequently, emailuser would not be returned because the cache
+ # lookup would have had returned two entries which is an error
+ ent.assert_passwd_by_name("emailuser@LDAP",
+ dict(name="emailuser", uid=1002, gid=2002))
+ ent.assert_passwd_by_name("emailuser2@LDAP",
+ dict(name="emailuser2", uid=1003, gid=2003))
+
+
+@pytest.mark.parametrize('cache_first', [True, False])
+def test_conflicting_mail_addresses(request, ldap_conn, cache_first):
+ """
+ Negative test: looking up a user by e-mail which belongs to more than
+ one account fails in the back end.
+ """
+ users_with_email_setup(request, ldap_conn, cache_first)
+ with pytest.raises(KeyError):
+ pwd.getpwnam("userxy@LDAP")
+
+ # However resolving the users on their own must work
+ ent.assert_passwd_by_name("userx", dict(name="userx", uid=1004, gid=2004))
+ ent.assert_passwd_by_name("usery", dict(name="usery", uid=1005, gid=2005))
diff --git a/src/tests/intg/test_memory_cache.py b/src/tests/intg/test_memory_cache.py
new file mode 100644
index 0000000..b30c902
--- /dev/null
+++ b/src/tests/intg/test_memory_cache.py
@@ -0,0 +1,1215 @@
+#
+# LDAP integration test
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Lukas Slebodnik <lslebodn@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import stat
+import ent
+import grp
+import pwd
+import config
+import random
+import signal
+import string
+import struct
+import subprocess
+import time
+import pytest
+import pysss_murmur
+
+import ds_openldap
+import ldap_ent
+import sssd_id
+from util import unindent
+
+LDAP_BASE_DN = "dc=example,dc=com"
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123")
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(lambda: ds_inst.teardown())
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(lambda: ldap_conn.unbind_s())
+ return ldap_conn
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list):
+ """Add LDAP entries and add teardown for removing them"""
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+ def teardown():
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+ request.addfinalizer(teardown)
+
+
+def create_conf_fixture(request, contents):
+ """Generate sssd.conf and add teardown for removing it"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+ request.addfinalizer(lambda: os.unlink(config.CONF_PATH))
+
+
+def stop_sssd():
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+
+
+def create_sssd_fixture(request):
+ """Start sssd and add teardown for stopping it and removing state"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+ def teardown():
+ try:
+ stop_sssd()
+ except Exception:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+ # force sss_client libs to realize mem-cache files were deleted
+ try:
+ sssd_id.call_sssd_initgroups("user1", 2001)
+ except Exception:
+ pass
+ try:
+ grp.getgrnam("group1")
+ except Exception:
+ pass
+ try:
+ pwd.getpwnam("user1")
+ except Exception:
+ pass
+ request.addfinalizer(teardown)
+
+
+def load_data_to_ldap(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2002)
+ ent_list.add_user("user3", 1003, 2003)
+ ent_list.add_user("user11", 1011, 2001)
+ ent_list.add_user("user12", 1012, 2002)
+ ent_list.add_user("user13", 1013, 2003)
+ ent_list.add_user("user21", 1021, 2001)
+ ent_list.add_user("user22", 1022, 2002)
+ ent_list.add_user("user23", 1023, 2003)
+
+ ent_list.add_group("group1", 2001, ["user1", "user11", "user21"])
+ ent_list.add_group("group2", 2002, ["user2", "user12", "user22"])
+ ent_list.add_group("group3", 2003, ["user3", "user13", "user23"])
+
+ ent_list.add_group("group0x", 2000, ["user1", "user2", "user3"])
+ ent_list.add_group("group1x", 2010, ["user11", "user12", "user13"])
+ ent_list.add_group("group2x", 2020, ["user21", "user22", "user23"])
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+
+@pytest.fixture
+def disable_memcache_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+ memcache_size_group = 0
+ memcache_size_passwd = 0
+ memcache_size_initgroups = 0
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def disable_pwd_mc_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+ memcache_size_passwd = 0
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def disable_grp_mc_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+ memcache_size_group = 0
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def disable_initgr_mc_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+ memcache_size_initgroups = 0
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def sanity_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def fqname_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ use_fully_qualified_names = true
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def fqname_case_insensitive_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ use_fully_qualified_names = true
+ case_sensitive = false
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def zero_timeout_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+ memcache_timeout = 0
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.mark.converted('test_id.py', 'test_id__getpwuid')
+@pytest.mark.converted('test_id.py', 'test_id__getpwnam')
+def test_getpwnam(ldap_conn, sanity_rfc2307):
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ 'user2',
+ dict(name='user2', passwd='*', uid=1002, gid=2002,
+ gecos='1002', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1002,
+ dict(name='user2', passwd='*', uid=1002, gid=2002,
+ gecos='1002', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ 'user3',
+ dict(name='user3', passwd='*', uid=1003, gid=2003,
+ gecos='1003', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1003,
+ dict(name='user3', passwd='*', uid=1003, gid=2003,
+ gecos='1003', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ 'user11',
+ dict(name='user11', passwd='*', uid=1011, gid=2001,
+ gecos='1011', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1011,
+ dict(name='user11', passwd='*', uid=1011, gid=2001,
+ gecos='1011', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ 'user12',
+ dict(name='user12', passwd='*', uid=1012, gid=2002,
+ gecos='1012', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1012,
+ dict(name='user12', passwd='*', uid=1012, gid=2002,
+ gecos='1012', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ 'user13',
+ dict(name='user13', passwd='*', uid=1013, gid=2003,
+ gecos='1013', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1013,
+ dict(name='user13', passwd='*', uid=1013, gid=2003,
+ gecos='1013', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ 'user21',
+ dict(name='user21', passwd='*', uid=1021, gid=2001,
+ gecos='1021', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1021,
+ dict(name='user21', passwd='*', uid=1021, gid=2001,
+ gecos='1021', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ 'user22',
+ dict(name='user22', passwd='*', uid=1022, gid=2002,
+ gecos='1022', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1022,
+ dict(name='user22', passwd='*', uid=1022, gid=2002,
+ gecos='1022', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ 'user23',
+ dict(name='user23', passwd='*', uid=1023, gid=2003,
+ gecos='1023', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1023,
+ dict(name='user23', passwd='*', uid=1023, gid=2003,
+ gecos='1023', shell='/bin/bash'))
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__getpwnam')
+def test_getpwnam_with_mc(ldap_conn, sanity_rfc2307):
+ test_getpwnam(ldap_conn, sanity_rfc2307)
+ stop_sssd()
+ test_getpwnam(ldap_conn, sanity_rfc2307)
+
+
+@pytest.mark.converted('test_id.py', 'test_id__getgrgid')
+@pytest.mark.converted('test_id.py', 'test_id__getgrnam')
+def test_getgrnam_simple(ldap_conn, sanity_rfc2307):
+ ent.assert_group_by_name("group1", dict(name="group1", gid=2001))
+ ent.assert_group_by_gid(2001, dict(name="group1", gid=2001))
+
+ ent.assert_group_by_name("group2", dict(name="group2", gid=2002))
+ ent.assert_group_by_gid(2002, dict(name="group2", gid=2002))
+
+ ent.assert_group_by_name("group3", dict(name="group3", gid=2003))
+ ent.assert_group_by_gid(2003, dict(name="group3", gid=2003))
+
+ ent.assert_group_by_name("group0x", dict(name="group0x", gid=2000))
+ ent.assert_group_by_gid(2000, dict(name="group0x", gid=2000))
+
+ ent.assert_group_by_name("group1x", dict(name="group1x", gid=2010))
+ ent.assert_group_by_gid(2010, dict(name="group1x", gid=2010))
+
+ ent.assert_group_by_name("group2x", dict(name="group2x", gid=2020))
+ ent.assert_group_by_gid(2020, dict(name="group2x", gid=2020))
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__getgrnam')
+def test_getgrnam_simple_with_mc(ldap_conn, sanity_rfc2307):
+ test_getgrnam_simple(ldap_conn, sanity_rfc2307)
+ stop_sssd()
+ test_getgrnam_simple(ldap_conn, sanity_rfc2307)
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_passwd_getgrnam')
+def test_getgrnam_simple_disabled_pwd_mc(ldap_conn, disable_pwd_mc_rfc2307):
+ test_getgrnam_simple(ldap_conn, disable_pwd_mc_rfc2307)
+ stop_sssd()
+ test_getgrnam_simple(ldap_conn, disable_pwd_mc_rfc2307)
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_intitgroups_getgrnam')
+def test_getgrnam_simple_disabled_intitgr_mc(ldap_conn,
+ disable_initgr_mc_rfc2307):
+ test_getgrnam_simple(ldap_conn, disable_initgr_mc_rfc2307)
+ stop_sssd()
+ test_getgrnam_simple(ldap_conn, disable_initgr_mc_rfc2307)
+
+
+@pytest.mark.converted('test_id.py', 'test_id__membership_by_group_id')
+@pytest.mark.converted('test_id.py', 'test_id__membership_by_group_name')
+def test_getgrnam_membership(ldap_conn, sanity_rfc2307):
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+ ent.assert_group_by_gid(
+ 2001,
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+
+ ent.assert_group_by_name(
+ "group2",
+ dict(mem=ent.contains_only("user2", "user12", "user22")))
+ ent.assert_group_by_gid(
+ 2002,
+ dict(mem=ent.contains_only("user2", "user12", "user22")))
+
+ ent.assert_group_by_name(
+ "group3",
+ dict(mem=ent.contains_only("user3", "user13", "user23")))
+ ent.assert_group_by_gid(
+ 2003,
+ dict(mem=ent.contains_only("user3", "user13", "user23")))
+
+ ent.assert_group_by_name(
+ "group0x",
+ dict(mem=ent.contains_only("user1", "user2", "user3")))
+ ent.assert_group_by_gid(
+ 2000,
+ dict(mem=ent.contains_only("user1", "user2", "user3")))
+
+ ent.assert_group_by_name(
+ "group1x",
+ dict(mem=ent.contains_only("user11", "user12", "user13")))
+ ent.assert_group_by_gid(
+ 2010,
+ dict(mem=ent.contains_only("user11", "user12", "user13")))
+
+ ent.assert_group_by_name(
+ "group2x",
+ dict(mem=ent.contains_only("user21", "user22", "user23")))
+ ent.assert_group_by_gid(
+ 2020,
+ dict(mem=ent.contains_only("user21", "user22", "user23")))
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__membership_by_group_id')
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__membership_by_group_name')
+def test_getgrnam_membership_with_mc(ldap_conn, sanity_rfc2307):
+ test_getgrnam_membership(ldap_conn, sanity_rfc2307)
+ stop_sssd()
+ test_getgrnam_membership(ldap_conn, sanity_rfc2307)
+
+
+def assert_user_gids_equal(user, expected_gids):
+ (res, errno, gids) = sssd_id.get_user_gids(user)
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user %s, %d" % (user, errno)
+
+ assert sorted(gids) == sorted(expected_gids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(expected_gids)])
+ )
+
+
+@pytest.mark.converted('test_id.py', 'test_id__initgroups')
+def test_initgroups(ldap_conn, sanity_rfc2307):
+ assert_user_gids_equal('user1', [2000, 2001])
+ assert_user_gids_equal('user2', [2000, 2002])
+ assert_user_gids_equal('user3', [2000, 2003])
+
+ assert_user_gids_equal('user11', [2010, 2001])
+ assert_user_gids_equal('user12', [2010, 2002])
+ assert_user_gids_equal('user13', [2010, 2003])
+
+ assert_user_gids_equal('user21', [2020, 2001])
+ assert_user_gids_equal('user22', [2020, 2002])
+ assert_user_gids_equal('user23', [2020, 2003])
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__user_gids')
+def test_initgroups_with_mc(ldap_conn, sanity_rfc2307):
+ test_initgroups(ldap_conn, sanity_rfc2307)
+ stop_sssd()
+ test_initgroups(ldap_conn, sanity_rfc2307)
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__getpwnam_fully_qualified_names')
+@pytest.mark.converted('test_id.py', 'test_id__getpwnam_fully_qualified_names')
+def test_initgroups_fqname_with_mc(ldap_conn, fqname_rfc2307):
+ assert_user_gids_equal('user1@LDAP', [2000, 2001])
+ stop_sssd()
+ assert_user_gids_equal('user1@LDAP', [2000, 2001])
+
+
+def assert_initgroups_equal(user, primary_gid, expected_gids):
+ (res, errno, gids) = sssd_id.call_sssd_initgroups(user, primary_gid)
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user %s, %d" % (user, errno)
+
+ assert sorted(gids) == sorted(expected_gids), \
+ "result: %s\n expected %s" % (
+ ", ".join(["%s" % s for s in sorted(gids)]),
+ ", ".join(["%s" % s for s in sorted(expected_gids)])
+ )
+
+
+def assert_stored_last_initgroups(user1_case1, user1_case2, user1_case_last,
+ primary_gid, expected_gids):
+
+ assert_initgroups_equal(user1_case1, primary_gid, expected_gids)
+ assert_initgroups_equal(user1_case2, primary_gid, expected_gids)
+ assert_initgroups_equal(user1_case_last, primary_gid, expected_gids)
+ stop_sssd()
+
+ user = user1_case1
+ (res, errno, _) = sssd_id.call_sssd_initgroups(user, primary_gid)
+ assert res == sssd_id.NssReturnCode.UNAVAIL, \
+ "Initgroups for user should fail user %s, %d, %d" % (user, res, errno)
+
+ user = user1_case2
+ (res, errno, _) = sssd_id.call_sssd_initgroups(user, primary_gid)
+ assert res == sssd_id.NssReturnCode.UNAVAIL, \
+ "Initgroups for user should fail user %s, %d, %d" % (user, res, errno)
+
+ # Just last invocation of initgroups should PASS
+ # Otherwise, we would not be able to invalidate it
+ assert_initgroups_equal(user1_case_last, primary_gid, expected_gids)
+
+
+@pytest.mark.converted('test_id.py', 'test_id__fq_names_case_insensitive')
+@pytest.mark.converted('test_id.py', 'test_id__case_insensitive')
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__fq_names_case_insensitive')
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__case_insensitive')
+def test_initgroups_case_insensitive_with_mc1(ldap_conn,
+ fqname_case_insensitive_rfc2307):
+ user1_case1 = 'User1@LDAP'
+ user1_case2 = 'uSer1@LDAP'
+ user1_case_last = 'usEr1@LDAP'
+ primary_gid = 2001
+ expected_gids = [2000, 2001]
+
+ assert_stored_last_initgroups(user1_case1, user1_case2, user1_case_last,
+ primary_gid, expected_gids)
+
+
+@pytest.mark.converted('test_id.py', 'test_id__fq_names_case_insensitive')
+@pytest.mark.converted('test_id.py', 'test_id__case_insensitive')
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__fq_names_case_insensitive')
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__case_insensitive')
+def test_initgroups_case_insensitive_with_mc2(ldap_conn,
+ fqname_case_insensitive_rfc2307):
+ user1_case1 = 'usEr1@LDAP'
+ user1_case2 = 'User1@LDAP'
+ user1_case_last = 'uSer1@LDAP'
+ primary_gid = 2001
+ expected_gids = [2000, 2001]
+
+ assert_stored_last_initgroups(user1_case1, user1_case2, user1_case_last,
+ primary_gid, expected_gids)
+
+
+@pytest.mark.converted('test_id.py', 'test_id__fq_names_case_insensitive')
+@pytest.mark.converted('test_id.py', 'test_id__case_insensitive')
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__fq_names_case_insensitive')
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__case_insensitive')
+def test_initgroups_case_insensitive_with_mc3(ldap_conn,
+ fqname_case_insensitive_rfc2307):
+ user1_case1 = 'uSer1@LDAP'
+ user1_case2 = 'usEr1@LDAP'
+ user1_case_last = 'User1@LDAP'
+ primary_gid = 2001
+ expected_gids = [2000, 2001]
+
+ assert_stored_last_initgroups(user1_case1, user1_case2, user1_case_last,
+ primary_gid, expected_gids)
+
+
+def run_simple_test_with_initgroups():
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+ ent.assert_group_by_gid(
+ 2001,
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+
+ # unrelated group to user1
+ ent.assert_group_by_name(
+ "group2",
+ dict(mem=ent.contains_only("user2", "user12", "user22")))
+ ent.assert_group_by_gid(
+ 2002,
+ dict(mem=ent.contains_only("user2", "user12", "user22")))
+
+ assert_initgroups_equal("user1", 2001, [2000, 2001])
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidatation_of_gids_after_initgroups')
+def test_invalidation_of_gids_after_initgroups(ldap_conn, sanity_rfc2307):
+
+ # the sssd cache was empty and not all user's group were
+ # resolved with getgr{nm,gid}. Therefore there is a change in
+ # group membership => user groups should be invalidated
+ run_simple_test_with_initgroups()
+ assert_initgroups_equal("user1", 2001, [2000, 2001])
+
+ stop_sssd()
+
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ # unrelated group to user1 must be returned
+ ent.assert_group_by_name(
+ "group2",
+ dict(mem=ent.contains_only("user2", "user12", "user22")))
+ ent.assert_group_by_gid(
+ 2002,
+ dict(mem=ent.contains_only("user2", "user12", "user22")))
+
+ assert_initgroups_equal("user1", 2001, [2000, 2001])
+
+ # user groups must be invalidated
+ for group in ["group1", "group0x"]:
+ with pytest.raises(KeyError):
+ grp.getgrnam(group)
+
+ for gid in [2000, 2001]:
+ with pytest.raises(KeyError):
+ grp.getgrgid(gid)
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__initgroups_without_change_in_membership')
+def test_initgroups_without_change_in_membership(ldap_conn, sanity_rfc2307):
+
+ # the sssd cache was empty and not all user's group were
+ # resolved with getgr{nm,gid}. Therefore there is a change in
+ # group membership => user groups should be invalidated
+ run_simple_test_with_initgroups()
+
+ # invalidate cache
+ subprocess.call(["sss_cache", "-E"])
+
+ # all users and groups will be just refreshed from LDAP
+ # but there will not be a change in group membership
+ # user groups should not be invlaidated
+ run_simple_test_with_initgroups()
+
+ stop_sssd()
+
+ # everything should be in memory cache
+ run_simple_test_with_initgroups()
+
+
+def assert_mc_records_for_user1():
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+ ent.assert_group_by_gid(
+ 2001,
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+ ent.assert_group_by_name(
+ "group0x",
+ dict(mem=ent.contains_only("user1", "user2", "user3")))
+ ent.assert_group_by_gid(
+ 2000,
+ dict(mem=ent.contains_only("user1", "user2", "user3")))
+
+ assert_initgroups_equal("user1", 2001, [2000, 2001])
+
+
+def assert_missing_mc_records_for_user1():
+ with pytest.raises(KeyError):
+ pwd.getpwnam("user1")
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1001)
+
+ for gid in [2000, 2001]:
+ with pytest.raises(KeyError):
+ grp.getgrgid(gid)
+ for group in ["group0x", "group1"]:
+ with pytest.raises(KeyError):
+ grp.getgrnam(group)
+
+ (res, err, _) = sssd_id.call_sssd_initgroups("user1", 2001)
+ assert res == sssd_id.NssReturnCode.UNAVAIL, \
+ "Initgroups should not find anything after invalidation of mc.\n" \
+ "User user1, errno:%d" % err
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_user_before_stop')
+def test_invalidate_user_before_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ subprocess.call(["sss_cache", "-u", "user1"])
+ stop_sssd()
+
+ assert_missing_mc_records_for_user1()
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_user_after_stop')
+def test_invalidate_user_after_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ stop_sssd()
+ subprocess.call(["sss_cache", "-u", "user1"])
+
+ assert_missing_mc_records_for_user1()
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_users_before_stop')
+def test_invalidate_users_before_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ subprocess.call(["sss_cache", "-U"])
+ stop_sssd()
+
+ assert_missing_mc_records_for_user1()
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_users_after_stop')
+def test_invalidate_users_after_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ stop_sssd()
+ subprocess.call(["sss_cache", "-U"])
+
+ assert_missing_mc_records_for_user1()
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_group_before_stop')
+def test_invalidate_group_before_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ subprocess.call(["sss_cache", "-g", "group1"])
+ stop_sssd()
+
+ assert_missing_mc_records_for_user1()
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_group_after_stop')
+def test_invalidate_group_after_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ stop_sssd()
+ subprocess.call(["sss_cache", "-g", "group1"])
+
+ assert_missing_mc_records_for_user1()
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_groups_before_stop')
+def test_invalidate_groups_before_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ subprocess.call(["sss_cache", "-G"])
+ stop_sssd()
+
+ assert_missing_mc_records_for_user1()
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_groups_after_stop')
+def test_invalidate_groups_after_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ stop_sssd()
+ subprocess.call(["sss_cache", "-G"])
+
+ assert_missing_mc_records_for_user1()
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_everything_before_stop')
+def test_invalidate_everything_before_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ subprocess.call(["sss_cache", "-E"])
+ stop_sssd()
+
+ assert_missing_mc_records_for_user1()
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__invalidate_everything_after_stop')
+def test_invalidate_everything_after_stop(ldap_conn, sanity_rfc2307):
+ # initialize cache with full ID
+ (res, errno, _) = sssd_id.get_user_groups("user1")
+ assert res == sssd_id.NssReturnCode.SUCCESS, \
+ "Could not find groups for user1, %d" % errno
+ assert_mc_records_for_user1()
+
+ stop_sssd()
+ subprocess.call(["sss_cache", "-E"])
+
+ assert_missing_mc_records_for_user1()
+
+
+def get_random_string(length):
+ return ''.join([random.choice(string.ascii_letters + string.digits)
+ for n in range(length)])
+
+
+class MemoryCache(object):
+ SIZEOF_UINT32_T = 4
+
+ def __init__(self, path):
+ with open(path, "rb") as fin:
+ fin.seek(4 * self.SIZEOF_UINT32_T)
+ self.seed = struct.unpack('i', fin.read(4))[0]
+ self.data_size = struct.unpack('i', fin.read(4))[0]
+ self.ft_size = struct.unpack('i', fin.read(4))[0]
+ hash_len = struct.unpack('i', fin.read(4))[0]
+ self.hash_size = hash_len / self.SIZEOF_UINT32_T
+
+ def sss_nss_mc_hash(self, key):
+ input_key = key + '\0'
+ input_len = len(key) + 1
+
+ murmur_hash = pysss_murmur.murmurhash3(input_key, input_len, self.seed)
+ return murmur_hash % self.hash_size
+
+
+def test_colliding_hashes(ldap_conn, sanity_rfc2307):
+ """
+ Regression test for ticket:
+ https://github.com/SSSD/sssd/issues/4595
+ """
+
+ first_user = 'user1'
+
+ # initialize data in memcache
+ ent.assert_passwd_by_name(
+ first_user,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ mem_cache = MemoryCache(config.MCACHE_PATH + '/passwd')
+
+ colliding_hash = mem_cache.sss_nss_mc_hash(first_user)
+
+ while True:
+ # string for colliding hash need to be longer then data for user1
+ # stored in memory cache (almost equivalent to:
+ # `getent passwd user1 | wc -c` ==> 45
+ second_user = get_random_string(80)
+ val = mem_cache.sss_nss_mc_hash(second_user)
+ if val == colliding_hash:
+ break
+
+ # add new user to LDAP
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user(second_user, 5001, 5001)
+ ldap_conn.add_s(ent_list[0][0], ent_list[0][1])
+
+ ent.assert_passwd_by_name(
+ second_user,
+ dict(name=second_user, passwd='*', uid=5001, gid=5001,
+ gecos='5001', shell='/bin/bash'))
+
+ stop_sssd()
+
+ # check that both users are stored in cache
+ ent.assert_passwd_by_name(
+ first_user,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ ent.assert_passwd_by_name(
+ second_user,
+ dict(name=second_user, passwd='*', uid=5001, gid=5001,
+ gecos='5001', shell='/bin/bash'))
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__removed_cache_without_invalidation')
+def test_removed_mc(ldap_conn, sanity_rfc2307):
+ """
+ Regression test for ticket:
+ https://fedorahosted.org/sssd/ticket/2726
+ """
+
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ ent.assert_group_by_name("group1", dict(name="group1", gid=2001))
+ ent.assert_group_by_gid(2001, dict(name="group1", gid=2001))
+ stop_sssd()
+
+ # remove cache without invalidation
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+ # sssd is stopped; so the memory cache should not be used
+ # in long living clients (py.test in this case)
+ with pytest.raises(KeyError):
+ pwd.getpwnam('user1')
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1001)
+
+ with pytest.raises(KeyError):
+ grp.getgrnam('group1')
+ with pytest.raises(KeyError):
+ grp.getgrgid(2001)
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__memcache_timeout_zero')
+def test_mc_zero_timeout(ldap_conn, zero_timeout_rfc2307):
+ """
+ Test that the memory cache is not created at all with memcache_timeout=0
+ """
+ # No memory cache files must be created
+ assert len(os.listdir(config.MCACHE_PATH)) == 0
+
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ ent.assert_group_by_name("group1", dict(name="group1", gid=2001))
+ ent.assert_group_by_gid(2001, dict(name="group1", gid=2001))
+ stop_sssd()
+
+ # sssd is stopped; so the memory cache should not be used
+ # in long living clients (py.test in this case)
+ with pytest.raises(KeyError):
+ pwd.getpwnam('user1')
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1001)
+
+ with pytest.raises(KeyError):
+ grp.getgrnam('group1')
+ with pytest.raises(KeyError):
+ grp.getgrgid(2001)
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_cache')
+def test_disabled_mc(ldap_conn, disable_memcache_rfc2307):
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ ent.assert_group_by_name("group1", dict(name="group1", gid=2001))
+ ent.assert_group_by_gid(2001, dict(name="group1", gid=2001))
+
+ assert_user_gids_equal('user1', [2000, 2001])
+
+ stop_sssd()
+
+ # sssd is stopped and the memory cache is disabled;
+ # so pytest should not be able to find anything
+ with pytest.raises(KeyError):
+ pwd.getpwnam('user1')
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1001)
+
+ with pytest.raises(KeyError):
+ grp.getgrnam('group1')
+ with pytest.raises(KeyError):
+ grp.getgrgid(2001)
+
+ with pytest.raises(KeyError):
+ (res, errno, gids) = sssd_id.get_user_gids('user1')
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_passwd_getpwnam')
+def test_disabled_passwd_mc(ldap_conn, disable_pwd_mc_rfc2307):
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ assert_user_gids_equal('user1', [2000, 2001])
+
+ stop_sssd()
+
+ # passwd cache is disabled
+ with pytest.raises(KeyError):
+ pwd.getpwnam('user1')
+ with pytest.raises(KeyError):
+ pwd.getpwuid(1001)
+
+ # Initgroups looks up the user first, hence KeyError from the
+ # passwd database even if the initgroups cache is active.
+ with pytest.raises(KeyError):
+ (res, errno, gids) = sssd_id.get_user_gids('user1')
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_group')
+def test_disabled_group_mc(ldap_conn, disable_grp_mc_rfc2307):
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ ent.assert_group_by_name("group1", dict(name="group1", gid=2001))
+ ent.assert_group_by_gid(2001, dict(name="group1", gid=2001))
+
+ assert_user_gids_equal('user1', [2000, 2001])
+
+ stop_sssd()
+
+ # group cache is disabled, other caches should work
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ with pytest.raises(KeyError):
+ grp.getgrnam('group1')
+ with pytest.raises(KeyError):
+ grp.getgrgid(2001)
+
+ assert_user_gids_equal('user1', [2000, 2001])
+
+
+@pytest.mark.converted('test_memory_cache.py', 'test_memory_cache__disabled_intitgroups_getpwnam')
+def test_disabled_initgr_mc(ldap_conn, disable_initgr_mc_rfc2307):
+ # Even if initgroups is disabled, passwd should work
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+
+ stop_sssd()
+
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_uid(
+ 1001,
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
diff --git a/src/tests/intg/test_netgroup.py b/src/tests/intg/test_netgroup.py
new file mode 100644
index 0000000..56c742c
--- /dev/null
+++ b/src/tests/intg/test_netgroup.py
@@ -0,0 +1,538 @@
+#
+# Netgroup integration test
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# Author: Petr Cech <pcech@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import stat
+import signal
+import subprocess
+import time
+import ldap
+import ldap.modlist
+import pytest
+
+import config
+import ds_openldap
+import ldap_ent
+from util import unindent
+from sssd_nss import NssReturnCode
+from sssd_netgroup import get_sssd_netgroups
+
+LDAP_BASE_DN = "dc=example,dc=com"
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(ds_inst.teardown)
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(ldap_conn.unbind_s)
+ return ldap_conn
+
+
+def create_ldap_entries(ldap_conn, ent_list=None):
+ """Add LDAP entries from ent_list"""
+ if ent_list is not None:
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+
+def cleanup_ldap_entries(ldap_conn, ent_list=None):
+ """Remove LDAP entries added by create_ldap_entries"""
+ if ent_list is None:
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"):
+ for entry in ldap_conn.search_s(f"ou={ou},"
+ f"{ldap_conn.ds_inst.base_dn}",
+ ldap.SCOPE_ONELEVEL,
+ attrlist=[]):
+ ldap_conn.delete_s(entry[0])
+ else:
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+
+
+def create_ldap_cleanup(request, ldap_conn, ent_list=None):
+ """Add teardown for removing all user/group LDAP entries"""
+ request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list))
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list=None):
+ """Add LDAP entries and add teardown for removing them"""
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, ent_list)
+
+
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+
+def format_basic_conf(ldap_conn, schema):
+ """Format a basic SSSD configuration"""
+ schema_conf = "ldap_schema = " + schema + "\n"
+ schema_conf += "ldap_group_object_class = groupOfNames\n"
+ return unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+ disable_netlink = true
+
+ [nss]
+
+ [domain/LDAP]
+ {schema_conf}
+ id_provider = ldap
+ auth_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ ldap_netgroup_search_base = ou=Netgroups,{ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+ """Add teardown for removing sssd.conf"""
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+ create_conf_cleanup(request)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def get_sssd_pid():
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ return pid
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ pid = get_sssd_pid()
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_cleanup(request):
+ """Add teardown for stopping SSSD and removing its state"""
+ request.addfinalizer(cleanup_sssd_process)
+
+
+def simulate_offline():
+ pid = get_sssd_pid()
+ os.kill(pid, signal.SIGUSR1)
+
+
+def create_sssd_fixture(request):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process()
+ create_sssd_cleanup(request)
+
+
+@pytest.fixture
+def add_empty_netgroup(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ ent_list.add_netgroup("empty_netgroup")
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_add_empty_netgroup(add_empty_netgroup):
+ """
+ Adding empty netgroup.
+ """
+
+ res, _, netgroups = get_sssd_netgroups("empty_netgroup")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == []
+
+
+@pytest.fixture
+def add_tripled_netgroup(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ ent_list.add_netgroup("tripled_netgroup", ["(host,user,domain)"])
+
+ ent_list.add_netgroup("adv_tripled_netgroup", ["(host1,user1,domain1)",
+ "(host2,user2,domain2)"])
+
+ ent_list.add_netgroup("tripled_netgroup_no_domain", ["(host,user,)"])
+
+ ent_list.add_netgroup("tripled_netgroup_no_user", ["(host,,domain)"])
+
+ ent_list.add_netgroup("tripled_netgroup_no_host", ["(,user,domain)"])
+
+ ent_list.add_netgroup("tripled_netgroup_none", ["(,,)"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_add_tripled_netgroup(add_tripled_netgroup):
+ """
+ Adding netgroup with triplet.
+ """
+
+ res, _, netgrps = get_sssd_netgroups("tripled_netgroup")
+ assert res == NssReturnCode.SUCCESS
+ assert netgrps == [("host", "user", "domain")]
+
+ res, _, netgrps = get_sssd_netgroups("adv_tripled_netgroup")
+ assert res == NssReturnCode.SUCCESS
+ assert sorted(netgrps) == sorted([("host1", "user1", "domain1"),
+ ("host2", "user2", "domain2")])
+
+ res, _, netgrps = get_sssd_netgroups("tripled_netgroup_no_domain")
+ assert res == NssReturnCode.SUCCESS
+ assert netgrps == [("host", "user", "")]
+
+ res, _, netgrps = get_sssd_netgroups("tripled_netgroup_no_user")
+ assert res == NssReturnCode.SUCCESS
+ assert netgrps == [("host", "", "domain")]
+
+ res, _, netgrps = get_sssd_netgroups("tripled_netgroup_no_host")
+ assert res == NssReturnCode.SUCCESS
+ assert netgrps == [("", "user", "domain")]
+
+ res, _, netgrps = get_sssd_netgroups("tripled_netgroup_none")
+ assert res == NssReturnCode.SUCCESS
+ assert netgrps == [("", "", "")]
+
+
+@pytest.fixture
+def add_mixed_netgroup(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ ent_list.add_netgroup("mixed_netgroup1")
+ ent_list.add_netgroup("mixed_netgroup2", members=["mixed_netgroup1"])
+
+ ent_list.add_netgroup("mixed_netgroup3", ["(host1,user1,domain1)"])
+ ent_list.add_netgroup("mixed_netgroup4",
+ ["(host2,user2,domain2)", "(host3,user3,domain3)"])
+
+ ent_list.add_netgroup("mixed_netgroup5",
+ ["(host4,user4,domain4)"],
+ ["mixed_netgroup1"])
+ ent_list.add_netgroup("mixed_netgroup6",
+ ["(host5,user5,domain5)"],
+ ["mixed_netgroup2"])
+
+ ent_list.add_netgroup("mixed_netgroup7", members=["mixed_netgroup3"])
+ ent_list.add_netgroup("mixed_netgroup8",
+ members=["mixed_netgroup3", "mixed_netgroup4"])
+
+ ent_list.add_netgroup("mixed_netgroup9",
+ ["(host6,user6,domain6)"],
+ ["mixed_netgroup3", "mixed_netgroup4"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_add_mixed_netgroup(add_mixed_netgroup):
+ """
+ Adding many netgroups of different type.
+ """
+
+ res, _, netgroups = get_sssd_netgroups("mixed_netgroup1")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == []
+
+ res, _, netgroups = get_sssd_netgroups("mixed_netgroup2")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == []
+
+ res, _, netgroups = get_sssd_netgroups("mixed_netgroup3")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [("host1", "user1", "domain1")]
+
+ res, _, netgroups = get_sssd_netgroups("mixed_netgroup4")
+ assert res == NssReturnCode.SUCCESS
+ assert sorted(netgroups) == sorted([("host2", "user2", "domain2"),
+ ("host3", "user3", "domain3")])
+
+ res, _, netgroups = get_sssd_netgroups("mixed_netgroup5")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [("host4", "user4", "domain4")]
+
+ res, _, netgroups = get_sssd_netgroups("mixed_netgroup6")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [("host5", "user5", "domain5")]
+
+ res, _, netgroups = get_sssd_netgroups("mixed_netgroup7")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [("host1", "user1", "domain1")]
+
+ res, _, netgroups = get_sssd_netgroups("mixed_netgroup8")
+ assert res == NssReturnCode.SUCCESS
+ assert sorted(netgroups) == sorted([("host1", "user1", "domain1"),
+ ("host2", "user2", "domain2"),
+ ("host3", "user3", "domain3")])
+
+ res, _, netgroups = get_sssd_netgroups("mixed_netgroup9")
+ assert res == NssReturnCode.SUCCESS
+ assert sorted(netgroups) == sorted([("host1", "user1", "domain1"),
+ ("host2", "user2", "domain2"),
+ ("host3", "user3", "domain3"),
+ ("host6", "user6", "domain6")])
+
+
+@pytest.fixture
+def remove_step_by_step(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ ent_list.add_netgroup("rm_empty_netgroup1", ["(host1,user1,domain1)"])
+ ent_list.add_netgroup("rm_empty_netgroup2",
+ ["(host2,user2,domain2)"],
+ ["rm_empty_netgroup1"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return ent_list
+
+
+def test_remove_step_by_step(remove_step_by_step, ldap_conn):
+ """
+ Removing netgroups step by step.
+ """
+
+ ent_list = remove_step_by_step
+
+ res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup1")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [('host1', 'user1', 'domain1')]
+
+ res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup2")
+ assert res == NssReturnCode.SUCCESS
+ assert sorted(netgroups) == sorted([('host1', 'user1', 'domain1'),
+ ('host2', 'user2', 'domain2')])
+
+ # removing of rm_empty_netgroup1
+ ldap_conn.delete_s(ent_list[0][0])
+ ent_list.remove(ent_list[0])
+
+ if subprocess.call(["sss_cache", "-N"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup1")
+ assert res == NssReturnCode.NOTFOUND
+ assert netgroups == []
+
+ res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup2")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [('host2', 'user2', 'domain2')]
+
+ # removing of rm_empty_netgroup2
+ ldap_conn.delete_s(ent_list[0][0])
+ ent_list.remove(ent_list[0])
+
+ if subprocess.call(["sss_cache", "-N"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup1")
+ assert res == NssReturnCode.NOTFOUND
+ assert netgroups == []
+
+ res, _, netgroups = get_sssd_netgroups("rm_empty_netgroup2")
+ assert res == NssReturnCode.NOTFOUND
+ assert netgroups == []
+
+
+@pytest.fixture
+def removing_nested_netgroups(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ ent_list.add_netgroup("t2841_netgroup1", ["(host1,user1,domain1)"])
+ ent_list.add_netgroup("t2841_netgroup2", ["(host2,user2,domain2)"])
+ ent_list.add_netgroup("t2841_netgroup3",
+ members=["t2841_netgroup1", "t2841_netgroup2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_removing_nested_netgroups(removing_nested_netgroups, ldap_conn):
+ """
+ Regression test for ticket 2841.
+ https://fedorahosted.org/sssd/ticket/2841
+ """
+
+ netgrp_dn = 'cn=t2841_netgroup3,ou=Netgroups,' + ldap_conn.ds_inst.base_dn
+
+ res, _, netgroups = get_sssd_netgroups("t2841_netgroup1")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [('host1', 'user1', 'domain1')]
+
+ res, _, netgroups = get_sssd_netgroups("t2841_netgroup2")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [('host2', 'user2', 'domain2')]
+
+ res, _, netgroups = get_sssd_netgroups("t2841_netgroup3")
+ assert res == NssReturnCode.SUCCESS
+ assert sorted(netgroups) == sorted([('host1', 'user1', 'domain1'),
+ ('host2', 'user2', 'domain2')])
+
+ # removing of t2841_netgroup1 from t2841_netgroup3
+ old = {'memberNisNetgroup': [b"t2841_netgroup1", b"t2841_netgroup2"]}
+ new = {'memberNisNetgroup': [b"t2841_netgroup2"]}
+
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(netgrp_dn, ldif)
+
+ if subprocess.call(["sss_cache", "-N"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ res, _, netgroups = get_sssd_netgroups("t2841_netgroup1")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [('host1', 'user1', 'domain1')]
+
+ res, _, netgroups = get_sssd_netgroups("t2841_netgroup2")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [('host2', 'user2', 'domain2')]
+
+ res, _, netgroups = get_sssd_netgroups("t2841_netgroup3")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [('host2', 'user2', 'domain2')]
+
+ # removing of t2841_netgroup2 from t2841_netgroup3
+ old = {'memberNisNetgroup': [b"t2841_netgroup2"]}
+ new = {'memberNisNetgroup': []}
+
+ ldif = ldap.modlist.modifyModlist(old, new)
+ ldap_conn.modify_s(netgrp_dn, ldif)
+
+ if subprocess.call(["sss_cache", "-N"]) != 0:
+ raise Exception("sssd_cache failed")
+
+ res, _, netgroups = get_sssd_netgroups("t2841_netgroup1")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [('host1', 'user1', 'domain1')]
+
+ res, _, netgroups = get_sssd_netgroups("t2841_netgroup2")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == [('host2', 'user2', 'domain2')]
+
+ res, _, netgroups = get_sssd_netgroups("t2841_netgroup3")
+ assert res == NssReturnCode.SUCCESS
+ assert netgroups == []
+
+
+def test_offline_netgroups(add_tripled_netgroup):
+ res, _, netgrps = get_sssd_netgroups("tripled_netgroup")
+ assert res == NssReturnCode.SUCCESS
+ assert netgrps == [("host", "user", "domain")]
+
+ subprocess.check_call(["sss_cache", "-N"])
+
+ simulate_offline()
+
+ res, _, netgrps = get_sssd_netgroups("tripled_netgroup")
+ assert res == NssReturnCode.SUCCESS
+ assert netgrps == [("host", "user", "domain")]
+
+
+@pytest.fixture
+def add_thread_test_netgroup(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ triple_list = []
+ for i in range(1, 999):
+ triple_list.append("(host1,user" + str(i) + ",domain1)")
+ ent_list.add_netgroup("ng1", triple_list)
+
+ triple_list = []
+ for i in range(1, 999):
+ triple_list.append("(host2,user" + str(i) + ",domain2)")
+ ent_list.add_netgroup("ng2", triple_list)
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_innetgr_with_threads(add_thread_test_netgroup):
+
+ subprocess.check_call(["sss_netgroup_thread_test"])
diff --git a/src/tests/intg/test_pac_responder.py b/src/tests/intg/test_pac_responder.py
new file mode 100644
index 0000000..640f547
--- /dev/null
+++ b/src/tests/intg/test_pac_responder.py
@@ -0,0 +1,129 @@
+#
+# SSSD PAC responder tests
+#
+# Copyright (c) 2017 Red Hat, Inc.
+# Author: Sumit Bose <sbose@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import stat
+import time
+import config
+import signal
+import subprocess
+import pytest
+from util import unindent
+
+
+def have_sssd_responder(responder_name):
+ responder_path = os.path.join(config.LIBEXEC_PATH, "sssd", responder_name)
+
+ return os.access(responder_path, os.X_OK)
+
+
+def stop_sssd():
+ with open(config.PIDFILE_PATH, "r") as pid_file:
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+
+
+def create_conf_fixture(request, contents):
+ """Generate sssd.conf and add teardown for removing it"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+ request.addfinalizer(lambda: os.unlink(config.CONF_PATH))
+
+
+def create_sssd_fixture(request):
+ """Start sssd and add teardown for stopping it and removing state"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+ def teardown():
+ try:
+ stop_sssd()
+ except Exception:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+ request.addfinalizer(teardown)
+
+
+@pytest.fixture
+def files_domain_only(request):
+ conf = unindent("""\
+ [sssd]
+ services = nss, pac
+ domains = files
+
+ [nss]
+ memcache_timeout = 0
+
+ [domain/files]
+ id_provider = proxy
+ proxy_lib_name = files
+ auth_provider = none
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def sssd_pac_test_client(request):
+ path = os.path.join(config.ABS_BUILDDIR,
+ "..", "..", "..", "sssd_pac_test_client")
+ if os.access(path, os.X_OK):
+ return path
+
+ return None
+
+
+def timeout_handler(signum, frame):
+ raise Exception("Timeout")
+
+
+@pytest.mark.skipif(not have_sssd_responder("sssd_pac"),
+ reason="No PAC responder, skipping")
+def test_multithreaded_pac_client(files_domain_only, sssd_pac_test_client):
+ """
+ Test for ticket
+ https://github.com/SSSD/sssd/issues/4544
+ """
+
+ if not sssd_pac_test_client:
+ pytest.skip("The sssd_pac_test_client is not available, skipping test")
+
+ signal.signal(signal.SIGALRM, timeout_handler)
+ signal.alarm(10)
+
+ try:
+ subprocess.check_call(sssd_pac_test_client)
+ except Exception:
+ # cancel alarm
+ signal.alarm(0)
+ raise Exception("sssd_pac_test_client failed")
+
+ signal.alarm(0)
diff --git a/src/tests/intg/test_pam_responder.py b/src/tests/intg/test_pam_responder.py
new file mode 100644
index 0000000..1fc3937
--- /dev/null
+++ b/src/tests/intg/test_pam_responder.py
@@ -0,0 +1,926 @@
+#
+# Test for the PAM responder
+#
+# Copyright (c) 2018 Red Hat, Inc.
+# Author: Sumit Bose <sbose@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+"""
+Tests for the PAM responder
+"""
+import os
+import stat
+import signal
+import errno
+import subprocess
+import time
+
+import config
+import intg.ds_openldap
+import kdc
+
+import pytest
+
+from intg.util import unindent
+
+LDAP_BASE_DN = "dc=example,dc=com"
+
+def provider_list():
+ if os.environ['FILES_PROVIDER'] == "enabled":
+ return ('files', 'files_with_policy', 'proxy')
+ else:
+ # The comma is required to indicate a list with the string 'proxy' as
+ # only item, without it the string 'proxy' will be interpreted as list
+ # with five letters.
+ return ('proxy',)
+
+
+class provider_switch:
+ def __init__(self, p):
+ if p == 'files':
+ self.p = "id_provider = files\nfallback_to_nss = False\n"
+ elif p == 'files_with_policy':
+ self.p = "id_provider = files\nfallback_to_nss = False\nlocal_auth_policy = enable:smartcard\n"
+ elif p == 'proxy':
+ self.p = "id_provider = proxy\nlocal_auth_policy = only\nproxy_lib_name = call\n"
+ elif p == 'proxy_password':
+ self.p = "id_provider = proxy\nproxy_lib_name = call\nproxy_pam_target = sssd-shadowutils\n"
+ elif p == 'proxy_password_with_sc':
+ self.p = "id_provider = proxy\nlocal_auth_policy = enable:smartcard\nproxy_lib_name = call\nproxy_pam_target = sssd-shadowutils\n"
+ else:
+ self.p = none
+
+
+@pytest.fixture(scope="module")
+def ad_inst(request):
+ """Fake AD server instance fixture"""
+ instance = intg.ds_openldap.FakeAD(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ instance.setup()
+ except Exception:
+ instance.teardown()
+ raise
+ request.addfinalizer(instance.teardown)
+ return instance
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ad_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ad_inst.bind()
+ ldap_conn.ad_inst = ad_inst
+ request.addfinalizer(ldap_conn.unbind_s)
+ return ldap_conn
+
+
+def format_basic_conf(ldap_conn):
+ """Format a basic SSSD configuration"""
+ return unindent("""\
+ [sssd]
+ domains = FakeAD
+ services = pam, nss
+
+ [nss]
+
+ [pam]
+ debug_level = 10
+
+ [domain/FakeAD]
+ debug_level = 10
+ ldap_search_base = {ldap_conn.ad_inst.base_dn}
+ ldap_referrals = false
+
+ id_provider = ldap
+ auth_provider = ldap
+ chpass_provider = ldap
+ access_provider = ldap
+
+ ldap_uri = {ldap_conn.ad_inst.ldap_url}
+ ldap_default_bind_dn = {ldap_conn.ad_inst.admin_dn}
+ ldap_default_authtok_type = password
+ ldap_default_authtok = {ldap_conn.ad_inst.admin_pw}
+
+ ldap_schema = ad
+ ldap_id_mapping = true
+ ldap_idmap_default_domain_sid = S-1-5-21-1305200397-2901131868-73388776
+ case_sensitive = False
+
+ [prompting/password]
+ password_prompt = My global prompt
+
+ [prompting/password/pam_sss_alt_service]
+ password_prompt = My alt service prompt
+ """).format(**locals())
+
+
+USER1 = dict(name='user1', passwd='x', uid=10001, gid=20001,
+ gecos='User for tests',
+ dir='/home/user1',
+ shell='/bin/bash')
+
+USER2 = dict(name='user2', passwd='x', uid=10002, gid=20002,
+ gecos='User with no Smartcard mapping',
+ dir='/home/user2',
+ shell='/bin/bash')
+
+
+def format_pam_cert_auth_conf(config, provider):
+ """Format a basic SSSD configuration"""
+ return unindent("""\
+ [sssd]
+ debug_level = 10
+ domains = auth_only
+ services = pam, nss
+
+ [nss]
+ debug_level = 10
+
+ [pam]
+ pam_cert_auth = True
+ pam_p11_allowed_services = +pam_sss_service, +pam_sss_sc_required, \
+ +pam_sss_try_sc, +pam_sss_allow_missing_name
+ pam_cert_db_path = {config.PAM_CERT_DB_PATH}
+ p11_uri = pkcs11:manufacturer=SoftHSM%20project; \
+ token=SSSD%20Test%20Token
+ p11_child_timeout = 5
+ p11_wait_for_card_timeout = 5
+ debug_level = 10
+
+ [domain/auth_only]
+ debug_level = 10
+ {provider.p}
+
+ [certmap/auth_only/user1]
+ matchrule = <SUBJECT>.*CN=SSSD test cert 0001.*
+ """).format(**locals())
+
+
+def format_pam_cert_auth_conf_name_format(config, provider):
+ """Format SSSD configuration with full_name_format"""
+ return unindent("""\
+ [sssd]
+ debug_level = 10
+ domains = auth_only
+ services = pam, nss
+
+ [nss]
+ debug_level = 10
+
+ [pam]
+ pam_cert_auth = True
+ pam_p11_allowed_services = +pam_sss_service, +pam_sss_sc_required, \
+ +pam_sss_try_sc, +pam_sss_allow_missing_name
+ pam_cert_db_path = {config.PAM_CERT_DB_PATH}
+ p11_uri = pkcs11:manufacturer=SoftHSM%20project; \
+ token=SSSD%20Test%20Token
+ p11_child_timeout = 5
+ p11_wait_for_card_timeout = 5
+ debug_level = 10
+
+ [domain/auth_only]
+ use_fully_qualified_names = True
+ full_name_format = %2$s\\%1$s
+ debug_level = 10
+ {provider.p}
+
+ [certmap/auth_only/user1]
+ matchrule = <SUBJECT>.*CN=SSSD test cert 0001.*
+ """).format(**locals())
+
+
+def format_pam_krb5_auth(config, kdc_instance):
+ """Format SSSD configuration for krb5 authentication"""
+ return unindent("""\
+ [sssd]
+ debug_level = 10
+ domains = krb5_auth
+ services = pam, nss
+
+ [nss]
+ debug_level = 10
+
+ [pam]
+ debug_level = 10
+
+ [domain/krb5_auth]
+ debug_level = 10
+ id_provider = proxy
+ proxy_lib_name = call
+ auth_provider = krb5
+
+ krb5_realm = PAMKRB5TEST
+ krb5_server = localhost:{kdc_instance.kdc_port}
+ """).format(**locals())
+
+
+def format_pam_krb5_auth_domains(config, kdc_instance):
+ """Format SSSD configuration for krb5 authentication"""
+ return unindent("""\
+ [sssd]
+ debug_level = 10
+ domains = wrong.dom1, wrong.dom2, krb5_auth, wrong.dom3
+ services = pam, nss
+
+ [nss]
+ debug_level = 10
+
+ [pam]
+ debug_level = 10
+
+ [domain/wrong.dom1]
+ debug_level = 10
+ id_provider = proxy
+ proxy_lib_name = call
+ auth_provider = krb5
+
+ krb5_realm = WRONG1REALM
+ krb5_server = localhost:{kdc_instance.kdc_port}
+
+ [domain/wrong.dom2]
+ debug_level = 10
+ id_provider = proxy
+ proxy_lib_name = call
+ auth_provider = krb5
+
+ krb5_realm = WRONG2REALM
+ krb5_server = localhost:{kdc_instance.kdc_port}
+
+ [domain/wrong.dom3]
+ debug_level = 10
+ id_provider = proxy
+ proxy_lib_name = call
+ auth_provider = krb5
+
+ krb5_realm = WRONG3REALM
+ krb5_server = localhost:{kdc_instance.kdc_port}
+
+ [domain/krb5_auth]
+ debug_level = 10
+ id_provider = proxy
+ proxy_lib_name = call
+ auth_provider = krb5
+
+ krb5_realm = PAMKRB5TEST
+ krb5_server = localhost:{kdc_instance.kdc_port}
+ """).format(**locals())
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+
+ def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_sssd_process(krb5_conf_path=None):
+ """Start the SSSD process"""
+ my_env = os.environ.copy()
+ my_env["SSS_FILES_PASSWD"] = os.environ["NSS_WRAPPER_PASSWD"]
+ my_env["SSS_FILES_GROUP"] = os.environ["NSS_WRAPPER_GROUP"]
+ if krb5_conf_path is not None:
+ my_env['KRB5_CONFIG'] = krb5_conf_path
+ if subprocess.call(["sssd", "-D", "--logger=files"], env=my_env) != 0:
+ raise Exception("sssd start failed")
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ with open(config.PIDFILE_PATH, "r") as pid_file:
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+ # make sure that the indicator file is removed during shutdown
+ try:
+ assert not os.stat(config.PUBCONF_PATH + "/pam_preauth_available")
+ except OSError as ex:
+ if ex.errno != errno.ENOENT:
+ raise ex
+
+
+def create_sssd_fixture(request, krb5_conf_path=None):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process(krb5_conf_path)
+ request.addfinalizer(cleanup_sssd_process)
+
+
+@pytest.fixture
+def simple_pam_cert_auth(request, passwd_ops_setup):
+ """Setup SSSD with pam_cert_auth=True"""
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+ conf = format_pam_cert_auth_conf(config, provider_switch(request.param))
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ passwd_ops_setup.useradd(**USER1)
+ passwd_ops_setup.useradd(**USER2)
+ return None
+
+
+@pytest.fixture
+def simple_pam_cert_auth_no_cert(request, passwd_ops_setup):
+ """Setup SSSD with pam_cert_auth=True"""
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+
+ old_softhsm2_conf = os.environ['SOFTHSM2_CONF']
+ del os.environ['SOFTHSM2_CONF']
+
+ conf = format_pam_cert_auth_conf(config, provider_switch(request.param))
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+ os.environ['SOFTHSM2_CONF'] = old_softhsm2_conf
+
+ passwd_ops_setup.useradd(**USER1)
+ passwd_ops_setup.useradd(**USER2)
+
+ return None
+
+
+@pytest.fixture
+def simple_pam_cert_auth_name_format(request, passwd_ops_setup):
+ """Setup SSSD with pam_cert_auth=True and full_name_format"""
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+ conf = format_pam_cert_auth_conf_name_format(config, provider_switch(request.param))
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ passwd_ops_setup.useradd(**USER1)
+ passwd_ops_setup.useradd(**USER2)
+ return None
+
+@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True)
+def test_preauth_indicator(simple_pam_cert_auth):
+ """Check if preauth indicator file is created"""
+ statinfo = os.stat(config.PUBCONF_PATH + "/pam_preauth_available")
+ assert stat.S_ISREG(statinfo.st_mode)
+
+
+@pytest.fixture
+def pam_prompting_config(request, ldap_conn):
+ """Setup SSSD with PAM prompting config"""
+ conf = format_basic_conf(ldap_conn)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_password_prompting_config_global(ldap_conn, pam_prompting_config,
+ env_for_sssctl):
+ """Check global change of the password prompt"""
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1_dom1-19661",
+ "--action=auth", "--service=pam_sss_service"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="111")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("My global prompt") != -1
+
+
+def test_password_prompting_config_srv(ldap_conn, pam_prompting_config,
+ env_for_sssctl):
+ """Check change of the password prompt for dedicated service"""
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1_dom1-19661",
+ "--action=auth",
+ "--service=pam_sss_alt_service"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="111")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("My alt service prompt") != -1
+
+
+@pytest.fixture
+def env_for_sssctl(request):
+ pwrap_runtimedir = os.getenv("PAM_WRAPPER_SERVICE_DIR")
+ if pwrap_runtimedir is None:
+ raise ValueError("The PAM_WRAPPER_SERVICE_DIR variable is unset\n")
+
+ env_for_sssctl = os.environ.copy()
+ env_for_sssctl['PAM_WRAPPER'] = "1"
+ env_for_sssctl['SSSD_INTG_PEER_UID'] = "0"
+ env_for_sssctl['SSSD_INTG_PEER_GID'] = "0"
+ env_for_sssctl['LD_PRELOAD'] += ':' + os.environ['PAM_WRAPPER_PATH']
+
+ return env_for_sssctl
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True)
+def test_sc_auth_wrong_pin(simple_pam_cert_auth, env_for_sssctl):
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+ "--action=auth", "--service=pam_sss_service"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="111")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("pam_authenticate for user [user1]: "
+ "Authentication failure") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True)
+def test_sc_auth(simple_pam_cert_auth, env_for_sssctl):
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+ "--action=auth", "--service=pam_sss_service"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("pam_authenticate for user [user1]: Success") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', ['proxy_password'], indirect=True)
+def test_sc_proxy_password_fallback(simple_pam_cert_auth, env_for_sssctl):
+ """
+ Check that there will be a password prompt if another proxy auth module is
+ configured and Smartcard authentication is not allowed but a Smartcard is
+ present.
+ """
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+ "--action=auth", "--service=pam_sss_service"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ assert err.find("Password:") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', ['proxy_password_with_sc'],
+ indirect=True)
+def test_sc_proxy_no_password_fallback(simple_pam_cert_auth, env_for_sssctl):
+ """
+ Use the same environ as for test_sc_proxy_password_fallback but now allow
+ local Smartcard authentication. Here we expect that there will be a prompt
+ for the Smartcard PIN and that Smartcard authentication is successful.
+ """
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+ "--action=auth", "--service=pam_sss_service"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("pam_authenticate for user [user1]: Success") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True)
+def test_require_sc_auth(simple_pam_cert_auth, env_for_sssctl):
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+ "--action=auth",
+ "--service=pam_sss_sc_required"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("pam_authenticate for user [user1]: Success") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth_no_cert', provider_list(), indirect=True)
+def test_require_sc_auth_no_cert(simple_pam_cert_auth_no_cert, env_for_sssctl):
+
+ # We have to wait about 20s before the command returns because there will
+ # be 2 run since retry=1 in the PAM configuration and both
+ # p11_child_timeout and p11_wait_for_card_timeout are 5s in sssd.conf,
+ # so 2*(5+5)=20. */
+ start_time = time.time()
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+ "--action=auth",
+ "--service=pam_sss_sc_required"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ end_time = time.time()
+ assert end_time > start_time and \
+ (end_time - start_time) >= 20 and \
+ (end_time - start_time) < 40
+ assert out.find("Please insert smart card\nPlease insert smart card") != -1
+ assert err.find("pam_authenticate for user [user1]: Authentication "
+ "service cannot retrieve authentication info") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True)
+def test_try_sc_auth_no_map(simple_pam_cert_auth, env_for_sssctl):
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user2",
+ "--action=auth",
+ "--service=pam_sss_try_sc"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("pam_authenticate for user [user2]: Authentication "
+ "service cannot retrieve authentication info") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True)
+def test_try_sc_auth(simple_pam_cert_auth, env_for_sssctl):
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+ "--action=auth",
+ "--service=pam_sss_try_sc"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("pam_authenticate for user [user1]: Success") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True)
+def test_try_sc_auth_root(simple_pam_cert_auth, env_for_sssctl):
+ """
+ Make sure pam_sss returns PAM_AUTHINFO_UNAVAIL even for root if
+ try_cert_auth is set.
+ """
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "root",
+ "--action=auth",
+ "--service=pam_sss_try_sc"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("pam_authenticate for user [root]: Authentication "
+ "service cannot retrieve authentication info") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True)
+def test_sc_auth_missing_name(simple_pam_cert_auth, env_for_sssctl):
+ """
+ Test pam_sss allow_missing_name feature.
+ """
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "",
+ "--action=auth",
+ "--service=pam_sss_allow_missing_name"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("pam_authenticate for user [user1]: Success") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth', provider_list(), indirect=True)
+def test_sc_auth_missing_name_whitespace(simple_pam_cert_auth, env_for_sssctl):
+ """
+ Test pam_sss allow_missing_name feature.
+ """
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", " ",
+ "--action=auth",
+ "--service=pam_sss_allow_missing_name"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find("pam_authenticate for user [user1]: Success") != -1
+
+
+@pytest.mark.parametrize('simple_pam_cert_auth_name_format', provider_list(), indirect=True)
+def test_sc_auth_name_format(simple_pam_cert_auth_name_format, env_for_sssctl):
+ """
+ Test that full_name_format is respected with pam_sss allow_missing_name
+ option.
+ """
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "",
+ "--action=auth",
+ "--service=pam_sss_allow_missing_name"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="123456")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find(r"pam_authenticate for user [auth_only\user1]: "
+ "Success") != -1
+
+
+@pytest.fixture
+def kdc_instance(request):
+ """Kerberos server instance fixture"""
+ kdc_instance = kdc.KDC(config.PREFIX, "PAMKRB5TEST")
+ try:
+ kdc_instance.set_up()
+ kdc_instance.start_kdc()
+ except Exception:
+ kdc_instance.teardown()
+ raise
+ request.addfinalizer(kdc_instance.teardown)
+ return kdc_instance
+
+
+@pytest.fixture
+def setup_krb5(request, kdc_instance, passwd_ops_setup):
+ """
+ Setup SSSD for Kerberos authentication with 2 users with different
+ passwords
+ """
+ conf = format_pam_krb5_auth(config, kdc_instance)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request, kdc_instance.krb5_conf_path)
+
+ passwd_ops_setup.useradd(**USER1)
+ passwd_ops_setup.useradd(**USER2)
+ kdc_instance.add_principal("user1", "Secret123User1")
+ kdc_instance.add_principal("user2", "Secret123User2")
+ return None
+
+
+def test_krb5_auth(setup_krb5, env_for_sssctl):
+ """
+ Test basic Kerberos authentication, check for authentication failure when
+ a wrong password is used
+ """
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+ "--action=auth",
+ "--service=pam_sss_service"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="Secret123User1")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find(r"pam_authenticate for user [user1]: Success") != -1
+
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user2",
+ "--action=auth",
+ "--service=pam_sss_service"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="Secret123User1")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find(r"pam_authenticate for user [user2]: "
+ "Authentication failure") != -1
+
+
+@pytest.fixture
+def setup_krb5_domains(request, kdc_instance, passwd_ops_setup):
+ """
+ Setup SSSD for Kerberos authentication with 2 users with different
+ passwords and multiple domains configured in sssd.conf
+ """
+ conf = format_pam_krb5_auth_domains(config, kdc_instance)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request, kdc_instance.krb5_conf_path)
+
+ passwd_ops_setup.useradd(**USER1)
+ passwd_ops_setup.useradd(**USER2)
+ kdc_instance.add_principal("user1", "Secret123User1")
+ kdc_instance.add_principal("user2", "Secret123User2")
+ return None
+
+
+def test_krb5_auth_domains(setup_krb5_domains, env_for_sssctl):
+ """
+ Test basic Kerberos authentication with pam_sss 'domains' option, make
+ sure not-matching domains are skipped even if the user exists in that
+ domain
+ """
+ sssctl = subprocess.Popen(["sssctl", "user-checks", "user1",
+ "--action=auth",
+ "--service=pam_sss_domains"],
+ universal_newlines=True,
+ env=env_for_sssctl, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ try:
+ out, err = sssctl.communicate(input="Secret123User1")
+ except Exception:
+ sssctl.kill()
+ out, err = sssctl.communicate()
+
+ sssctl.stdin.close()
+ sssctl.stdout.close()
+
+ if sssctl.wait() != 0:
+ raise Exception("sssctl failed")
+
+ assert err.find(r"pam_authenticate for user [user1]: Success") != -1
diff --git a/src/tests/intg/test_pysss_nss_idmap.py b/src/tests/intg/test_pysss_nss_idmap.py
new file mode 100644
index 0000000..fd71420
--- /dev/null
+++ b/src/tests/intg/test_pysss_nss_idmap.py
@@ -0,0 +1,360 @@
+#
+# LDAP integration test
+#
+# Copyright (c) 2017 Red Hat, Inc.
+# Author: Lukas Slebodnik <lslebodn@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import stat
+import pwd
+import grp
+import signal
+import subprocess
+import time
+import pytest
+import ldb
+import pysss_nss_idmap
+
+import config
+import ds_openldap
+
+from .util import unindent
+
+LDAP_BASE_DN = "dc=example,dc=com"
+
+
+@pytest.fixture(scope="module")
+def ad_inst(request):
+ """Fake AD server instance fixture"""
+ instance = ds_openldap.FakeAD(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ instance.setup()
+ except Exception:
+ instance.teardown()
+ raise
+ request.addfinalizer(instance.teardown)
+ return instance
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ad_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ad_inst.bind()
+ ldap_conn.ad_inst = ad_inst
+ request.addfinalizer(ldap_conn.unbind_s)
+ return ldap_conn
+
+
+def format_basic_conf(ldap_conn, ignore_unreadable_refs):
+ """Format a basic SSSD configuration"""
+
+ ignore_unreadable_refs_conf = "false"
+ if ignore_unreadable_refs:
+ ignore_unreadable_refs_conf = "true"
+
+ return unindent("""\
+ [sssd]
+ domains = FakeAD
+ services = nss
+
+ [nss]
+
+ [pam]
+
+ [domain/FakeAD]
+ ldap_search_base = {ldap_conn.ad_inst.base_dn}
+ ldap_referrals = false
+
+ id_provider = ldap
+ auth_provider = ldap
+ chpass_provider = ldap
+ access_provider = ldap
+
+ ldap_uri = {ldap_conn.ad_inst.ldap_url}
+ ldap_default_bind_dn = {ldap_conn.ad_inst.admin_dn}
+ ldap_default_authtok_type = password
+ ldap_default_authtok = {ldap_conn.ad_inst.admin_pw}
+
+ ldap_schema = ad
+ ldap_id_mapping = true
+ ldap_idmap_default_domain_sid = S-1-5-21-1305200397-2901131868-73388776
+ case_sensitive = False
+
+ ldap_ignore_unreadable_references = {ignore_unreadable_refs_conf}
+ """).format(**locals())
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+
+ def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ with open(config.PIDFILE_PATH, "r") as pid_file:
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_fixture(request):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process()
+ request.addfinalizer(cleanup_sssd_process)
+
+
+def sysdb_sed_domainid(domain_name, domain_id):
+ sssd_cache = "{0}/cache_{1}.ldb".format(config.DB_PATH, domain_name)
+ domain_ldb = ldb.Ldb(sssd_cache)
+
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(domain_ldb, "cn=sysdb")
+ msg["cn"] = "sysdb"
+ msg["description"] = "base object"
+ msg["version"] = "0.17"
+ domain_ldb.add(msg)
+
+ # Set domainID for fake AD domain
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(domain_ldb, "cn={0},cn=sysdb".format(domain_name))
+ msg["cn"] = domain_name
+ msg["domainID"] = domain_id
+ msg["distinguishedName"] = "cn={0},cn=sysdb".format(domain_name)
+ domain_ldb.add(msg)
+
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(domain_ldb, "@ATTRIBUTES")
+ msg["distinguishedName"] = "@ATTRIBUTES"
+ for attr in ['cn', 'dc', 'dn', 'objectclass', 'originalDN',
+ 'userPrincipalName']:
+ msg[attr] = "CASE_INSENSITIVE"
+ domain_ldb.add(msg)
+
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(domain_ldb, "@INDEXLIST")
+ msg["distinguishedName"] = "@INDEXLIST"
+ msg["@IDXONE"] = "1"
+ for attr in ['cn', 'objectclass', 'member', 'memberof', 'name',
+ 'uidNumber', 'gidNumber', 'lastUpdate', 'dataExpireTimestamp',
+ 'originalDN', 'nameAlias', 'servicePort', 'serviceProtocol',
+ 'sudoUser', 'sshKnownHostsExpire', 'objectSIDString']:
+ msg["@IDXATTR"] = attr
+ domain_ldb.add(msg)
+
+ msg = ldb.Message()
+ msg.dn = ldb.Dn(domain_ldb, "@MODULES")
+ msg["distinguishedName"] = "@MODULES"
+ msg["@LIST"] = "asq,memberof"
+ domain_ldb.add(msg)
+
+
+@pytest.fixture
+def simple_ad(request, ldap_conn):
+ conf = format_basic_conf(ldap_conn, ignore_unreadable_refs=False)
+ sysdb_sed_domainid("FakeAD", "S-1-5-21-1305200397-2901131868-73388776")
+
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_user_operations(ldap_conn, simple_ad):
+ user = 'user1_dom1-19661'
+ user_id = pwd.getpwnam(user).pw_uid
+ user_sid = 'S-1-5-21-1305200397-2901131868-73388776-82809'
+
+ output = pysss_nss_idmap.getsidbyname(user)[user]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER
+ assert output[pysss_nss_idmap.SID_KEY] == user_sid
+
+ output = pysss_nss_idmap.getsidbyusername(user)[user]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER
+ assert output[pysss_nss_idmap.SID_KEY] == user_sid
+
+ output = pysss_nss_idmap.getsidbygroupname(user)
+ assert len(output) == 0
+
+ output = pysss_nss_idmap.getsidbyid(user_id)[user_id]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER
+ assert output[pysss_nss_idmap.SID_KEY] == user_sid
+
+ output = pysss_nss_idmap.getsidbyuid(user_id)[user_id]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER
+ assert output[pysss_nss_idmap.SID_KEY] == user_sid
+
+ output = pysss_nss_idmap.getsidbygid(user_id)
+ assert len(output) == 0
+
+ output = pysss_nss_idmap.getidbysid(user_sid)[user_sid]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER
+ assert output[pysss_nss_idmap.ID_KEY] == user_id
+
+ output = pysss_nss_idmap.getnamebysid(user_sid)[user_sid]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_USER
+ assert output[pysss_nss_idmap.NAME_KEY] == user
+
+
+def test_group_operations(ldap_conn, simple_ad):
+ group = 'group1_dom1-19661'
+ group_id = grp.getgrnam(group).gr_gid
+ group_sid = 'S-1-5-21-1305200397-2901131868-73388776-82810'
+
+ output = pysss_nss_idmap.getsidbyname(group)[group]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbygroupname(group)[group]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbyusername(group)
+ assert len(output) == 0
+
+ output = pysss_nss_idmap.getsidbyid(group_id)[group_id]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbygid(group_id)[group_id]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbyuid(group_id)
+ assert len(output) == 0
+
+ output = pysss_nss_idmap.getidbysid(group_sid)[group_sid]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.ID_KEY] == group_id
+
+ output = pysss_nss_idmap.getnamebysid(group_sid)[group_sid]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.NAME_KEY] == group
+
+
+def test_case_insensitive(ldap_conn, simple_ad):
+ # resolve group and also member of this group
+ group = 'Domain Users'
+ group_id = grp.getgrnam(group).gr_gid
+ group_sid = 'S-1-5-21-1305200397-2901131868-73388776-513'
+
+ output = pysss_nss_idmap.getsidbyname(group)[group]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbyid(group_id)[group_id]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbygid(group_id)[group_id]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbyuid(group_id)
+ assert len(output) == 0
+
+ output = pysss_nss_idmap.getidbysid(group_sid)[group_sid]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.ID_KEY] == group_id
+
+ output = pysss_nss_idmap.getnamebysid(group_sid)[group_sid]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.NAME_KEY] == group.lower()
+
+
+@pytest.fixture
+def simple_ad_ignore_unrdbl_refs(request, ldap_conn):
+ conf = format_basic_conf(ldap_conn, ignore_unreadable_refs=True)
+ sysdb_sed_domainid("FakeAD", "S-1-5-21-1305200397-2901131868-73388776")
+
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_ignore_unreadable_references(ldap_conn, simple_ad_ignore_unrdbl_refs):
+ group = 'group3_dom1-17775'
+ group_id = grp.getgrnam(group).gr_gid
+ group_sid = 'S-1-5-21-1305200397-2901131868-73388776-82764'
+
+ output = pysss_nss_idmap.getsidbyname(group)[group]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbyid(group_id)[group_id]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbygid(group_id)[group_id]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.SID_KEY] == group_sid
+
+ output = pysss_nss_idmap.getsidbyuid(group_id)
+ assert len(output) == 0
+
+ output = pysss_nss_idmap.getidbysid(group_sid)[group_sid]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.ID_KEY] == group_id
+
+ output = pysss_nss_idmap.getnamebysid(group_sid)[group_sid]
+ assert output[pysss_nss_idmap.TYPE_KEY] == pysss_nss_idmap.ID_GROUP
+ assert output[pysss_nss_idmap.NAME_KEY] == group
+
+
+def test_no_ignore_unreadable_references(ldap_conn, simple_ad):
+ group = 'group3_dom1-17775'
+
+ # This group has a member attribute referencing to a user in other
+ # domain
+ with pytest.raises(KeyError):
+ grp.getgrnam(group)
diff --git a/src/tests/intg/test_resolver.py b/src/tests/intg/test_resolver.py
new file mode 100644
index 0000000..1b1a494
--- /dev/null
+++ b/src/tests/intg/test_resolver.py
@@ -0,0 +1,326 @@
+#
+# Resolver integration test
+#
+# Authors:
+# Samuel Cabrero <scabrero@suse.com>
+#
+# Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import os
+import stat
+import signal
+import subprocess
+import time
+import ldap
+import pytest
+import socket
+import config
+import ds_openldap
+import ldap_ent
+from util import unindent
+from sssd_nss import NssReturnCode, HostError
+from sssd_hosts import call_sssd_gethostbyname
+from sssd_nets import call_sssd_getnetbyname, call_sssd_getnetbyaddr
+
+LDAP_BASE_DN = "dc=example,dc=com"
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(ds_inst.teardown)
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(ldap_conn.unbind_s)
+ return ldap_conn
+
+
+def create_ldap_entries(ldap_conn, ent_list=None):
+ """Add LDAP entries from ent_list"""
+ if ent_list is not None:
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+
+def cleanup_ldap_entries(ldap_conn, ent_list=None):
+ """Remove LDAP entries added by create_ldap_entries"""
+ if ent_list is None:
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies",
+ "Hosts", "Networks"):
+ for entry in ldap_conn.search_s(f"ou={ou},"
+ f"{ldap_conn.ds_inst.base_dn}",
+ ldap.SCOPE_ONELEVEL,
+ attrlist=[]):
+ ldap_conn.delete_s(entry[0])
+ else:
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+
+
+def create_ldap_cleanup(request, ldap_conn, ent_list=None):
+ """Add teardown for removing all user/group LDAP entries"""
+ request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list))
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list=None):
+ """Add LDAP entries and add teardown for removing them"""
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, ent_list)
+
+
+SCHEMA_RFC2307 = "rfc2307"
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+
+def format_basic_conf(ldap_conn, schema):
+ """Format a basic SSSD configuration"""
+ schema_conf = "ldap_schema = " + schema + "\n"
+ if schema == SCHEMA_RFC2307_BIS:
+ schema_conf += "ldap_group_object_class = groupOfNames\n"
+ iphost_search_base = "ou=Hosts," + ldap_conn.ds_inst.base_dn
+ ipnetwork_search_base = "ou=Networks," + ldap_conn.ds_inst.base_dn
+ return unindent("""\
+ [sssd]
+ debug_level = 0xffff
+ domains = LDAP
+ services = nss
+
+ [nss]
+ debug_level = 0xffff
+ memcache_timeout = 0
+ entry_negative_timeout = 1
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ debug_level = 0xffff
+ {schema_conf}
+ id_provider = ldap
+ auth_provider = ldap
+ resolver_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ ldap_iphost_search_base = {iphost_search_base}
+ ldap_ipnetwork_search_base = {ipnetwork_search_base}
+ """).format(**locals())
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+ """Add teardown for removing sssd.conf"""
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+ create_conf_cleanup(request)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def get_sssd_pid():
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ return pid
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ pid = get_sssd_pid()
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_fixture(request):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process()
+ create_sssd_cleanup(request)
+
+
+def create_sssd_cleanup(request):
+ """Add teardown for stopping SSSD and removing its state"""
+ request.addfinalizer(cleanup_sssd_process)
+
+
+@pytest.fixture
+def add_hosts(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ ent_list.add_host("host1",
+ aliases=["host1_alias1", "host1_alias2"],
+ addresses=["192.168.1.1", "192.168.1.2",
+ "2001:db8:1::1", "2001:db8:1::2"])
+ ent_list.add_host("host2.example.com",
+ aliases=["host2_alias1.example.com",
+ "host2_alias2.example.com"],
+ addresses=["192.168.2.1", "192.168.2.2",
+ "2001:db8:2::1", "2001:db8:2::2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def add_nets(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ ent_list.add_ipnet("net1", "192.168.1.1",
+ aliases=["net1_alias1", "net1_alias2"])
+ ent_list.add_ipnet("net2", "10.2.2.2",
+ aliases=["net2_alias1", "net2_alias2"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_hostbyname(add_hosts):
+ (res, hres, _) = call_sssd_gethostbyname("invalid")
+
+ assert res == NssReturnCode.NOTFOUND
+ assert hres == HostError.HOST_NOT_FOUND
+
+ (res, hres, _) = call_sssd_gethostbyname("example.com")
+ assert res == NssReturnCode.NOTFOUND
+ assert hres == HostError.HOST_NOT_FOUND
+
+ (res, hres, _) = call_sssd_gethostbyname("invalid.example.com")
+ assert res == NssReturnCode.NOTFOUND
+ assert hres == HostError.HOST_NOT_FOUND
+
+ (res, hres, _) = call_sssd_gethostbyname("host1")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_gethostbyname("host1_alias1")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_gethostbyname("host1_alias2")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_gethostbyname("host2.example.com")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_gethostbyname("host2_alias1.example.com")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_gethostbyname("host2_alias2.example.com")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+
+def test_netbyname(add_nets):
+ (res, hres, _) = call_sssd_getnetbyname("invalid")
+ assert res == NssReturnCode.NOTFOUND
+ assert hres == HostError.HOST_NOT_FOUND
+
+ (res, hres, _) = call_sssd_getnetbyname("net1")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_getnetbyname("net1_alias1")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_getnetbyname("net1_alias2")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_getnetbyname("net2")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_getnetbyname("net2_alias1")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_getnetbyname("net2_alias2")
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+
+def test_netbyaddr(add_nets):
+ (res, hres, _) = call_sssd_getnetbyaddr("10.2.2.1", socket.AF_INET)
+ assert res == NssReturnCode.NOTFOUND
+ assert hres == HostError.HOST_NOT_FOUND
+
+ (res, hres, _) = call_sssd_getnetbyaddr("10.2.2.1", socket.AF_UNSPEC)
+ assert res == NssReturnCode.NOTFOUND
+ assert hres == HostError.HOST_NOT_FOUND
+
+ (res, hres, _) = call_sssd_getnetbyaddr("10.2.2.2", socket.AF_INET)
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
+
+ (res, hres, _) = call_sssd_getnetbyaddr("10.2.2.2", socket.AF_UNSPEC)
+ assert res == NssReturnCode.SUCCESS
+ assert hres == 0
diff --git a/src/tests/intg/test_session_recording.py b/src/tests/intg/test_session_recording.py
new file mode 100644
index 0000000..15faf12
--- /dev/null
+++ b/src/tests/intg/test_session_recording.py
@@ -0,0 +1,1165 @@
+#
+# Session Recording tests
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import stat
+import ent
+import config
+import signal
+import subprocess
+import time
+import ldap
+import pytest
+import ds_openldap
+import ldap_ent
+from util import unindent
+
+LDAP_BASE_DN = "dc=example,dc=com"
+INTERACTIVE_TIMEOUT = 4
+
+
+def stop_sssd():
+ """Stop sssd"""
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+
+
+def start_sssd():
+ """Start sssd"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def restart_sssd():
+ """Restart sssd"""
+ stop_sssd()
+ start_sssd()
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(lambda: ds_inst.teardown())
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(lambda: ldap_conn.unbind_s())
+ return ldap_conn
+
+
+def create_ldap_entries(ldap_conn, ent_list=None):
+ """Add LDAP entries from ent_list"""
+ if ent_list is not None:
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+
+def cleanup_ldap_entries(ldap_conn, ent_list=None):
+ """Remove LDAP entries added by create_ldap_entries"""
+ if ent_list is None:
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"):
+ for entry in ldap_conn.search_s(f"ou={ou},"
+ f"{ldap_conn.ds_inst.base_dn}",
+ ldap.SCOPE_ONELEVEL,
+ attrlist=[]):
+ ldap_conn.delete_s(entry[0])
+ else:
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+
+
+def create_ldap_cleanup(request, ldap_conn, ent_list=None):
+ """Add teardown for removing all user/group LDAP entries"""
+ request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list))
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list=None):
+ """Add LDAP entries and add teardown for removing them"""
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, ent_list)
+
+
+SCHEMA_RFC2307 = "rfc2307"
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+
+def format_basic_conf(ldap_conn, schema):
+ """
+ Format a basic SSSD configuration.
+
+ The files domain is defined but not enabled in order to avoid enumerating
+ users from the files domain that would otherwise by implicitly enabled.
+ """
+ schema_conf = "ldap_schema = " + schema + "\n"
+ if schema == SCHEMA_RFC2307_BIS:
+ schema_conf += "ldap_group_object_class = groupOfNames\n"
+ return unindent("""\
+ [sssd]
+ debug_level = 0xffff
+ domains = LDAP
+ services = nss, pam
+
+ [nss]
+ debug_level = 0xffff
+ memcache_timeout = 0
+
+ [pam]
+ debug_level = 0xffff
+
+ [domain/files]
+ id_provider = files
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ debug_level = 0xffff
+ enumerate = true
+ {schema_conf}
+ id_provider = ldap
+ auth_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ ldap_enumeration_refresh_offset = 0
+ """).format(**locals())
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+ """Add teardown for removing sssd.conf"""
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it.
+ """
+ create_conf_file(contents)
+ create_conf_cleanup(request)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_cleanup(request):
+ """Add teardown for stopping SSSD and removing its state"""
+ request.addfinalizer(cleanup_sssd_process)
+
+
+def create_sssd_fixture(request):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process()
+ create_sssd_cleanup(request)
+
+
+@pytest.fixture
+def users_and_groups(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001, loginShell="/bin/sh1")
+ ent_list.add_user("user2", 1002, 2002, loginShell="/bin/sh2")
+ ent_list.add_user("user3", 1003, 2003, loginShell="/bin/sh3")
+ # User without primary group
+ ent_list.add_user("user4", 1004, 2004, loginShell="/bin/sh4")
+ ent_list.add_group("group1", 2001)
+ ent_list.add_group("group2", 2002)
+ ent_list.add_group("group3", 2003)
+ ent_list.add_group("empty_group", 2010)
+ ent_list.add_group("one_user_group", 2011, ["user1"])
+ ent_list.add_group("two_user_group", 2012, ["user1", "user2"])
+ ent_list.add_group("three_user_group", 2013, ["user1", "user2", "user3"])
+ # Supplementary group for a user without primary group
+ ent_list.add_group("groupless_user_group", 2014, ["user4"])
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+
+@pytest.fixture
+def none(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "none".
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = none
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_none(none):
+ """Test "none" scope"""
+ ent.assert_passwd(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell="/bin/sh1"),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def all(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "all".
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = all
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_all_nam(all):
+ """Test "all" scope with getpwnam"""
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ user3=dict(name="user3", uid=1003,
+ shell=config.SESSION_RECORDING_SHELL),
+ user4=dict(name="user4", uid=1004,
+ shell=config.SESSION_RECORDING_SHELL),
+ ))
+
+
+def test_all_uid(all):
+ """Test "all" scope with getpwuid"""
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1003: dict(name="user3", uid=1003,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1004: dict(name="user4", uid=1004,
+ shell=config.SESSION_RECORDING_SHELL),
+ })
+
+
+def test_all_ent(all):
+ """Test "all" scope with getpwent"""
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user4", uid=1004, shell=config.SESSION_RECORDING_SHELL),
+ )
+ )
+
+
+@pytest.fixture
+def some_empty(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", but no users or groups listed.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_some_empty(some_empty):
+ """Test "some" scope with no users or groups"""
+ ent.assert_passwd(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell="/bin/sh1"),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def some_users(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", and some users listed.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ users = user1, user2
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_some_users_nam(some_users):
+ """Test "some" scope with user list and getpwnam"""
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_users_uid(some_users):
+ """Test "some" scope with user list and getpwuid"""
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_users_ent(some_users):
+ """Test "some" scope with user list and getpwent"""
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def some_users_overridden(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", specifying two users with
+ overridden names, but one listed with the original name.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ users = overridden_user1, user2
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ subprocess.check_call(["sss_override", "user-add", "user1",
+ "-n", "overridden_user1"])
+ subprocess.check_call(["sss_override", "user-add", "user2",
+ "-n", "overridden_user2"])
+ restart_sssd()
+
+
+def test_some_users_overridden_nam(some_users_overridden):
+ """
+ Test "some" scope with user list containing some
+ overridden users, requested with getpwnam.
+ """
+ ent.assert_each_passwd_by_name(dict(
+ overridden_user1=dict(name="overridden_user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ overridden_user2=dict(name="overridden_user2", uid=1002,
+ shell="/bin/sh2"),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_users_overridden_uid(some_users_overridden):
+ """
+ Test "some" scope with user list containing some
+ overridden users, requested with getpwuid.
+ """
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="overridden_user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="overridden_user2", uid=1002,
+ shell="/bin/sh2"),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_users_overridden_ent(some_users_overridden):
+ """
+ Test "some" scope with user list containing some
+ overridden users, requested with getpwent.
+ """
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="overridden_user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ dict(name="overridden_user2", uid=1002,
+ shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def some_groups1(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", specifying a single-user supplementary group,
+ and a two-user supplementary group intersecting with the first one.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ groups = one_user_group, two_user_group
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+@pytest.fixture
+def some_groups2(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", specifying a three-user supplementary group.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ groups = three_user_group
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+@pytest.fixture
+def some_groups3(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", specifying a group with a user with
+ non-existent primary group.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ groups = groupless_user_group
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+@pytest.fixture
+def some_groups4(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", specifying two primary groups.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ groups = group1, group3
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_some_groups1_nam(some_groups1):
+ """Test "some" scope with group list and getpwnam"""
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_groups1_uid(some_groups1):
+ """Test "some" scope with group list and getpwuid"""
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_groups1_ent(some_groups1):
+ """Test "some" scope with group list and getpwent"""
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+def test_some_groups2_nam(some_groups2):
+ """Test "some" scope with group list and getpwnam"""
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ user3=dict(name="user3", uid=1003,
+ shell=config.SESSION_RECORDING_SHELL),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_groups2_uid(some_groups2):
+ """Test "some" scope with group list and getpwuid"""
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1003: dict(name="user3", uid=1003,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_groups2_ent(some_groups2):
+ """Test "some" scope with group list and getpwent"""
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+def test_some_groups3_nam(some_groups3):
+ """Test "some" scope with group list and getpwnam"""
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001, shell="/bin/sh1"),
+ user2=dict(name="user2", uid=1002, shell="/bin/sh2"),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004,
+ shell=config.SESSION_RECORDING_SHELL),
+ ))
+
+
+def test_some_groups3_uid(some_groups3):
+ """Test "some" scope with group list and getpwuid"""
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001, shell="/bin/sh1"),
+ 1002: dict(name="user2", uid=1002, shell="/bin/sh2"),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004,
+ shell=config.SESSION_RECORDING_SHELL),
+ })
+
+
+def test_some_groups3_ent(some_groups3):
+ """Test "some" scope with group list and getpwent"""
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell="/bin/sh1"),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell=config.SESSION_RECORDING_SHELL),
+ )
+ )
+
+
+def test_some_groups4_nam(some_groups4):
+ """Test "some" scope with group list and getpwnam"""
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002, shell="/bin/sh2"),
+ user3=dict(name="user3", uid=1003,
+ shell=config.SESSION_RECORDING_SHELL),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_groups4_uid(some_groups4):
+ """Test "some" scope with group list and getpwuid"""
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002, shell="/bin/sh2"),
+ 1003: dict(name="user3", uid=1003,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_groups4_ent(some_groups4):
+ """Test "some" scope with group list and getpwent"""
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def some_groups_overridden1(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", specifying two primary groups with
+ overridden names, but one listed with the original name.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ groups = overridden_group1, group2
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ subprocess.check_call(["sss_override", "group-add", "group1",
+ "-n", "overridden_group1"])
+ subprocess.check_call(["sss_override", "group-add", "group2",
+ "-n", "overridden_group2"])
+ restart_sssd()
+
+
+def test_some_groups_overridden1_nam(some_groups_overridden1):
+ """
+ Test "some" scope with group list containing some
+ overridden groups, and users requested with getpwnam.
+ """
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002, shell="/bin/sh2"),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_groups_overridden1_uid(some_groups_overridden1):
+ """
+ Test "some" scope with group list containing some
+ overridden groups, and users requested with getpwuid.
+ """
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002, shell="/bin/sh2"),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_groups_overridden1_ent(some_groups_overridden1):
+ """
+ Test "some" scope with group list containing some
+ overridden groups, and users requested with getpwent.
+ """
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def some_groups_overridden2(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", specifying two supplementary groups with
+ overridden names, but one listed with the original name.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ groups = one_user_group_overridden, two_user_group
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ subprocess.check_call(["sss_override", "group-add", "one_user_group",
+ "-n", "one_user_group_overridden"])
+ subprocess.check_call(["sss_override", "group-add", "two_user_group",
+ "-n", "two_user_group_overridden"])
+ restart_sssd()
+
+
+def test_some_groups_overridden2_nam(some_groups_overridden2):
+ """
+ Test "some" scope with group list containing some
+ overridden groups, and users requested with getpwnam.
+ """
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002, shell="/bin/sh2"),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_groups_overridden2_uid(some_groups_overridden2):
+ """
+ Test "some" scope with group list containing some
+ overridden groups, and users requested with getpwuid.
+ """
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002, shell="/bin/sh2"),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_groups_overridden2_ent(some_groups_overridden2):
+ """
+ Test "some" scope with group list containing some
+ overridden groups, and users requested with getpwent.
+ """
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def some_groups_overridden3(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", having two primary groups with
+ IDs swapped via overriding, but only one of them listed.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ groups = group2
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ subprocess.check_call(["sss_override", "group-add", "group1",
+ "-g", "2002"])
+ subprocess.check_call(["sss_override", "group-add", "group2",
+ "-g", "2001"])
+ restart_sssd()
+
+
+def test_some_groups_overridden3_nam(some_groups_overridden3):
+ """
+ Test "some" scope with group list containing some
+ overridden group, and users requested with getpwnam.
+ """
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002, shell="/bin/sh2"),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_groups_overridden3_uid(some_groups_overridden3):
+ """
+ Test "some" scope with group list containing some
+ overridden group, and users requested with getpwuid.
+ """
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002, shell="/bin/sh2"),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_groups_overridden3_ent(some_groups_overridden3):
+ """
+ Test "some" scope with group list containing some
+ overridden group, and users requested with getpwent.
+ """
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def some_groups_overridden4(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", two users with GIDs swapped via overridding,
+ and one of their primary groups listed.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ groups = group2
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ subprocess.check_call(["sss_override", "user-add", "user1",
+ "-g", "2002"])
+ subprocess.check_call(["sss_override", "user-add", "user2",
+ "-g", "2001"])
+ restart_sssd()
+
+
+def test_some_groups_overridden4_nam(some_groups_overridden3):
+ """
+ Test "some" scope with group list containing some
+ overridden group, and users requested with getpwnam.
+ """
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002, shell="/bin/sh2"),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_groups_overridden4_uid(some_groups_overridden3):
+ """
+ Test "some" scope with group list containing some
+ overridden group, and users requested with getpwuid.
+ """
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002, shell="/bin/sh2"),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_groups_overridden4_ent(some_groups_overridden3):
+ """
+ Test "some" scope with group list containing some
+ overridden group, and users requested with getpwent.
+ """
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def some_users_and_groups(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some", listing some users and groups.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ users = user3
+ groups = one_user_group
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_some_users_and_groups_nam(some_users_and_groups):
+ """
+ Test "some" scope with user and group lists and getpwnam.
+ """
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002, shell="/bin/sh2"),
+ user3=dict(name="user3", uid=1003,
+ shell=config.SESSION_RECORDING_SHELL),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_users_and_groups_uid(some_users_and_groups):
+ """
+ Test "some" scope with user and group lists and getpwuid.
+ """
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002, shell="/bin/sh2"),
+ 1003: dict(name="user3", uid=1003,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_users_and_groups_ent(some_users_and_groups):
+ """
+ Test "some" scope with user and group lists and getpwent.
+ """
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
+
+
+@pytest.fixture
+def all_exclude_users(request, ldap_conn, users_and_groups):
+ """
+ Test "all" scope with a simple excludes user list
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = all
+ exclude_users = user1, user3
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_all_exclude_users_nam(all_exclude_users):
+ """Test "all" scope with exclude users list and getpwnam"""
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001, shell="/bin/sh1"),
+ user2=dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004,
+ shell=config.SESSION_RECORDING_SHELL),
+ ))
+
+
+def test_all_exclude_users_uid(all_exclude_users):
+ """Test "all" scope with exclude users list and getpwuid"""
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001, shell="/bin/sh1"),
+ 1002: dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004,
+ shell=config.SESSION_RECORDING_SHELL),
+ })
+
+
+def test_all_exclude_users_ent(all_exclude_users):
+ """Test "all" scope with exclude users list and getpwent"""
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell="/bin/sh1"),
+ dict(name="user2", uid=1002, shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell=config.SESSION_RECORDING_SHELL),
+ )
+ )
+
+
+@pytest.fixture
+def all_exclude_groups(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "all", specifying two primary exclude
+ groups and one supplementary exclude group.
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = all
+ exclude_groups = group1, group3, two_user_group
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_all_exclude_groups_nam(all_exclude_groups):
+ """Test "all" scope with exclude groups list and getpwnam"""
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001, shell="/bin/sh1"),
+ user2=dict(name="user2", uid=1002, shell="/bin/sh2"),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004,
+ shell=config.SESSION_RECORDING_SHELL),
+ ))
+
+
+def test_all_exclude_groups_uid(all_exclude_groups):
+ """Test "all" scope with group list and getpwuid"""
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001, shell="/bin/sh1"),
+ 1002: dict(name="user2", uid=1002, shell="/bin/sh2"),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004,
+ shell=config.SESSION_RECORDING_SHELL),
+ })
+
+
+def test_all_exclude_groups_ent(all_exclude_groups):
+ """Test "all" scope with group list and getpwent"""
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001, shell="/bin/sh1"),
+ dict(name="user2", uid=1002, shell="/bin/sh2"),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004,
+ shell=config.SESSION_RECORDING_SHELL),
+ )
+ )
+
+
+@pytest.fixture
+def some_users_and_exclude_groups(request, ldap_conn, users_and_groups):
+ """
+ Fixture with scope "some" containing users to
+ enable recording, and exclude_* options to be ignored
+ intentionally
+ """
+ conf = \
+ format_basic_conf(ldap_conn, SCHEMA_RFC2307) + \
+ unindent("""\
+ [session_recording]
+ scope = some
+ users = user1, user2
+ exclude_users = user1, user2, user3
+ exclude_groups = group1, group3, two_user_group
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+
+def test_some_users_and_exclude_groups_nam(some_users_and_exclude_groups):
+ """Test "some" scope with exclude users and groups list and getpwnam"""
+ ent.assert_each_passwd_by_name(dict(
+ user1=dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ user2=dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ user3=dict(name="user3", uid=1003, shell="/bin/sh3"),
+ user4=dict(name="user4", uid=1004, shell="/bin/sh4"),
+ ))
+
+
+def test_some_users_and_exclude_groups_uid(some_users_and_exclude_groups):
+ """Test "some" scope with exclude users and groups list and getpwuid"""
+ ent.assert_each_passwd_by_uid({
+ 1001: dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1002: dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ 1003: dict(name="user3", uid=1003, shell="/bin/sh3"),
+ 1004: dict(name="user4", uid=1004, shell="/bin/sh4"),
+ })
+
+
+def test_some_users_and_exclude_groups_ent(some_users_and_exclude_groups):
+ """Test "some" scope with exclude users and group list and getpwent"""
+ ent.assert_passwd_list(
+ ent.contains_only(
+ dict(name="user1", uid=1001,
+ shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user2", uid=1002,
+ shell=config.SESSION_RECORDING_SHELL),
+ dict(name="user3", uid=1003, shell="/bin/sh3"),
+ dict(name="user4", uid=1004, shell="/bin/sh4"),
+ )
+ )
diff --git a/src/tests/intg/test_ssh_pubkey.py b/src/tests/intg/test_ssh_pubkey.py
new file mode 100644
index 0000000..34bde1b
--- /dev/null
+++ b/src/tests/intg/test_ssh_pubkey.py
@@ -0,0 +1,340 @@
+#
+# ssh public key integration test
+#
+# Copyright (c) 2018 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import stat
+import signal
+import subprocess
+import time
+import string
+import random
+import pytest
+
+import ds_openldap
+import ldap_ent
+import ldap
+import ldap.modlist
+import config
+
+from util import unindent, get_call_output
+
+LDAP_BASE_DN = "dc=example,dc=com"
+
+USER1_PUBKEY1 = "ssh-dss AAAAB3NzaC1kc3MAAACBAPMkvcU53RVhBtjwiC3IqeRIWR9Qwdv8\
+DmZzEsDD3Csd6jYxMsPZoXcPrHqwYcEj1s5MVqhdSFS0Cjz13e7gO6OMLInO3xMBSSFHjfp9RE1H\
+pgc4WisazzyJaW9EMkQo/DqvkFkKh31oqAmxcSbLAFJRg4TTIqm18qu8IRKS6m/RAAAAFQC97TA5\
+JSsMsaX1bRszC7y4PhMBvQAAAIEAt9Yo9v/h9W4nDbzUdkGwNRszlPEK+T12bJv0O9Fk6subD3Do\
+6A4Qru/Nr6voXoq8b018Wb7iFWvKOoz5uT/plWBKLXL2NN7ovTR+dUJIzvwurQZroukmU1EghNey\
+lkSHmDlxSoMK6Nh21uGu6l+b6x5pXNaZHMpsywG4kY8SoC0AAACAAWLHneEGvqkYA8La4Eob+Hjj\
+mAKilx8byxm3Kfb1XO+ZrR6XxadofZOaUYRMpPKgFjKAKPxJftPLiDjWM7lSe6h8df0dUMLVXt6m\
+eA83kE0uK5JOOGJfJDqmRed2YnfxUDNNFQGT4xFWGrNtYNbGyw9BWKbkooAsLqaO04zP3Rs= \
+user1@LDAP"
+
+USER1_PUBKEY2 = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAwHUUF3HPH+DkU6j8k7Q1wHG\
+RJY9NeLqSav3h95mTSCQYPSC7I9RTJ4OORgqCbEzrP/DYrrn4TtQ9dhRJar3ZY+F36SH5yFIXORb\
+lAIbFU+/anahBuFS9vHi1MqFPckGmwJ4QCpjQhdYxo1ro0e1RuGSaQNp/w9N6S/fDz4Cj4I99xDz\
+SeQeGHxYv0e60plQ8dUajmnaGmYRJHF9a6Ban7IWySActCja7eQP2zIRXEZMpuhl1E0U4y+gHTFI\
+gD3zQai3QrXm8RUrQURIJ0u6BlGS910OPbHqLpLTFWG08L8sNUcYzC+DY6yoCSO0n/Df3pVRS4C9\
+5Krf3FqppMTjdfQ== user1@LDAP"
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(ds_inst.teardown)
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(ldap_conn.unbind_s)
+ return ldap_conn
+
+
+def create_ldap_entries(ldap_conn, ent_list=None):
+ """Add LDAP entries from ent_list"""
+ if ent_list is not None:
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+
+def cleanup_ldap_entries(ldap_conn, ent_list=None):
+ """Remove LDAP entries added by create_ldap_entries"""
+ if ent_list is None:
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"):
+ for entry in ldap_conn.search_s(f"ou={ou},"
+ f"{ldap_conn.ds_inst.base_dn}",
+ ldap.SCOPE_ONELEVEL,
+ attrlist=[]):
+ ldap_conn.delete_s(entry[0])
+ else:
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+
+
+def create_ldap_cleanup(request, ldap_conn, ent_list=None):
+ """Add teardown for removing all user/group LDAP entries"""
+ request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list))
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list=None):
+ """Add LDAP entries and add teardown for removing them"""
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, ent_list)
+
+
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+
+def format_basic_conf(ldap_conn, schema, config):
+ """Format a basic SSSD configuration"""
+ schema_conf = "ldap_schema = " + schema + "\n"
+ schema_conf += "ldap_group_object_class = groupOfNames\n"
+ return unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss, ssh
+
+ [nss]
+
+ [ssh]
+ debug_level=10
+ ca_db = {config.PAM_CERT_DB_PATH}
+
+ [pam]
+ pam_cert_auth = True
+
+ [domain/LDAP]
+ {schema_conf}
+ id_provider = ldap
+ auth_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ ldap_sudo_use_host_filter = false
+ debug_level=10
+ ldap_user_certificate = userCertificate;binary
+ """).format(**locals())
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+ """Add teardown for removing sssd.conf"""
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+ create_conf_cleanup(request)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def get_sssd_pid():
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ return pid
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ pid = get_sssd_pid()
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_fixture(request):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process()
+ create_sssd_cleanup(request)
+
+
+def create_sssd_cleanup(request):
+ """Add teardown for stopping SSSD and removing its state"""
+ request.addfinalizer(cleanup_sssd_process)
+
+
+@pytest.fixture
+def add_user_with_ssh_key(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001,
+ sshPubKey=(USER1_PUBKEY1, USER1_PUBKEY2))
+ ent_list.add_user("user2", 1002, 2001)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS, config)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_ssh_pubkey_retrieve(add_user_with_ssh_key):
+ """
+ Test that we can retrieve an SSH public key for a user who has one
+ and can't retrieve a key for a user who does not have one.
+ """
+ sshpubkey = get_call_output(["sss_ssh_authorizedkeys", "user1"])
+ assert sshpubkey == USER1_PUBKEY1 + '\n' + USER1_PUBKEY2 + '\n'
+
+ sshpubkey = get_call_output(["sss_ssh_authorizedkeys", "user2"])
+ assert len(sshpubkey) == 0
+
+
+def test_ssh_pubkey_retrieve_cert(add_user_with_ssh_cert):
+ """
+ Test that we can retrieve an SSH public key derived from a cert in ldap.
+ Compare with the sshpubkey derived via ssh-keygen, they should match.
+ """
+ for u in [1, 7]:
+ pubsshkey_path = os.path.dirname(config.PAM_CERT_DB_PATH)
+ pubsshkey_path += "/SSSD_test_cert_pubsshkey_000%s.pub" % u
+ with open(pubsshkey_path, 'r') as f:
+ pubsshkey = f.read()
+ sshpubkey = get_call_output(["sss_ssh_authorizedkeys", "user%s" % u])
+ print(sshpubkey)
+ print(pubsshkey)
+ assert sshpubkey == pubsshkey
+
+
+@pytest.fixture()
+def sighup_client(request):
+ test_ssh_cli_path = os.path.join(config.ABS_BUILDDIR,
+ "..", "..", "..", "test_ssh_client")
+ assert os.access(test_ssh_cli_path, os.X_OK)
+ return test_ssh_cli_path
+
+
+@pytest.fixture
+def add_user_with_many_keys(request, ldap_conn):
+ # Generate a large list of unique ssh pubkeys
+ pubkey_list = []
+ while len(pubkey_list) < 50:
+ new_pubkey = list(USER1_PUBKEY1)
+ new_pubkey[10] = random.choice(string.ascii_uppercase)
+ new_pubkey[11] = random.choice(string.ascii_uppercase)
+ new_pubkey[12] = random.choice(string.ascii_uppercase)
+ str_new_pubkey = ''.join(c for c in new_pubkey)
+ if str_new_pubkey in pubkey_list:
+ continue
+ pubkey_list.append(str_new_pubkey)
+
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001, sshPubKey=pubkey_list)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS, config)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def add_user_with_ssh_cert(request, ldap_conn):
+ # Add a certificate to ldap, to manually test a cert from a smartcard.
+ config.PAM_CERT_DB_PATH = os.environ['PAM_CERT_DB_PATH']
+
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user7", 1007, 2001)
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+ for u in [1, 7]:
+ der_path = os.path.dirname(config.PAM_CERT_DB_PATH)
+ der_path += "/SSSD_test_cert_x509_000%s.der" % u
+ with open(der_path, 'rb') as f:
+ val = f.read()
+
+ dn = "uid=user%s,ou=Users," % u + LDAP_BASE_DN
+ ldap_conn.modify_s(dn, [(ldap.MOD_ADD, 'usercertificate;binary', val)])
+
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS, config)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+
+ return None
+
+
+def test_ssh_sighup(add_user_with_many_keys, sighup_client):
+ """
+ A regression test for https://github.com/SSSD/sssd/issues/4754
+
+ OpenSSH can close its end of the pipe towards sss_ssh_authorizedkeys
+ before all of the output is read. In that case, older versions
+ of sss_ssh_authorizedkeys were receiving a SIGPIPE
+ """
+ cli_path = sighup_client
+
+ # python actually does the sensible, but unexpected (for a C programmer)
+ # thing and handles SIGPIPE. In order to reproduce the bug, we need
+ # to unset the SIGPIPE handler
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
+ process = subprocess.Popen([cli_path, "user1"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ _, _ = process.communicate()
+ # If the test tool detects that sss_ssh_authorizedkeys was killed with a
+ # signal, it would have returned 1
+ assert process.returncode == 0
diff --git a/src/tests/intg/test_sss_cache.py b/src/tests/intg/test_sss_cache.py
new file mode 100644
index 0000000..22f12f0
--- /dev/null
+++ b/src/tests/intg/test_sss_cache.py
@@ -0,0 +1,34 @@
+#
+# SSSD files domain tests
+#
+# Copyright (c) 2019 Red Hat, Inc.
+# Author: Lukas Slebodnik <lslebodn@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import subprocess
+
+
+def test_missing_domains():
+ # Utilities in shadow-utils call sss_cache but it might fail in case
+ # sssd has never been started on such host.
+ ret = subprocess.call(["sss_cache", "-U"])
+ assert ret == 0
+
+ ret = subprocess.call(["sss_cache", "-G"])
+ assert ret == 0
+
+ ret = subprocess.call(["sss_cache", "-E"])
+ assert ret == 0
diff --git a/src/tests/intg/test_sssctl.py b/src/tests/intg/test_sssctl.py
new file mode 100644
index 0000000..60c2167
--- /dev/null
+++ b/src/tests/intg/test_sssctl.py
@@ -0,0 +1,532 @@
+#
+# sssctl tool integration test
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# Author: Michal Zidek <mzidek@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+import os
+import ent
+import subprocess
+import pytest
+import stat
+import time
+import signal
+import ds_openldap
+import ldap_ent
+import config
+from util import unindent, get_call_output
+import sssd_netgroup
+
+LDAP_BASE_DN = "dc=example,dc=com"
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123")
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(lambda: ds_inst.teardown())
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(lambda: ldap_conn.unbind_s())
+ return ldap_conn
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list):
+ """Add LDAP entries and add teardown for removing them"""
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+ def teardown():
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+ request.addfinalizer(teardown)
+
+
+def create_conf_fixture(request, contents, snippet=None):
+ """Generate sssd.conf and add teardown for removing it"""
+ if contents is not None:
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+ request.addfinalizer(lambda: os.unlink(config.CONF_PATH))
+ if snippet is not None:
+ conf = open(config.CONF_SNIPPET_PATH, "w")
+ conf.write(snippet)
+ conf.close()
+ os.chmod(config.CONF_SNIPPET_PATH, stat.S_IRUSR | stat.S_IWUSR)
+ request.addfinalizer(lambda: os.unlink(config.CONF_SNIPPET_PATH))
+
+
+def stop_sssd():
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+
+
+def create_sssd_fixture(request):
+ """Start sssd and add teardown for stopping it and removing state"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+ def teardown():
+ try:
+ stop_sssd()
+ except Exception:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+ request.addfinalizer(teardown)
+
+
+@pytest.fixture
+def portable_LC_ALL(request):
+ os.environ["LC_ALL"] = "C"
+ return None
+
+
+def load_data_to_ldap(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("CamelCaseUser1", 1002, 2002)
+
+ ent_list.add_group("group1", 2001, ["user1"])
+ ent_list.add_group("CamelCaseGroup1", 2002, ["CamelCaseUser1"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+
+@pytest.fixture
+def sanity_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ ldap_netgroup_search_base = ou=Netgroups,{ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def fqname_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ use_fully_qualified_names = true
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def fqname_case_insensitive_rfc2307(request, ldap_conn):
+ load_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+
+ [domain/LDAP]
+ ldap_auth_disable_tls_never_use_in_production = true
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ use_fully_qualified_names = true
+ case_sensitive = false
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+def test_user_show_basic_sanity(ldap_conn, sanity_rfc2307, portable_LC_ALL):
+ # Fill the cache first
+ ent.assert_passwd_by_name(
+ 'user1',
+ dict(name='user1', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_name(
+ 'CamelCaseUser1',
+ dict(name='CamelCaseUser1', passwd='*', uid=1002, gid=2002,
+ gecos='1002', shell='/bin/bash'))
+
+ output = get_call_output(["sssctl", "user-show", "user1"])
+ assert output.find("Name: user1") != -1
+ assert output.find("Initgroups expiration time: Initgroups were not yet "
+ "performed") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "user-show",
+ "CamelCaseUser1"])
+ assert output.find("Name: CamelCaseUser1") != -1
+ assert output.find("Initgroups expiration time: Initgroups were not yet "
+ "performed") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "user-show", "camelcaseuser1"])
+ assert output.find("User camelcaseuser1 is not present in cache.") != -1
+
+
+def test_user_show_basic_fqname(ldap_conn, fqname_rfc2307, portable_LC_ALL):
+ # Fill the cache first
+ ent.assert_passwd_by_name(
+ 'user1@LDAP',
+ dict(name='user1@LDAP', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_name(
+ 'CamelCaseUser1@LDAP',
+ dict(name='CamelCaseUser1@LDAP', passwd='*', uid=1002, gid=2002,
+ gecos='1002', shell='/bin/bash'))
+
+ output = get_call_output(["sssctl", "user-show", "user1@LDAP"])
+ assert output.find("Name: user1@LDAP") != -1
+ assert output.find("Initgroups expiration time: Initgroups were not yet "
+ "performed") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "user-show", "CamelCaseUser1@LDAP"])
+ assert output.find("Name: CamelCaseUser1@LDAP") != -1
+ assert output.find("Initgroups expiration time: Initgroups were not yet "
+ "performed") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "user-show", "camelcaseuser1@LDAP"])
+ assert output.find("User camelcaseuser1 is not present in cache.") != -1
+
+
+def test_user_show_basic_fqname_insensitive(ldap_conn,
+ fqname_case_insensitive_rfc2307,
+ portable_LC_ALL):
+ # Fill the cache first
+ ent.assert_passwd_by_name(
+ 'user1@LDAP',
+ dict(name='user1@LDAP', passwd='*', uid=1001, gid=2001,
+ gecos='1001', shell='/bin/bash'))
+ ent.assert_passwd_by_name(
+ 'CamelCaseUser1@LDAP',
+ dict(name='camelcaseuser1@LDAP', passwd='*', uid=1002, gid=2002,
+ gecos='1002', shell='/bin/bash'))
+
+ output = get_call_output(["sssctl", "user-show", "user1@LDAP"])
+ assert output.find("Name: user1@LDAP") != -1
+ assert output.find("Initgroups expiration time: Initgroups were not yet "
+ "performed") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "user-show", "CamelCaseUser1@LDAP"])
+ assert output.find("Name: camelcaseuser1@LDAP") != -1
+ assert output.find("Initgroups expiration time: Initgroups were not yet "
+ "performed") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "user-show", "camelcaseuser1@LDAP"])
+ assert output.find("Name: camelcaseuser1@LDAP") != -1
+ assert output.find("Initgroups expiration time: Initgroups were not yet "
+ "performed") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+
+def test_group_show_basic_sanity(ldap_conn, sanity_rfc2307, portable_LC_ALL):
+ # Fill the cache first
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1")))
+ ent.assert_group_by_name(
+ "CamelCaseGroup1",
+ dict(mem=ent.contains_only("CamelCaseUser1")))
+
+ output = get_call_output(["sssctl", "group-show", "group1"])
+ assert output.find("Name: group1") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "group-show", "CamelCaseGroup1"])
+ assert output.find("Name: CamelCaseGroup1") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "group-show", "camelcasegroup1"])
+ assert output.find("Group camelcasegroup1 is not present in cache.") != -1
+
+
+def test_group_show_basic_fqname(ldap_conn, fqname_rfc2307, portable_LC_ALL):
+ # Fill the cache first
+ ent.assert_group_by_name(
+ "group1@LDAP",
+ dict(mem=ent.contains_only("user1@LDAP")))
+ ent.assert_group_by_name(
+ "CamelCaseGroup1@LDAP",
+ dict(mem=ent.contains_only("CamelCaseUser1@LDAP")))
+
+ output = get_call_output(["sssctl", "group-show", "group1@LDAP"])
+ assert output.find("Name: group1@LDAP") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "group-show", "CamelCaseGroup1@LDAP"])
+ assert output.find("Name: CamelCaseGroup1@LDAP") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "group-show", "camelcasegroup1@LDAP"])
+ assert output.find("Group camelcasegroup1 is not present in cache.") != -1
+
+
+def test_group_show_basic_fqname_insensitive(ldap_conn,
+ fqname_case_insensitive_rfc2307,
+ portable_LC_ALL):
+ # Fill the cache first
+ ent.assert_group_by_name(
+ "group1@LDAP",
+ dict(mem=ent.contains_only("user1@LDAP")))
+ ent.assert_group_by_name(
+ "camelcasegroup1@LDAP",
+ dict(mem=ent.contains_only("camelcaseuser1@LDAP")))
+
+ output = get_call_output(["sssctl", "group-show", "group1@LDAP"])
+ assert output.find("Name: group1@LDAP") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "group-show", "CamelCaseGroup1@LDAP"])
+ assert output.find("Name: camelcasegroup1@LDAP") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+ output = get_call_output(["sssctl", "group-show", "camelcasegroup1@LDAP"])
+ assert output.find("Name: camelcasegroup1@LDAP") != -1
+ assert output.find("Cached in InfoPipe: No") != -1
+
+
+@pytest.fixture
+def add_tripled_netgroup(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+
+ ent_list.add_netgroup("tripled_netgroup", ["(host,user,domain)"])
+
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ return None
+
+
+def test_netgroup_show(ldap_conn,
+ sanity_rfc2307,
+ portable_LC_ALL,
+ add_tripled_netgroup):
+ output = get_call_output(["sssctl", "netgroup-show", "tripled_netgroup"])
+ assert "Name: tripled_netgroup" not in output
+
+ res, _, netgrps = sssd_netgroup.get_sssd_netgroups("tripled_netgroup")
+ assert res == sssd_netgroup.NssReturnCode.SUCCESS
+ assert netgrps == [("host", "user", "domain")]
+
+ output = get_call_output(["sssctl", "netgroup-show", "tripled_netgroup"])
+ assert "Name: tripled_netgroup" in output
+
+
+@pytest.fixture
+def conf_snippets_only(request):
+ snip = unindent("""\
+ [sssd]
+ services = nss, pam, ssh
+ [nss]
+ [pam]
+ [ssh]
+ """)
+ create_conf_fixture(request, None, snip)
+ return None
+
+
+@pytest.fixture
+def conf_stub_domain(request):
+ snip = unindent("""\
+ [sssd]
+ services = nss
+ domains = files
+ [nss]
+ [domain/files]
+ id_provider = files
+ """)
+ create_conf_fixture(request, None, snip)
+ return None
+
+
+def test_sssctl_snippets_only(conf_snippets_only, portable_LC_ALL):
+ output = get_call_output(["sssctl", "config-check"])
+ assert "There is no configuration" not in output
+ assert config.CONF_SNIPPET_PATH in output
+
+
+def test_sssctl_no_config(portable_LC_ALL):
+ output = get_call_output(["sssctl", "config-check"])
+ assert "There is no configuration" in output
+
+
+def test_debug_level_sanity(ldap_conn, sanity_rfc2307, portable_LC_ALL):
+ output = get_call_output(["sssctl", "debug-level", "0x00F0"],
+ check=True)
+ assert output.strip() == ""
+ output = get_call_output(["sssctl", "debug-level"],
+ check=True)
+ for line in output.splitlines():
+ elems = line.split()
+ assert elems[0] in ["sssd", "nss", "domain/LDAP", "domain/implicit_files"]
+ assert elems[1] == "0x00f0"
+
+ output = get_call_output(["sssctl", "debug-level", "--sssd", "0x0270"],
+ check=True)
+ assert output.strip() == ""
+ output = get_call_output(["sssctl", "debug-level", "--sssd"],
+ check=True)
+ assert "sssd " in output
+ assert "0x0270" in output
+
+ output = get_call_output(["sssctl", "debug-level", "--nss", "0x0370"],
+ check=True)
+ assert output.strip() == ""
+ output = get_call_output(["sssctl", "debug-level", "--nss"],
+ check=True)
+ assert "nss " in output
+ assert "0x0370" in output
+
+ output = get_call_output(["sssctl", "debug-level", "--domain=LDAP", "8"],
+ check=True)
+ assert output.strip() == ""
+ output = get_call_output(["sssctl", "debug-level", "--domain=LDAP"],
+ check=True)
+ assert "domain/LDAP " in output
+ assert "0x37f0" in output
+
+ try:
+ get_call_output(["sssctl", "debug-level", "--domain=FAKE"],
+ check=True)
+ except subprocess.CalledProcessError as cpe:
+ assert cpe.returncode == 1
+ assert "domain/FAKE " in cpe.output
+ assert " Unknown domain" in cpe.output
+
+ try:
+ get_call_output(["sssctl", "debug-level", "--pac"],
+ check=True)
+ except subprocess.CalledProcessError as cpe:
+ assert cpe.returncode == 1
+ assert "pac " in cpe.output
+ assert " Unreachable service" in cpe.output
+
+ try:
+ get_call_output(["sssctl", "debug-level", "--domain=FAKE", "8"],
+ check=True)
+ except subprocess.CalledProcessError as cpe:
+ assert cpe.returncode == 1
+ assert cpe.output.strip() == ""
+
+
+def test_debug_level_no_sssd(conf_stub_domain, portable_LC_ALL):
+ # Once we are sure all tests run using Python 3.5 or newer,
+ # we can remove the redirections STDOUT > STDERR and check cpe.stderr.
+
+ try:
+ get_call_output(["sssctl", "debug-level"], check=True,
+ stderr_output=subprocess.STDOUT)
+ except subprocess.CalledProcessError as cpe:
+ assert cpe.returncode == 1
+ assert "SSSD is not running" in cpe.output
+
+ try:
+ get_call_output(["sssctl", "debug-level", "0x70"], check=True,
+ stderr_output=subprocess.STDOUT)
+ except subprocess.CalledProcessError as cpe:
+ assert cpe.returncode == 1
+ assert "SSSD is not running" in cpe.output
+
+ try:
+ get_call_output(["sssctl", "debug-level", "--nss"], check=True,
+ stderr_output=subprocess.STDOUT)
+ except subprocess.CalledProcessError as cpe:
+ assert cpe.returncode == 1
+ assert "SSSD is not running" in cpe.output
+
+ try:
+ get_call_output(["sssctl", "debug-level", "--nss", "0x70"], check=True,
+ stderr_output=subprocess.STDOUT)
+ except subprocess.CalledProcessError as cpe:
+ assert cpe.returncode == 1
+ assert "SSSD is not running" in cpe.output
+
+
+def test_invalidate_missing_specific_entry(ldap_conn, sanity_rfc2307, portable_LC_ALL):
+ # Ensure we will fail when invalidating missing specific entry
+ ret = subprocess.call(["sssctl", "cache-expire", "-u", "non-existing"])
+ assert ret == 1
+
+ ret = subprocess.call(["sssctl", "cache-expire", "-d", "non-existing", "-u", "dummy"])
+ assert ret == 1
+
+ ret = subprocess.call(["sssctl", "cache-expire", "-g", "non-existing"])
+ assert ret == 1
+
+ ret = subprocess.call(["sssctl", "cache-expire", "-d", "non-existing", "-g", "dummy"])
+ assert ret == 1
diff --git a/src/tests/intg/test_sudo.py b/src/tests/intg/test_sudo.py
new file mode 100644
index 0000000..ec85511
--- /dev/null
+++ b/src/tests/intg/test_sudo.py
@@ -0,0 +1,284 @@
+#
+# Sudo integration test
+#
+# Copyright (c) 2018 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import stat
+import signal
+import subprocess
+import time
+import ldap
+import pytest
+import json
+
+import config
+import ds_openldap
+import ldap_ent
+from util import unindent, get_call_output
+
+LDAP_BASE_DN = "dc=example,dc=com"
+
+
+class SudoReplyElement:
+ def __init__(self, retval, rules):
+ self.retval = retval
+ self.rules = rules
+
+
+class SudoReply:
+ def __init__(self, json_string):
+ self.jres = json.loads(json_string)
+ for reply_elem in self.jres:
+ el = SudoReplyElement(reply_elem['retval'],
+ reply_elem['result']['rules'])
+ if reply_elem['type'] == 'default':
+ self.defaults = el
+ if reply_elem['type'] == 'rules':
+ self.sudo_rules = el
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123"
+ )
+
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(ds_inst.teardown)
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(ldap_conn.unbind_s)
+ return ldap_conn
+
+
+def create_ldap_entries(ldap_conn, ent_list=None):
+ """Add LDAP entries from ent_list"""
+ if ent_list is not None:
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+
+def cleanup_ldap_entries(ldap_conn, ent_list=None):
+ """Remove LDAP entries added by create_ldap_entries"""
+ if ent_list is None:
+ for ou in ("Users", "Groups", "Netgroups", "Services", "Policies"):
+ for entry in ldap_conn.search_s(f"ou={ou},"
+ f"{ldap_conn.ds_inst.base_dn}",
+ ldap.SCOPE_ONELEVEL,
+ attrlist=[]):
+ ldap_conn.delete_s(entry[0])
+ else:
+ for entry in ent_list:
+ ldap_conn.delete_s(entry[0])
+
+
+def create_ldap_cleanup(request, ldap_conn, ent_list=None):
+ """Add teardown for removing all user/group LDAP entries"""
+ request.addfinalizer(lambda: cleanup_ldap_entries(ldap_conn, ent_list))
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list=None):
+ """Add LDAP entries and add teardown for removing them"""
+ create_ldap_entries(ldap_conn, ent_list)
+ create_ldap_cleanup(request, ldap_conn, ent_list)
+
+
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+
+def format_basic_conf(ldap_conn, schema):
+ """Format a basic SSSD configuration"""
+ schema_conf = "ldap_schema = " + schema + "\n"
+ schema_conf += "ldap_group_object_class = groupOfNames\n"
+ return unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss, sudo
+
+ [nss]
+
+ [sudo]
+ debug_level=10
+
+ [domain/LDAP]
+ {schema_conf}
+ id_provider = ldap
+ auth_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ ldap_sudo_use_host_filter = false
+ ldap_sudo_random_offset = 0
+ debug_level=10
+ """).format(**locals())
+
+
+def create_conf_file(contents):
+ """Create sssd.conf with specified contents"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+
+
+def cleanup_conf_file():
+ """Remove sssd.conf, if it exists"""
+ if os.path.lexists(config.CONF_PATH):
+ os.unlink(config.CONF_PATH)
+
+
+def create_conf_cleanup(request):
+ """Add teardown for removing sssd.conf"""
+ request.addfinalizer(cleanup_conf_file)
+
+
+def create_conf_fixture(request, contents):
+ """
+ Create sssd.conf with specified contents and add teardown for removing it
+ """
+ create_conf_file(contents)
+ create_conf_cleanup(request)
+
+
+def create_sssd_process():
+ """Start the SSSD process"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+
+def get_sssd_pid():
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ return pid
+
+
+def cleanup_sssd_process():
+ """Stop the SSSD process and remove its state"""
+ try:
+ pid = get_sssd_pid()
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+ except OSError:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+
+
+def create_sssd_fixture(request):
+ """Start SSSD and add teardown for stopping it and removing its state"""
+ create_sssd_process()
+ create_sssd_cleanup(request)
+
+
+def create_sssd_cleanup(request):
+ """Add teardown for stopping SSSD and removing its state"""
+ request.addfinalizer(cleanup_sssd_process)
+
+
+@pytest.fixture()
+def sudocli_tool(request):
+ sudocli_path = os.path.join(config.ABS_BUILDDIR,
+ "..", "..", "..", "sss_sudo_cli")
+ assert os.access(sudocli_path, os.X_OK)
+ return sudocli_path
+
+
+@pytest.fixture
+def add_common_rules(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1001, 2001)
+ ent_list.add_sudo_rule("user1_allow_less_shadow",
+ users=("user1",),
+ hosts=("ALL",),
+ commands=("/usr/bin/less /etc/shadow", "/bin/ls"))
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.mark.converted('test_sudo.py', 'test_sudo__user_allowed')
+def test_sudo_rule_for_user(add_common_rules, sudocli_tool):
+ """
+ Test that user1 is allowed in the rule but user2 is not
+ """
+ user1_rules = get_call_output([sudocli_tool, "user1"])
+ reply = SudoReply(user1_rules)
+ assert len(reply.sudo_rules.rules) == 1
+ assert reply.sudo_rules.rules[0]['cn'] == 'user1_allow_less_shadow'
+
+ user2_rules = get_call_output([sudocli_tool, "user2"])
+ reply = SudoReply(user2_rules)
+ assert len(reply.sudo_rules.rules) == 0
+
+
+@pytest.fixture
+def add_double_qualified_rules(request, ldap_conn):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user2", 1002, 2001)
+ ent_list.add_user("user3", 1003, 2001)
+ ent_list.add_user("user4", 1004, 2001)
+ ent_list.add_sudo_rule("user1_allow_less_shadow",
+ users=("user1", "user2", "user2@LDAP", "user3"),
+ hosts=("ALL",),
+ commands=("/usr/bin/less /etc/shadow", "/bin/ls"))
+ create_ldap_fixture(request, ldap_conn, ent_list)
+ conf = format_basic_conf(ldap_conn, SCHEMA_RFC2307_BIS)
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.mark.converted('test_sudo.py', 'test_sudo__duplicate_sudo_user')
+def test_sudo_rule_duplicate_sudo_user(add_double_qualified_rules,
+ sudocli_tool):
+ """
+ Test that despite user1 and user1@LDAP meaning the same user,
+ the rule is still usable
+ """
+ # Try several users to make sure we don't mangle the list
+ for u in ["user1", "user2", "user3"]:
+ user_rules = get_call_output([sudocli_tool, u])
+ reply = SudoReply(user_rules)
+ assert len(reply.sudo_rules.rules) == 1
+ assert reply.sudo_rules.rules[0]['cn'] == 'user1_allow_less_shadow'
+
+ user4_rules = get_call_output([sudocli_tool, "user4"])
+ reply = SudoReply(user4_rules)
+ assert len(reply.sudo_rules.rules) == 0
diff --git a/src/tests/intg/test_ts_cache.py b/src/tests/intg/test_ts_cache.py
new file mode 100644
index 0000000..dbade2e
--- /dev/null
+++ b/src/tests/intg/test_ts_cache.py
@@ -0,0 +1,679 @@
+#
+# LDAP integration test - test updating the sysdb and timestamp
+# cache
+#
+# Copyright (c) 2016 Red Hat, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import stat
+import ent
+import grp
+import pwd
+import config
+import signal
+import subprocess
+import time
+import ldap
+import pytest
+import ds_openldap
+import ldap_ent
+import sssd_ldb
+import sssd_id
+from util import unindent
+
+LDAP_BASE_DN = "dc=example,dc=com"
+SSSD_DOMAIN = "LDAP"
+
+SCHEMA_RFC2307 = "rfc2307"
+SCHEMA_RFC2307_BIS = "rfc2307bis"
+
+TS_ATTRLIST = ("dataExpireTimestamp", "originalModifyTimestamp")
+
+
+@pytest.fixture(scope="module")
+def ds_inst(request):
+ """LDAP server instance fixture"""
+ ds_inst = ds_openldap.DSOpenLDAP(
+ config.PREFIX, 10389, LDAP_BASE_DN,
+ "cn=admin", "Secret123")
+ try:
+ ds_inst.setup()
+ except Exception:
+ ds_inst.teardown()
+ raise
+ request.addfinalizer(lambda: ds_inst.teardown())
+ return ds_inst
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ds_inst):
+ """LDAP server connection fixture"""
+ ldap_conn = ds_inst.bind()
+ ldap_conn.ds_inst = ds_inst
+ request.addfinalizer(lambda: ldap_conn.unbind_s())
+ return ldap_conn
+
+
+def create_ldap_fixture(request, ldap_conn, ent_list):
+ """Add LDAP entries and add teardown for removing them"""
+ for entry in ent_list:
+ ldap_conn.add_s(entry[0], entry[1])
+
+ def teardown():
+ for entry in ent_list:
+ try:
+ ldap_conn.delete_s(entry[0])
+ except ldap.NO_SUCH_OBJECT:
+ # if the test already removed an object, it's fine
+ # to not care in the teardown
+ pass
+ request.addfinalizer(teardown)
+
+
+def create_conf_fixture(request, contents):
+ """Generate sssd.conf and add teardown for removing it"""
+ conf = open(config.CONF_PATH, "w")
+ conf.write(contents)
+ conf.close()
+ os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR)
+ request.addfinalizer(lambda: os.unlink(config.CONF_PATH))
+
+
+def stop_sssd():
+ pid_file = open(config.PIDFILE_PATH, "r")
+ pid = int(pid_file.read())
+ os.kill(pid, signal.SIGTERM)
+ while True:
+ try:
+ os.kill(pid, signal.SIGCONT)
+ except OSError:
+ break
+ time.sleep(1)
+
+
+def create_sssd_fixture(request):
+ """Start sssd and add teardown for stopping it and removing state"""
+ if subprocess.call(["sssd", "-D", "--logger=files"]) != 0:
+ raise Exception("sssd start failed")
+
+ def teardown():
+ try:
+ stop_sssd()
+ except Exception:
+ pass
+ for path in os.listdir(config.DB_PATH):
+ os.unlink(config.DB_PATH + "/" + path)
+ for path in os.listdir(config.MCACHE_PATH):
+ os.unlink(config.MCACHE_PATH + "/" + path)
+ request.addfinalizer(teardown)
+
+
+def load_data_to_ldap(request, ldap_conn, schema):
+ ent_list = ldap_ent.List(ldap_conn.ds_inst.base_dn)
+ ent_list.add_user("user1", 1001, 2001)
+ ent_list.add_user("user11", 1011, 2001)
+ ent_list.add_user("user21", 1021, 2001)
+
+ if schema == SCHEMA_RFC2307_BIS:
+ ent_list.add_group_bis("group1", 2001, ("user1", "user11", "user21"))
+ elif schema == SCHEMA_RFC2307:
+ ent_list.add_group("group1", 2001, ("user1", "user11", "user21"))
+ create_ldap_fixture(request, ldap_conn, ent_list)
+
+
+def load_2307bis_data_to_ldap(request, ldap_conn):
+ return load_data_to_ldap(request, ldap_conn, SCHEMA_RFC2307_BIS)
+
+
+def load_2307_data_to_ldap(request, ldap_conn):
+ return load_data_to_ldap(request, ldap_conn, SCHEMA_RFC2307)
+
+
+@pytest.fixture
+def setup_rfc2307bis(request, ldap_conn):
+ load_2307bis_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+ memcache_timeout = 1
+
+ [domain/LDAP]
+ ldap_schema = rfc2307bis
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_group_object_class = groupOfNames
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def setup_rfc2307(request, ldap_conn):
+ load_2307_data_to_ldap(request, ldap_conn)
+
+ conf = unindent("""\
+ [sssd]
+ domains = LDAP
+ services = nss
+
+ [nss]
+ memcache_timeout = 1
+
+ [domain/LDAP]
+ ldap_schema = rfc2307
+ id_provider = ldap
+ auth_provider = ldap
+ sudo_provider = ldap
+ ldap_uri = {ldap_conn.ds_inst.ldap_url}
+ ldap_search_base = {ldap_conn.ds_inst.base_dn}
+ """).format(**locals())
+ create_conf_fixture(request, conf)
+ create_sssd_fixture(request)
+ return None
+
+
+@pytest.fixture
+def ldb_examine(request):
+ ldb_conn = sssd_ldb.SssdLdb('LDAP')
+ return ldb_conn
+
+
+def invalidate_group(ldb_conn, name):
+ ldb_conn.invalidate_entry(name, sssd_ldb.TsCacheEntry.group, SSSD_DOMAIN)
+
+
+def invalidate_user(ldb_conn, name):
+ ldb_conn.invalidate_entry(name, sssd_ldb.TsCacheEntry.user, SSSD_DOMAIN)
+
+
+def get_attrs(ldb_conn, type, name, domain, attr_list):
+ sysdb_attrs = dict()
+ ts_attrs = dict()
+
+ for attr in attr_list:
+ val = ldb_conn.get_entry_attr(sssd_ldb.CacheType.sysdb,
+ type, name, domain, attr)
+ if val:
+ val = val.decode('utf-8')
+ sysdb_attrs[attr] = val
+
+ val = ldb_conn.get_entry_attr(sssd_ldb.CacheType.timestamps,
+ type, name, domain, attr)
+ if val:
+ val = val.decode('utf-8')
+ ts_attrs[attr] = val
+ return (sysdb_attrs, ts_attrs)
+
+
+def get_group_attrs(ldb_conn, name, domain, attr_list):
+ return get_attrs(ldb_conn, sssd_ldb.TsCacheEntry.group,
+ name, domain, attr_list)
+
+
+def get_user_attrs(ldb_conn, name, domain, attr_list):
+ return get_attrs(ldb_conn, sssd_ldb.TsCacheEntry.user,
+ name, domain, attr_list)
+
+
+def assert_same_attrval(adict1, adict2, attr_name):
+ assert adict1.get(attr_name) is not None and \
+ adict1.get(attr_name) == adict2.get(attr_name)
+
+
+def assert_diff_attrval(adict1, adict2, attr_name):
+ assert adict1.get(attr_name) is not None and \
+ adict1.get(attr_name) != adict2.get(attr_name)
+
+
+def prime_cache_group(ldb_conn, name, members):
+ ent.assert_group_by_name(
+ name,
+ dict(mem=ent.contains_only(*members)))
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, name,
+ SSSD_DOMAIN, TS_ATTRLIST)
+ assert_same_attrval(sysdb_attrs, ts_attrs, "dataExpireTimestamp")
+ assert_same_attrval(sysdb_attrs, ts_attrs, "originalModifyTimestamp")
+
+ # just to force different stamps and make sure memcache is gone
+ time.sleep(1)
+ invalidate_group(ldb_conn, name)
+
+ return sysdb_attrs, ts_attrs
+
+
+def prime_cache_user(ldb_conn, name, primary_gid):
+ # calling initgroups would add the initgExpire timestamp attribute and
+ # make sure that sss_cache doesn't add it with a value of 1,
+ # triggering a sysdb update
+ (res, errno, gids) = sssd_id.call_sssd_initgroups(name, primary_gid)
+ assert res == sssd_id.NssReturnCode.SUCCESS
+
+ sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, name,
+ SSSD_DOMAIN, TS_ATTRLIST)
+ assert_same_attrval(sysdb_attrs, ts_attrs, "dataExpireTimestamp")
+ assert_same_attrval(sysdb_attrs, ts_attrs, "originalModifyTimestamp")
+
+ # just to force different stamps and make sure memcache is gone
+ time.sleep(1)
+ invalidate_user(ldb_conn, name)
+
+ return sysdb_attrs, ts_attrs
+
+
+def test_group_2307bis_update_same_modstamp(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that a group update with the same modifyTimestamp does not trigger
+ sysdb cache update
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_group(
+ ldb_conn, "group1",
+ ("user1", "user11", "user21"))
+
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp")
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs,
+ "originalModifyTimestamp")
+
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp")
+ assert_same_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp")
+
+
+def test_group_2307bis_update_same_attrs(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that a group update with a different modifyTimestamp but the same
+ attrs does not trigger sysdb cache update
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_group(
+ ldb_conn, "group1",
+ ("user1", "user11", "user21"))
+
+ # modify an argument we don't save to the cache. This will bump the
+ # modifyTimestamp attribute, but the attributes themselves will be the same
+ # from sssd's point of view
+ ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_ADD, "description", b"group one")])
+ # wait for slapd to change its database
+ time.sleep(1)
+
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp")
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs,
+ "originalModifyTimestamp")
+
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp")
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp")
+
+
+def test_group_2307bis_update_diff_attrs(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that a group update with different attribute triggers cache update
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_group(
+ ldb_conn, "group1",
+ ("user1", "user11", "user21"))
+
+ user_dn = "uid=user1,ou=Users," + ldap_conn.ds_inst.base_dn
+ ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_DELETE, "member", user_dn.encode('utf-8'))])
+ # wait for slapd to change its database
+ time.sleep(1)
+
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user11", "user21")))
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert_diff_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp")
+ assert_diff_attrval(sysdb_attrs, old_sysdb_attrs,
+ "originalModifyTimestamp")
+
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp")
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp")
+
+
+def test_group_2307bis_delete_group(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that deleting a group removes it from both caches
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_group(
+ ldb_conn, "group1",
+ ("user1", "user11", "user21"))
+
+ e = ldap_ent.group_bis(ldap_conn.ds_inst.base_dn, "group1", 2001)
+ ldap_conn.delete_s(e[0])
+ # wait for slapd to change its database
+ time.sleep(1)
+
+ with pytest.raises(KeyError):
+ grp.getgrnam("group1")
+
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+ assert sysdb_attrs.get("dataExpireTimestamp") is None
+ assert sysdb_attrs.get("originalModifyTimestamp") is None
+ assert ts_attrs.get("dataExpireTimestamp") is None
+ assert ts_attrs.get("originalModifyTimestamp") is None
+
+
+def test_group_2307_update_same_modstamp(ldap_conn,
+ ldb_examine,
+ setup_rfc2307):
+ """
+ Test that a group update with the same modifyTimestamp does not trigger
+ sysdb cache update
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_group(
+ ldb_conn, "group1",
+ ("user1", "user11", "user21"))
+
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp")
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs,
+ "originalModifyTimestamp")
+
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp")
+ assert_same_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp")
+
+
+def test_group_2307_update_same_attrs(ldap_conn,
+ ldb_examine,
+ setup_rfc2307):
+ """
+ Test that a group update with a different modifyTimestamp but the same
+ attrs does not trigger sysdb cache update
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_group(
+ ldb_conn, "group1",
+ ("user1", "user11", "user21"))
+
+ # modify an argument we don't save to the cache. This will bump the
+ # modifyTimestamp attribute, but the attributes themselves will be the same
+ # from sssd's point of view
+ ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_ADD, "description", b"group one")])
+ # wait for slapd to change its database
+ time.sleep(1)
+
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user1", "user11", "user21")))
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp")
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs,
+ "originalModifyTimestamp")
+
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp")
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp")
+
+
+def test_group_2307_update_diff_attrs(ldap_conn,
+ ldb_examine,
+ setup_rfc2307):
+ """
+ Test that a group update with different attribute triggers cache update
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_group(
+ ldb_conn, "group1",
+ ("user1", "user11", "user21"))
+
+ ldap_conn.modify_s("cn=group1,ou=Groups," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_DELETE, "memberUid", b"user1")])
+ # wait for slapd to change its database
+ time.sleep(1)
+
+ ent.assert_group_by_name(
+ "group1",
+ dict(mem=ent.contains_only("user11", "user21")))
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert_diff_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp")
+ assert_diff_attrval(sysdb_attrs, old_sysdb_attrs,
+ "originalModifyTimestamp")
+
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp")
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp")
+
+
+def test_group_2307_delete_group(ldap_conn,
+ ldb_examine,
+ setup_rfc2307):
+ """
+ Test that deleting a group removes it from both caches
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_group(
+ ldb_conn, "group1",
+ ("user1", "user11", "user21"))
+
+ e = ldap_ent.group_bis(ldap_conn.ds_inst.base_dn, "group1", 2001)
+ ldap_conn.delete_s(e[0])
+ # wait for slapd to change its database
+ time.sleep(1)
+
+ with pytest.raises(KeyError):
+ grp.getgrnam("group1")
+
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+ assert sysdb_attrs.get("dataExpireTimestamp") is None
+ assert sysdb_attrs.get("originalModifyTimestamp") is None
+ assert ts_attrs.get("dataExpireTimestamp") is None
+ assert ts_attrs.get("originalModifyTimestamp") is None
+
+
+def test_user_update_same_modstamp(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that a user update with the same modifyTimestamp does not trigger
+ sysdb cache update
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001)
+
+ ent.assert_passwd_by_name("user1", dict(name="user1"))
+
+ sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp")
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs,
+ "originalModifyTimestamp")
+
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp")
+ assert_same_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp")
+
+
+def test_user_update_same_attrs(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that a user update with the same modifyTimestamp does not trigger
+ sysdb cache update
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001)
+
+ # modify an argument we don't save to the cache. This will bump the
+ # modifyTimestamp attribute, but the attributes themselves will be the same
+ # from sssd's point of view
+ ldap_conn.modify_s("uid=user1,ou=Users," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_ADD, "description", b"user one")])
+ # wait for slapd to change its database
+ time.sleep(1)
+
+ ent.assert_passwd_by_name("user1", dict(name="user1"))
+
+ sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp")
+ assert_same_attrval(sysdb_attrs, old_sysdb_attrs,
+ "originalModifyTimestamp")
+
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp")
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp")
+
+
+def test_user_update_diff_attrs(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that a user update with the same modifyTimestamp does not trigger
+ sysdb cache update
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001)
+
+ # modify an argument we don't save to the cache. This will bump the
+ # modifyTimestamp attribute, but the attributes themselves will be the same
+ # from sssd's point of view
+ ldap_conn.modify_s("uid=user1,ou=Users," + ldap_conn.ds_inst.base_dn,
+ [(ldap.MOD_REPLACE, "loginShell", b"/bin/zsh")])
+ # wait for slapd to change its database
+ time.sleep(1)
+
+ ent.assert_passwd_by_name("user1", dict(name="user1"))
+ sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+ assert_diff_attrval(sysdb_attrs, old_sysdb_attrs, "dataExpireTimestamp")
+ assert_diff_attrval(sysdb_attrs, old_sysdb_attrs,
+ "originalModifyTimestamp")
+
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "dataExpireTimestamp")
+ assert_diff_attrval(ts_attrs, old_ts_attrs, "originalModifyTimestamp")
+
+
+def test_user_2307bis_delete_user(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that deleting a user removes it from both caches
+ """
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001)
+
+ e = ldap_ent.user(ldap_conn.ds_inst.base_dn, "user1", 1001, 2001)
+
+ ldap_conn.delete_s(e[0])
+ # wait for slapd to change its database
+ time.sleep(1)
+
+ with pytest.raises(KeyError):
+ pwd.getpwnam("user1")
+ sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+ assert sysdb_attrs.get("dataExpireTimestamp") is None
+ assert sysdb_attrs.get("originalModifyTimestamp") is None
+ assert ts_attrs.get("dataExpireTimestamp") is None
+ assert ts_attrs.get("originalModifyTimestamp") is None
+
+
+def test_sss_cache_invalidate_user(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that sss_cache invalidate user in both caches
+ """
+
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_user(ldb_conn, "user1", 2001)
+
+ subprocess.call(["sss_cache", "-u", "user1"])
+
+ sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert sysdb_attrs.get("dataExpireTimestamp") == '1'
+ assert ts_attrs.get("dataExpireTimestamp") == '1'
+
+ time.sleep(1)
+ pwd.getpwnam("user1")
+ sysdb_attrs, ts_attrs = get_user_attrs(ldb_conn, "user1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert sysdb_attrs.get("dataExpireTimestamp") == '1'
+ assert_diff_attrval(ts_attrs, sysdb_attrs, "dataExpireTimestamp")
+
+
+def test_sss_cache_invalidate_group(ldap_conn,
+ ldb_examine,
+ setup_rfc2307bis):
+ """
+ Test that sss_cache invalidate group in both caches
+ """
+
+ ldb_conn = ldb_examine
+ old_sysdb_attrs, old_ts_attrs = prime_cache_group(
+ ldb_conn, "group1",
+ ("user1", "user11", "user21"))
+
+ subprocess.call(["sss_cache", "-g", "group1"])
+
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert sysdb_attrs.get("dataExpireTimestamp") == '1'
+ assert ts_attrs.get("dataExpireTimestamp") == '1'
+
+ time.sleep(1)
+ grp.getgrnam("group1")
+ sysdb_attrs, ts_attrs = get_group_attrs(ldb_conn, "group1",
+ SSSD_DOMAIN, TS_ATTRLIST)
+
+ assert sysdb_attrs.get("dataExpireTimestamp") == '1'
+ assert_diff_attrval(ts_attrs, sysdb_attrs, "dataExpireTimestamp")
diff --git a/src/tests/intg/util.py b/src/tests/intg/util.py
new file mode 100644
index 0000000..9814ec0
--- /dev/null
+++ b/src/tests/intg/util.py
@@ -0,0 +1,106 @@
+#
+# Various functions
+#
+# Copyright (c) 2015 Red Hat, Inc.
+# Author: Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import re
+import os
+import sys
+import subprocess
+import config
+import shutil
+
+UNINDENT_RE = re.compile("^ +", re.MULTILINE)
+
+
+def unindent(text):
+ """
+ Unindent text by removing at most the number of spaces present in
+ the first non-empty line from the beginning of every line.
+ """
+ indent_ref = [0]
+
+ def replace(match):
+ if indent_ref[0] == 0:
+ indent_ref[0] = len(match.group())
+ return match.group()[indent_ref[0]:]
+ return UNINDENT_RE.sub(replace, text)
+
+
+def run_shell():
+ """
+ Execute an interactive shell under "screen", preserving environment.
+ For use as a breakpoint for debugging.
+ """
+ my_env = os.environ.copy()
+ my_env["ROOT_DIR"] = config.PREFIX
+
+ # screen filter out LD_* evniroment varibles.
+ # Back-up them and set them later in screenrc
+ my_env["_LD_LIBRARY_PATH"] = os.getenv("LD_LIBRARY_PATH", "")
+ my_env["_LD_PRELOAD"] = os.getenv("LD_PRELOAD", "")
+
+ subprocess.call([
+ "screen", "-DAm", "-S", "sssd_cwrap_session", "-c",
+ ".config/screenrc"],
+ env=my_env
+ )
+
+
+def first_dir(*args):
+ """Return first argument that points to an existing directory."""
+ for arg in args:
+ if os.path.isdir(arg):
+ return arg
+
+
+def backup_envvar_file(name):
+ path = os.environ[name]
+ backup_path = path + ".bak"
+ shutil.copyfile(path, backup_path)
+ return path
+
+
+def restore_envvar_file(name):
+ path = os.environ[name]
+ backup_path = path + ".bak"
+ os.rename(backup_path, path)
+
+
+def get_call_output(cmd, stderr_output=subprocess.PIPE, check=False):
+ """
+ Executes the provided command.
+ When check is set to True, this function will throw an exception
+ if the command returns with a non-zero value.
+ """
+
+ if (sys.version_info.major < 3
+ or (sys.version_info.major == 3 and sys.version_info.minor < 7)):
+ try:
+ output = subprocess.check_output(cmd, universal_newlines=True,
+ stderr=stderr_output)
+ except subprocess.CalledProcessError as err:
+ if (not check):
+ output = err.output
+ else:
+ raise err
+ return output
+
+ process = subprocess.run(cmd, check=check, text=True,
+ stdout=subprocess.PIPE, stderr=stderr_output)
+ return process.stdout